Sindbad~EG File Manager

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

<?php

namespace App\Jobs;

use App\Models\ImportRun;
use App\Models\Pengeluaran;
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;
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;

/**
 * Import untuk menu Pengeluaran (table: tr_keluar)
 * Syarat: semua kode_brg harus ada di tr_stock.
 * Jika ada yang tidak ada: gagal dan tidak mengubah tr_keluar.
 * Jika lolos: replace berdasarkan (no_inv, kode_brg) => delete dulu record lama yang sama, lalu insert.
 */
class ImportTrKeluarJob 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,
        ]);

        $path = Storage::disk('local')->path($run->file_path);
        $sheet = Excel::toCollection(null, $path)->first();

        if (!$sheet || $sheet->count() < 2) {
            $run->update(['status' => 'failed', 'message' => 'File kosong / format salah.']);
            return;
        }

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

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

        $header = $sheet->first()->toArray();

        // 1) buat map dulu
        $map = $this->buildHeaderMap($header);

        // 2) ambil rows lalu FILTER hanya baris yang punya no_inv & kode_brg
        $rows = $sheet->slice(1)
            ->filter(function ($r) use ($map) {
                $arr = $r->toArray();

                $noInvIdx = $map['no_inv'] ?? null;
                $kodeIdx  = $map['kode_brg'] ?? null;

                if ($noInvIdx === null || $kodeIdx === null) {
                    // kalau header tidak ada, jangan buang semua baris
                    return true;
                }

                $noInv = trim((string)($arr[$noInvIdx] ?? ''));
                $kode  = trim((string)($arr[$kodeIdx] ?? ''));

                return $noInv !== '' && $kode !== '';
            })
            ->values();

        // 3) total setelah difilter (jadi sesuai data beneran)
        $total = $rows->count();
        $run->update(['total_rows' => $total]);
        

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

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

            $kodeList = $chunk->map(fn($r) => trim((string)($this->val($r, $map, 'kode_brg') ?? '')))
                ->filter()->unique()->values()->all();

            // $existsSet = TrStock::whereIn('kode_brg', $kodeList)
            $existsSet = ms_stock::whereIn('kode_brg', $kodeList)
                ->pluck('kode_brg')
                ->map(fn($x) => (string)$x)
                ->flip();

            foreach ($chunk as $r) {
                $noInv = trim((string)($this->val($r, $map, 'no_inv') ?? ''));
                $kode  = trim((string)($this->val($r, $map, 'kode_brg') ?? ''));
                $nama  = (string)($this->val($r, $map, 'nama_brg') ?? '');

                if ($noInv === '' || $kode === '') continue;

                if (!isset($existsSet[$kode])) {
                    $missing[$kode.'|'.$nama] = ['kode_brg' => $kode, 'nama_brg' => $nama];
                    continue;
                }

                $keysToReplace[$noInv.'|'.$kode] = ['no_inv' => $noInv, 'kode_brg' => $kode];

                $prepared[] = [
                    'no_inv' => $noInv,
                    'tgl_inv' => $this->parseDate($this->val($r, $map, 'tgl_inv')),
                    'tr_code' => (string)($this->val($r, $map, 'tr_code') ?? ''),
                    'jns_tr' => (string)($this->val($r, $map, 'jns_tr') ?? ''),
                    'gudang' => (string)($this->val($r, $map, 'gudang') ?? ''),
                    'kode_brg' => $kode,
                    'kode_kategori_brg' => (string)($this->val($r, $map, 'kode_kategori_brg') ?? ''),
                    'quantity' => (float)$this->toNumber($this->val($r, $map, 'jumlah') ?? $this->val($r, $map, 'quantity') ?? 0),
                    'satuan' => (string)($this->val($r, $map, 'satuan') ?? ''),
                    'harga' => (float)$this->toNumber($this->val($r, $map, 'harga') ?? 0),
                    'tglid' => now(),
                ];
            }

            $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 di tr_stock. Tidak ada data yang diubah.',
            ]);
            return;
        }

        DB::beginTransaction();
        try {
            $grouped = [];
            foreach ($keysToReplace as $k) {
                $grouped[$k['no_inv']][] = $k['kode_brg'];
            }

            foreach ($grouped as $noInv => $kodeList) {
                Pengeluaran::where('no_inv', $noInv)
                    ->whereIn('kode_brg', array_values(array_unique($kodeList)))
                    ->delete();
            }

            // if (!empty($prepared)) {
            //     Pengeluaran::insert($prepared);
            // }

            if (!empty($prepared)) {
            
                foreach (array_chunk($prepared, 500) as $chunk) {
                    Pengeluaran::insert($chunk);
                }
            
            }

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

    private function buildHeaderMap(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 val($row, array $map, string $key)
    // {
    //     $arr = $row->toArray();
    //     $k = strtolower($key);
    //     return isset($map[$k]) ? ($arr[$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 val($row, array $map, string $key)
{
    $arr = $row->toArray();
    $k = strtolower($key);

    $aliases = [
        'tgl_inv' => ['tgl_inv', 'tglinv', 'tanggal_invoice', 'tanggal_inv'],
        'tgl_kirim' => ['tgl_kirim', 'tglkirim', 'tanggal_kirim'],
    ];

    if (isset($map[$k])) return $arr[$map[$k]] ?? null;

    if (isset($aliases[$k])) {
        foreach ($aliases[$k] as $a) {
            $a = strtolower($a);
            if (isset($map[$a])) return $arr[$map[$a]] ?? null;
        }
    }

    return null;
}


private function parseDate($value): ?string
{
    if ($value === null || $value === '') return null;

    // 1) Kalau Excel kirim angka serial (date)
    if (is_numeric($value)) {
        try {
            return Carbon::instance(ExcelDate::excelToDateTimeObject($value))->toDateString();
        } catch (\Throwable) {
            // lanjut ke parsing string
        }
    }

    // 2) Kalau Excel kirim string
    $v = trim((string)$value);

    foreach (['d/m/Y', 'm/d/Y', 'Y-m-d', 'd-m-Y', 'm-d-Y'] as $fmt) {
        try {
            return Carbon::createFromFormat($fmt, $v)->toDateString();
        } catch (\Throwable) {}
    }

    try {
        return Carbon::parse($v)->toDateString();
    } 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