diff --git a/app/Http/Controllers/AssetController.php b/app/Http/Controllers/AssetController.php index de1db9e..53fc60e 100644 --- a/app/Http/Controllers/AssetController.php +++ b/app/Http/Controllers/AssetController.php @@ -14,6 +14,7 @@ use App\Models\Unit; use App\Models\VendorContract; use App\Models\Warranty; use App\Services\Asset\AssetService; +use App\Services\System\SystemSettingService; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -22,7 +23,10 @@ use Illuminate\Support\Str; class AssetController extends Controller { - public function __construct(private readonly AssetService $assets) + public function __construct( + private readonly AssetService $assets, + private readonly SystemSettingService $settings + ) { } @@ -209,6 +213,13 @@ class AssetController extends Controller return back()->withErrors(['file' => 'File tidak bisa dibaca']); } + $maxRows = (int) $this->settings->get('asset.import_max_rows', 2000); + $allowRemoteImages = (bool) $this->settings->get('asset.import_allow_remote_images', true); + $dedupePriority = $this->settings->get('asset.import_dedupe_priority', 'code'); + $defaultStatusName = $this->settings->get('asset.import_default_status'); + $defaultCategoryName = $this->settings->get('asset.import_default_category'); + $defaultLocationName = $this->settings->get('asset.import_default_location'); + // Jika ada ZIP gambar, siapkan mapping filename => binary $images = []; if ($request->hasFile('images_zip')) { @@ -235,9 +246,15 @@ class AssetController extends Controller $statuses = AssetStatus::pluck('id','name')->toArray(); $categories = AssetCategory::pluck('id','name')->toArray(); $locations = AssetLocation::pluck('id','name')->toArray(); + $defaultStatusId = $statuses[$defaultStatusName] ?? null; + $defaultCategoryId = $categories[$defaultCategoryName] ?? null; + $defaultLocationId = $locations[$defaultLocationName] ?? null; while (($data = fgetcsv($handle)) !== false) { $line++; + if ($maxRows && ($line-1) > $maxRows) { + break; + } $row = array_combine($header, $data); if (!$row) { $rows[] = ['line' => $line, 'status' => 'error', 'message' => 'Header tidak sesuai', 'data' => $data]; @@ -249,13 +266,23 @@ class AssetController extends Controller $errors = []; if (!$name) $errors[] = 'Nama wajib'; - $statusId = $statuses[$row['status'] ?? ''] ?? null; - $categoryId = $categories[$row['category'] ?? ''] ?? null; - $locationId = $locations[$row['location'] ?? ''] ?? null; + $statusId = $statuses[$row['status'] ?? ''] ?? $defaultStatusId; + $categoryId = $categories[$row['category'] ?? ''] ?? $defaultCategoryId; + $locationId = $locations[$row['location'] ?? ''] ?? $defaultLocationId; - $targetId = $code && isset($existingCodes[$code]) ? $existingCodes[$code] : null; - if (!$targetId && $serial && isset($existingSerials[$serial])) { - $targetId = $existingSerials[$serial]; + $targetId = null; + if ($dedupePriority === 'serial') { + if ($serial && isset($existingSerials[$serial])) { + $targetId = $existingSerials[$serial]; + } elseif ($code && isset($existingCodes[$code])) { + $targetId = $existingCodes[$code]; + } + } else { + if ($code && isset($existingCodes[$code])) { + $targetId = $existingCodes[$code]; + } elseif ($serial && isset($existingSerials[$serial])) { + $targetId = $existingSerials[$serial]; + } } $rows[] = [ @@ -321,13 +348,15 @@ class AssetController extends Controller if ($assetModel) { $binary = null; if ($imageUrl) { - try { - $resp = \Illuminate\Support\Facades\Http::get($imageUrl); - if ($resp->ok()) { - $binary = $resp->body(); + if ($allowRemoteImages) { + try { + $resp = \Illuminate\Support\Facades\Http::get($imageUrl); + if ($resp->ok()) { + $binary = $resp->body(); + } + } catch (\Throwable $e) { + // abaikan error download agar import tetap lanjut } - } catch (\Throwable $e) { - // abaikan error download agar import tetap lanjut } } elseif ($imageFile && isset($images[$imageFile])) { $binary = $images[$imageFile]; @@ -350,6 +379,8 @@ class AssetController extends Controller private function validateRequest(Request $request, ?string $assetId = null): array { + $rfidRequired = $this->settings->get('asset.rfid_required', false); + $nfcRequired = $this->settings->get('asset.nfc_required', false); return $request->validate([ 'name' => ['required', 'string', 'max:255'], 'serial_number' => ['nullable', 'string', 'max:255'], @@ -372,6 +403,21 @@ class AssetController extends Controller 'capex_opex' => ['nullable', 'in:capex,opex'], 'vendor_contract_id' => ['nullable', 'uuid', 'exists:vendor_contracts,id'], 'metadata' => ['nullable', 'array'], + 'rfid_tag' => array_filter([ + 'string','max:255', + $rfidRequired ? 'required' : null, + "unique:assets,rfid_tag,$assetId", + ]), + 'nfc_tag' => array_filter([ + 'string','max:255', + $nfcRequired ? 'required' : null, + "unique:assets,nfc_tag,$assetId", + ]), + 'label_template' => ['nullable','string','max:100'], + 'is_consumable' => ['nullable','boolean'], + 'quantity' => ['nullable','integer','min:1'], + 'available_quantity' => ['nullable','integer','min:0'], + 'is_pool' => ['nullable','boolean'], ]); } } diff --git a/app/Services/Asset/AssetService.php b/app/Services/Asset/AssetService.php index f9a1b40..131827b 100644 --- a/app/Services/Asset/AssetService.php +++ b/app/Services/Asset/AssetService.php @@ -12,6 +12,7 @@ use App\Models\AssetAudit; use App\Models\AssetChangelog; use App\Models\AssetMaintenance; use App\Services\Logging\ActivityLogger; +use App\Services\Integration\WebhookDispatcher; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; @@ -21,7 +22,8 @@ class AssetService { public function __construct( private readonly SystemSettingService $settings, - private readonly ActivityLogger $logger + private readonly ActivityLogger $logger, + private readonly WebhookDispatcher $webhooks ) { } @@ -50,6 +52,7 @@ class AssetService } $this->logHistory($asset, 'created', 'Aset dibuat', $data); $this->logChangelog($asset, 'created', $data); + $this->dispatchWebhook('asset.created', $asset, $data); return $asset; }); @@ -67,6 +70,7 @@ class AssetService } $this->logHistory($asset, 'updated', 'Aset diperbarui', $data); $this->logChangelog($asset, 'updated', $data); + $this->dispatchWebhook('asset.updated', $asset, $data); return $asset; }); @@ -100,11 +104,13 @@ class AssetService private function generateQr(Asset $asset): void { - $path = "qr/{$asset->code}.svg"; + $format = $this->settings->get('asset.qr_format', 'svg'); + $size = (int) $this->settings->get('asset.qr_size', 300); + $path = "qr/{$asset->code}.{$format}"; $url = route('assets.public.show', $asset); - $image = QrCode::format('svg') - ->size(300) + $image = QrCode::format($format) + ->size(max(100, $size)) ->margin(1) ->generate($url); @@ -161,6 +167,7 @@ class AssetService } $this->logHistory($asset, 'movement', 'Perpindahan aset', $movement->toArray()); + $this->dispatchWebhook('asset.movement', $asset, $movement->toArray()); return $movement; }); @@ -201,6 +208,7 @@ class AssetService } $this->logHistory($asset, 'disposal', 'Aset didispose', $disposal->toArray()); + $this->dispatchWebhook('asset.disposal', $asset, $disposal->toArray()); return $disposal; }); @@ -231,6 +239,7 @@ class AssetService 'disposal_id' => $disposal->id, 'notes' => $notes, ]); + $this->dispatchWebhook('asset.reverse_disposal', $asset, ['disposal_id' => $disposal->id, 'notes' => $notes]); return $asset; }); @@ -252,6 +261,7 @@ class AssetService ]); $this->logHistory($asset, 'audit', 'Audit aset', $audit->toArray()); + $this->dispatchWebhook('asset.audit', $asset, $audit->toArray()); return $audit; }); @@ -284,8 +294,19 @@ class AssetService } $this->logHistory($asset, 'maintenance', 'Perawatan aset', $maintenance->toArray()); + $this->dispatchWebhook('asset.maintenance', $asset, $maintenance->toArray()); return $maintenance; }); } + + private function dispatchWebhook(string $event, Asset $asset, array $payload = []): void + { + $this->webhooks->send($event, [ + 'asset_id' => $asset->id, + 'asset_code' => $asset->code ?? null, + 'asset_status_id' => $asset->asset_status_id, + 'payload' => $payload, + ]); + } }