authorizeTmsAccess('tms.customs.view'); $business_id = $this->tmsUtil->getBusinessId(); if ($request->ajax()) { $query = CustomsClearance::where('business_id', $business_id) ->with(['contact:id,name', 'broker:id,name', 'shipment:id,ref_no']) ->orderByDesc('created_at'); if ($status = $request->get('status')) { $query->where('status', $status); } if ($type = $request->get('clearance_type')) { $query->where('clearance_type', $type); } $statuses = $this->tmsUtil->customsStatuses(); $types = $this->tmsUtil->customsTypes(); return DataTables::of($query) ->editColumn('status', fn ($row) => ''.($statuses[$row->status] ?? $row->status).'') ->editColumn('clearance_type', fn ($row) => $types[$row->clearance_type] ?? $row->clearance_type) ->addColumn('contact_name', fn ($row) => $row->contact?->name ?? '—') ->addColumn('broker_name', fn ($row) => $row->broker?->name ?? '—') ->addColumn('shipment_ref', fn ($row) => $row->shipment?->ref_no ?? '—') ->editColumn('total_duties', fn ($row) => @num_format($row->total_duties)) ->addColumn('action', function ($row) { $html = '
'; return $html; }) ->rawColumns(['status', 'action']) ->make(true); } return view('tms::customs.index', [ 'statuses' => $this->tmsUtil->customsStatuses(), 'types' => $this->tmsUtil->customsTypes(), ]); } public function create(Request $request) { $this->authorizeTmsAccess('tms.customs.create'); $business_id = $this->tmsUtil->getBusinessId(); $shipment = null; $transaction = null; if ($request->filled('shipment_id')) { $shipment = Shipment::where('business_id', $business_id)->find($request->shipment_id); } if ($request->filled('transaction_id')) { $transaction = Transaction::where('business_id', $business_id) ->whereIn('type', ['purchase', 'sell']) ->find($request->transaction_id); } return view('tms::customs.create', [ 'types' => $this->tmsUtil->customsTypes(), 'statuses' => $this->tmsUtil->customsStatuses(), 'customers' => $this->tmsUtil->getCustomersDropdown($business_id), 'suppliers' => $this->tmsUtil->getSuppliersDropdown($business_id), 'brokers' => $this->tmsUtil->getBrokersDropdown($business_id), 'shipments' => $this->tmsUtil->getShipmentsDropdown($business_id), 'shipment' => $shipment, 'transaction' => $transaction, 'document_types' => $this->tmsUtil->customsDocumentTypes(), ]); } public function store(Request $request) { $this->authorizeTmsAccess('tms.customs.create'); $business_id = $this->tmsUtil->getBusinessId(); $data = $this->validateClearance($request); $clearance = CustomsClearance::create(array_merge($data, [ 'business_id' => $business_id, 'ref_no' => $this->tmsUtil->generateCustomsRefNo($business_id), 'created_by' => auth()->id(), ])); $this->customsService->syncItems($clearance, $request->input('items', [])); $this->customsService->logStatus($clearance, $clearance->status, __('tms::lang.customs_created')); if ($clearance->shipment_id) { $shipment = Shipment::where('business_id', $business_id)->find($clearance->shipment_id); if ($shipment) { $this->customsSyncService->linkMutually($clearance, $shipment); $this->customsSyncService->syncShipmentFromCustoms($clearance->fresh()); } } else { $this->customsSyncService->linkByTransaction($clearance); } return redirect() ->action([self::class, 'show'], [$clearance->id]) ->with('status', ['success' => 1, 'msg' => __('tms::lang.customs_created')]); } public function show($id) { $this->authorizeTmsAccess('tms.customs.view'); $clearance = $this->findClearance($id); $clearance->load([ 'contact', 'broker', 'shipment', 'transaction', 'items', 'documents.uploader', 'statusLogs.creator', ]); return view('tms::customs.show', [ 'clearance' => $clearance, 'statuses' => $this->tmsUtil->customsStatuses(), 'types' => $this->tmsUtil->customsTypes(), 'document_types' => $this->tmsUtil->customsDocumentTypes(), 'shipment_statuses' => $this->tmsUtil->shipmentStatuses(), 'shipment_types' => $this->tmsUtil->shipmentTypes(), ]); } public function edit($id) { $this->authorizeTmsAccess('tms.customs.update'); $business_id = $this->tmsUtil->getBusinessId(); $clearance = $this->findClearance($id); $clearance->load('items'); return view('tms::customs.edit', [ 'clearance' => $clearance, 'types' => $this->tmsUtil->customsTypes(), 'statuses' => $this->tmsUtil->customsStatuses(), 'customers' => $this->tmsUtil->getCustomersDropdown($business_id), 'suppliers' => $this->tmsUtil->getSuppliersDropdown($business_id), 'brokers' => $this->tmsUtil->getBrokersDropdown($business_id), 'shipments' => $this->tmsUtil->getShipmentsDropdown($business_id), 'document_types' => $this->tmsUtil->customsDocumentTypes(), ]); } public function update(Request $request, $id) { $this->authorizeTmsAccess('tms.customs.update'); $clearance = $this->findClearance($id); $oldStatus = $clearance->status; $data = $this->validateClearance($request); $oldShipmentId = $clearance->shipment_id; $clearance->update($data); $this->customsService->syncItems($clearance, $request->input('items', [])); if (! empty($data['shipment_id']) && (int) $data['shipment_id'] !== (int) $oldShipmentId) { $shipment = Shipment::where('business_id', $clearance->business_id)->find($data['shipment_id']); if ($shipment) { $this->customsSyncService->linkMutually($clearance->fresh(), $shipment); } } if ($oldStatus !== $clearance->status) { $this->customsService->updateStatus($clearance, $clearance->status, __('tms::lang.status_changed')); } else { $this->customsService->recalculateTotals($clearance->fresh()); } return redirect() ->action([self::class, 'show'], [$clearance->id]) ->with('status', ['success' => 1, 'msg' => __('tms::lang.customs_updated')]); } public function updateStatus(Request $request, $id) { $this->authorizeTmsAccess('tms.customs.update'); $clearance = $this->findClearance($id); $data = $request->validate([ 'status' => 'required|string|max:50', 'note' => 'nullable|string', ]); $this->customsService->updateStatus($clearance, $data['status'], $data['note'] ?? null); return $this->tmsUtil->jsonOrRedirect($request, true, __('tms::lang.status_updated')); } public function createFromTransaction(Request $request, $transaction_id) { $this->authorizeTmsAccess('tms.customs.create'); $business_id = $this->tmsUtil->getBusinessId(); $transaction = Transaction::where('business_id', $business_id) ->whereIn('type', ['purchase', 'sell']) ->findOrFail($transaction_id); if ($transaction->type === 'purchase') { $transaction->load(['purchase_lines.product.unit', 'purchase_lines.sub_unit']); } else { $transaction->load(['sell_lines.product.unit']); } $clearance = $this->customsService->createFromTransaction($transaction); if ($request->ajax() || $request->wantsJson()) { return response()->json([ 'success' => true, 'msg' => __('tms::lang.customs_created'), 'clearance_id' => $clearance?->id, 'redirect' => $clearance ? action([self::class, 'show'], [$clearance->id]) : null, ]); } return redirect()->action([self::class, 'show'], [$clearance->id]) ->with('status', ['success' => 1, 'msg' => __('tms::lang.customs_created')]); } public function createFromShipment(Request $request, $shipment_id) { $this->authorizeTmsAccess('tms.customs.create'); $shipment = Shipment::where('business_id', $this->tmsUtil->getBusinessId())->findOrFail($shipment_id); $clearance = $this->customsService->createFromShipment($shipment, [ 'clearance_type' => $request->get('clearance_type', 'import'), ]); return redirect() ->action([self::class, 'show'], [$clearance->id]) ->with('status', ['success' => 1, 'msg' => __('tms::lang.customs_created')]); } public function uploadDocument(Request $request, $id) { $this->authorizeTmsAccess('tms.customs.update'); $clearance = $this->findClearance($id); $data = $request->validate([ 'document_type' => 'required|string|max:50', 'title' => 'nullable|string|max:255', 'document' => 'required|file', ]); $fileName = $this->util->uploadFile($request, 'document', 'documents'); if (! $fileName) { return $this->tmsUtil->jsonOrRedirect($request, false, __('tms::lang.document_upload_failed')); } CustomsDocument::create([ 'customs_clearance_id' => $clearance->id, 'document_type' => $data['document_type'], 'title' => $data['title'] ?? null, 'file_name' => $fileName, 'file_path' => 'documents/'.$fileName, 'uploaded_by' => auth()->id(), ]); return $this->tmsUtil->jsonOrRedirect($request, true, __('tms::lang.document_uploaded')); } public function deleteDocument(Request $request, $id, $document_id) { $this->authorizeTmsAccess('tms.customs.update'); $clearance = $this->findClearance($id); $document = CustomsDocument::where('customs_clearance_id', $clearance->id)->findOrFail($document_id); if ($document->file_name && Storage::exists('documents/'.$document->file_name)) { Storage::delete('documents/'.$document->file_name); } $document->delete(); return $this->tmsUtil->jsonOrRedirect($request, true, __('tms::lang.document_deleted')); } public function destroy(Request $request, $id) { $this->authorizeTmsAccess('tms.customs.delete'); $clearance = $this->findClearance($id); foreach ($clearance->documents as $doc) { if ($doc->file_name && Storage::exists('documents/'.$doc->file_name)) { Storage::delete('documents/'.$doc->file_name); } } $clearance->documents()->delete(); $clearance->items()->delete(); $clearance->statusLogs()->delete(); $clearance->delete(); return $this->tmsUtil->jsonOrRedirect( $request, true, __('tms::lang.customs_deleted'), action([self::class, 'index']) ); } protected function validateClearance(Request $request): array { $request->merge([ 'shipment_id' => $request->input('shipment_id') ?: null, 'transaction_id' => $request->input('transaction_id') ?: null, 'contact_id' => $request->input('contact_id') ?: null, 'broker_id' => $request->input('broker_id') ?: null, ]); return $request->validate([ 'clearance_type' => 'required|in:import,export', 'status' => 'required|string|max:50', 'shipment_id' => 'nullable|exists:tms_shipments,id', 'transaction_id' => 'nullable|exists:transactions,id', 'contact_id' => 'nullable|exists:contacts,id', 'broker_id' => 'nullable|exists:tms_customs_brokers,id', 'customs_office' => 'nullable|string|max:255', 'declaration_no' => 'nullable|string|max:100', 'bill_of_lading' => 'nullable|string|max:100', 'country_of_origin' => 'nullable|string|max:100', 'other_charges' => 'nullable|numeric|min:0', 'notes' => 'nullable|string', 'items' => 'nullable|array', 'items.*.description' => 'nullable|string|max:255', 'items.*.hs_code' => 'nullable|string|max:50', 'items.*.quantity' => 'nullable|numeric|min:0', 'items.*.unit' => 'nullable|string|max:50', 'items.*.customs_value' => 'nullable|numeric|min:0', 'items.*.duty_rate' => 'nullable|numeric|min:0', 'items.*.vat_rate' => 'nullable|numeric|min:0', ]); } protected function findClearance($id): CustomsClearance { return CustomsClearance::where('business_id', $this->tmsUtil->getBusinessId())->findOrFail($id); } }