ultimatepos/Modules/Tms/Http/Controllers/CustomsClearanceController.php

376 lines
15 KiB
PHP

<?php
namespace Modules\Tms\Http\Controllers;
use App\Transaction;
use App\Utils\Util;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Storage;
use Modules\Tms\Http\Controllers\Concerns\AuthorizesTms;
use Modules\Tms\Models\CustomsClearance;
use Modules\Tms\Models\CustomsDocument;
use Modules\Tms\Models\Shipment;
use Modules\Tms\Services\TmsCustomsService;
use Modules\Tms\Services\TmsCustomsShipmentSyncService;
use Modules\Tms\Utils\TmsUtil;
use Yajra\DataTables\Facades\DataTables;
class CustomsClearanceController extends Controller
{
use AuthorizesTms;
public function __construct(
protected TmsUtil $tmsUtil,
protected TmsCustomsService $customsService,
protected TmsCustomsShipmentSyncService $customsSyncService,
protected Util $util
) {}
public function index(Request $request)
{
$this->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) => '<span class="label label-info">'.($statuses[$row->status] ?? $row->status).'</span>')
->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 = '<div class="btn-group">';
$html .= '<a href="'.action([self::class, 'show'], [$row->id]).'" class="btn btn-xs btn-info"><i class="fa fa-eye"></i></a>';
if (auth()->user()->can('tms.customs.update')) {
$html .= ' <a href="'.action([self::class, 'edit'], [$row->id]).'" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-edit"></i></a>';
}
if (auth()->user()->can('tms.customs.delete')) {
$html .= ' <button type="button" data-href="'.action([self::class, 'destroy'], [$row->id]).'" class="btn btn-xs btn-danger delete_tms_customs"><i class="glyphicon glyphicon-trash"></i></button>';
}
$html .= '</div>';
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);
}
}