Sindbad~EG File Manager
<?php
namespace App\Jobs;
use App\Models\ImportRun;
use App\Models\Pemasukan;
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 Pemasukan (table: tr_masuk)
* Syarat: semua kode_brg harus ada di tr_stock.
* Jika ada yang tidak ada: gagal dan tidak mengubah tr_masuk.
* Jika lolos: replace berdasarkan (no_inv, kode_brg) => delete dulu record lama yang sama, lalu insert.
*/
class ImportTrMasukJob 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')),
'tgl_kirim' => $this->parseDate($this->val($r, $map, 'tgl_kirim')),
'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) {
Pemasukan::where('no_inv', $noInv)
->whereIn('kode_brg', array_values(array_unique($kodeList)))
->delete();
}
if (!empty($prepared)) {
Pemasukan::insert($prepared);
}
DB::commit();
$run->update(['status' => 'done', 'processed_rows' => $total, 'message' => 'Import Pemasukan 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