Duffer Derek

Current Path : /home/webg5288/www/laravel_kendal/app/Jobs/
Upload File :
Current File : /home/webg5288/www/laravel_kendal/app/Jobs/ImportModuleExcelJob.php

<?php

namespace App\Jobs;

use App\Models\Adjustment;
use App\Models\ImportRun;
use App\Models\Pemasukan;
use App\Models\Pengeluaran;
use App\Models\StockOpname;
use App\Models\TrStock;
use App\Models\ms_stock;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Facades\Excel;

class ImportModuleExcelJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(public int $importRunId) {}

    public function handle(): void
    {
        $run = ImportRun::findOrFail($this->importRunId);
        $run->update(['status' => 'running', 'processed_rows' => 0, 'missing_items' => null, 'message' => null]);

        $fullPath = Storage::disk('local')->path($run->file_path);
        $sheet = Excel::toCollection(null, $fullPath)->first();
        if (!$sheet || $sheet->count() === 0) {
            $run->update(['status' => 'failed', 'message' => 'File kosong / tidak terbaca.']);
            return;
        }

        $header = $sheet->first()->toArray();
        $rows = $sheet->slice(1)->values();
        $map = $this->headerMap($header);

        $total = $rows->count();
        $run->update(['total_rows' => $total]);

        // Config per module
        $cfg = $this->config($run->type);
        if ($cfg === null) {
            $run->update(['status' => 'failed', 'message' => 'Tipe import tidak dikenali: '.$run->type]);
            return;
        }

        $missing = [];
        $prepared = [];
        $keysToReplace = [];

        $chunkSize = 500;
        for ($i = 0; $i < $total; $i += $chunkSize) {
            $chunk = $rows->slice($i, $chunkSize);

            // Validation lookup (if any)
            $existsSet = null;
            if ($cfg['validate'] !== null) {
                $codeField = $cfg['validate']['code_field'];
                $lookupModel = $cfg['validate']['model'];

                $kodeList = $chunk->map(fn($r) => (string)($this->v($r, $map, $cfg['columns']['kode_brg'] ?? 'kode_brg') ?? ''))
                    ->filter()->unique()->values()->all();

                $existsSet = $lookupModel::whereIn($codeField, $kodeList)
                    ->pluck($codeField)
                    ->map(fn($x) => (string)$x)
                    ->flip();
            }

            foreach ($chunk as $r) {
                $row = $this->normalizeRow($r, $map, $cfg['columns']);

                // required keys
                foreach ($cfg['required'] as $req) {
                    if (($row[$req] ?? '') === '') {
                        continue 2;
                    }
                }

                // validate code
                if ($existsSet !== null) {
                    $kode = (string)($row['kode_brg'] ?? '');
                    if ($kode !== '' && !isset($existsSet[$kode])) {
                        $nama = (string)($row['nama_brg'] ?? '');
                        $missing[$kode.'|'.$nama] = ['kode_brg' => $kode, 'nama_brg' => $nama];
                        continue;
                    }
                }

                // keys for replace
                $key = [];
                foreach ($cfg['key_fields'] as $k) {
                    $key[$k] = $row[$k] ?? null;
                }
                $keysToReplace[] = $key;

                $prepared[] = $this->buildInsert($row, $cfg);
            }

            $run->update(['processed_rows' => min($i + $chunkSize, $total)]);
        }

        if (!empty($missing)) {
            $run->update([
                'status' => 'failed',
                'missing_items' => array_values($missing),
                'message' => 'Import gagal: ada kode barang yang tidak ditemukan. Data tidak diubah.',
            ]);
            return;
        }

        DB::beginTransaction();
        try {
            $this->replaceData($cfg, $keysToReplace, $prepared);
            DB::commit();
            $run->update(['status' => 'done', 'processed_rows' => $total, 'message' => 'Import sukses.']);
        } catch (\Throwable $e) {
            DB::rollBack();
            $run->update(['status' => 'failed', 'message' => $e->getMessage()]);
            throw $e;
        }
    }

    private function config(string $type): ?array
    {
        return match ($type) {
            // Pemasukan -> tr_masuk, validate ke tr_stock
            'tr_masuk' => [
                'model' => Pemasukan::class,
                'key_fields' => ['no_inv', 'kode_brg'],
                'required' => ['no_inv', 'kode_brg'],
                'validate' => ['model' => TrStock::class, 'code_field' => 'kode_brg'],
                'columns' => [
                    'no_inv' => 'no_inv',
                    'tgl_inv' => 'tgl_inv',
                    'tgl_kirim' => 'tgl_kirim',
                    'tr_code' => 'tr_code',
                    'jns_tr' => 'jns_tr',
                    'gudang' => 'gudang',
                    'kode_brg' => 'kode_brg',
                    'kode_kategori_brg' => 'kode_kategori_brg',
                    'quantity' => 'jumlah',
                    'satuan' => 'satuan',
                    'harga' => 'harga',
                    'userid' => 'userid',
                    'tglid' => 'tgl_id',
                    'nama_brg' => 'nama_brg',
                ],
                'insert_fields' => ['no_inv','tgl_inv','tgl_kirim','tr_code','jns_tr','gudang','kode_brg','kode_kategori_brg','quantity','satuan','harga','tglid','userid'],
            ],

            // Pengeluaran -> tr_keluar, validate ke tr_stock
            'tr_keluar' => [
                'model' => Pengeluaran::class,
                'key_fields' => ['no_inv', 'kode_brg'],
                'required' => ['no_inv', 'kode_brg'],
                'validate' => ['model' => TrStock::class, 'code_field' => 'kode_brg'],
                'columns' => [
                    'no_inv' => 'no_inv',
                    'tgl_inv' => 'tgl_inv',
                    'tr_code' => 'tr_code',
                    'jns_tr' => 'jns_tr',
                    'gudang' => 'gudang',
                    'kode_brg' => 'kode_brg',
                    'kode_kategori_brg' => 'kode_kategori_brg',
                    'quantity' => 'jumlah',
                    'satuan' => 'satuan',
                    'harga' => 'harga',
                    'tglid' => 'tgl_id',
                    'nama_brg' => 'nama_brg',
                ],
                'insert_fields' => ['no_inv','tgl_inv','tr_code','jns_tr','gudang','kode_brg','kode_kategori_brg','quantity','satuan','harga','tglid'],
            ],

            // Adjustment -> tr_stock, validate ke ms_stock
            'tr_stock_adj' => [
                'model' => Adjustment::class,
                'key_fields' => ['no_inv', 'kode_brg', 'type'],
                'required' => ['no_inv', 'kode_brg', 'type'],
                'validate' => ['model' => ms_stock::class, 'code_field' => 'kode_brg'],
                'columns' => [
                    'no_inv' => 'no_inv',
                    'tgl' => 'tgl',
                    'kode_brg' => 'kode_brg',
                    'type' => 'type',
                    'quantity' => 'jumlah',
                    'satuan' => 'satuan',
                    'harga' => 'harga',
                    'keterangan' => 'keterangan',
                    'tglid' => 'tgl_id',
                    'nama_brg' => 'nama_brg',
                ],
                'insert_fields' => ['no_inv','tgl','kode_brg','type','quantity','satuan','harga','keterangan','tglid'],
            ],

            // Stock Opname -> tr_stock, validate ke ms_stock
            'tr_stock_opname' => [
                'model' => StockOpname::class,
                'key_fields' => ['no_inv', 'kode_brg', 'type'],
                'required' => ['no_inv', 'kode_brg', 'type'],
                'validate' => ['model' => ms_stock::class, 'code_field' => 'kode_brg'],
                'columns' => [
                    'no_inv' => 'no_inv',
                    'tgl' => 'tgl',
                    'kode_brg' => 'kode_brg',
                    'type' => 'type',
                    'quantity' => 'jumlah',
                    'satuan' => 'satuan',
                    'harga' => 'harga',
                    'keterangan' => 'keterangan',
                    'tglid' => 'tgl_id',
                    'nama_brg' => 'nama_brg',
                ],
                'insert_fields' => ['no_inv','tgl','kode_brg','type','quantity','satuan','harga','keterangan','tglid'],
            ],

            // Master barang
            'ms_stock' => [
                'model' => ms_stock::class,
                'key_fields' => ['kode_brg'],
                'required' => ['kode_brg'],
                'validate' => null,
                'columns' => [
                    'kode_brg' => 'kode_brg',
                    'nama_brg' => 'nama_brg',
                    'kelompok' => 'kelompok',
                    'jenis' => 'jenis',
                    'merk' => 'merk',
                    'satuan' => 'satuan',
                    'status' => 'status',
                ],
                'insert_fields' => ['kode_brg','nama_brg','kelompok','jenis','merk','satuan','status'],
            ],

            default => null,
        };
    }

    private function replaceData(array $cfg, array $keys, array $rows): void
    {
        $model = $cfg['model'];
        $keyFields = $cfg['key_fields'];

        if ($model === ms_stock::class) {
            $kodeList = array_values(array_unique(array_map(fn($k) => (string)($k['kode_brg'] ?? ''), $keys)));
            if (!empty($kodeList)) {
                ms_stock::whereIn('kode_brg', $kodeList)->delete();
            }
            if (!empty($rows)) {
                DB::table('ms_stock')->insert($rows);
            }
            return;
        }

        if ($model === Pemasukan::class || $model === Pengeluaran::class) {
            // group delete by no_inv
            $grouped = [];
            foreach ($keys as $k) {
                $grouped[$k['no_inv']][] = $k['kode_brg'];
            }
            foreach ($grouped as $noInv => $kodeList) {
                $model::where('no_inv', $noInv)->whereIn('kode_brg', array_values(array_unique($kodeList)))->delete();
            }
            if (!empty($rows)) {
                $model::insert($rows);
            }
            return;
        }

        // tr_stock: group delete by no_inv and type
        $grouped = [];
        foreach ($keys as $k) {
            $nk = ($k['no_inv'] ?? '').'|'.($k['type'] ?? '');
            $grouped[$nk]['no_inv'] = $k['no_inv'] ?? null;
            $grouped[$nk]['type'] = $k['type'] ?? null;
            $grouped[$nk]['kode'][] = $k['kode_brg'] ?? null;
        }
        foreach ($grouped as $g) {
            $kodeList = array_values(array_unique(array_filter($g['kode'])));
            if (empty($kodeList)) continue;
            $model::where('no_inv', $g['no_inv'])->where('type', $g['type'])->whereIn('kode_brg', $kodeList)->delete();
        }
        if (!empty($rows)) {
            $model::insert($rows);
        }
    }

    private function buildInsert(array $row, array $cfg): array
    {
        $out = [];
        foreach ($cfg['insert_fields'] as $f) {
            $out[$f] = $row[$f] ?? null;
        }

        // normalize numeric + date
        foreach (['quantity','harga'] as $n) {
            if (array_key_exists($n, $out)) $out[$n] = (float)$this->toNumber($out[$n]);
        }

        foreach (['tgl_inv','tgl_kirim','tgl'] as $d) {
            if (array_key_exists($d, $out)) $out[$d] = $this->parseDate($out[$d]);
        }

        if (array_key_exists('tglid', $out)) {
            $out['tglid'] = $this->parseDateTime($out['tglid']) ?? now();
        }

        return $out;
    }

    private function normalizeRow($rowObj, array $map, array $columns): array
    {
        $arr = $rowObj->toArray();
        $out = [];

        foreach ($columns as $field => $headerKey) {
            $out[$field] = $this->v($arr, $map, $headerKey);
        }

        // alias: if quantity missing, try 'quantity'
        if (($out['quantity'] ?? null) === null) {
            $out['quantity'] = $this->v($arr, $map, 'quantity');
        }
        // alias: category header 'kategori_barang'
        if (($out['kode_kategori_brg'] ?? null) === null) {
            $out['kode_kategori_brg'] = $this->v($arr, $map, 'kategori_barang');
        }
        // alias datetime
        if (($out['tglid'] ?? null) === null) {
            $out['tglid'] = $this->v($arr, $map, 'tglid');
        }

        return $out;
    }

    private function headerMap(array $header): array
    {
        $map = [];
        foreach ($header as $idx => $h) {
            $key = strtolower(trim((string)$h));
            $key = str_replace([' ', '-'], '_', $key);
            $map[$key] = $idx;
        }
        return $map;
    }

    private function v(array $rowArr, array $map, string $headerKey)
    {
        $k = strtolower(trim($headerKey));
        $k = str_replace([' ', '-'], '_', $k);
        return isset($map[$k]) ? ($rowArr[$map[$k]] ?? null) : null;
    }

    private function parseDate($value): ?string
    {
        if ($value === null || $value === '') return null;
        $v = trim((string)$value);
        foreach (['d/m/Y','m/d/Y','Y-m-d'] as $fmt) {
            try { return Carbon::createFromFormat($fmt, $v)->toDateString(); } catch (\Throwable) {}
        }
        try { return Carbon::parse($v)->toDateString(); } catch (\Throwable) {}
        return null;
    }

    private function parseDateTime($value): ?string
    {
        if ($value === null || $value === '') return null;
        $v = trim((string)$value);
        foreach (['d/m/Y H:i','d/m/Y H:i:s','m/d/Y H:i','Y-m-d H:i:s'] as $fmt) {
            try { return Carbon::createFromFormat($fmt, $v)->toDateTimeString(); } catch (\Throwable) {}
        }
        try { return Carbon::parse($v)->toDateTimeString(); } catch (\Throwable) {}
        return null;
    }

    private function toNumber($value): float
    {
        if ($value === null || $value === '') return 0;
        $v = trim((string)$value);
        if (str_contains($v, ',') && str_contains($v, '.')) {
            $v = str_replace('.', '', $v);
            $v = str_replace(',', '.', $v);
        } else {
            $v = str_replace(',', '', $v);
        }
        return (float)$v;
    }
}

Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists