Duffer Derek
<?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