376 lines
15 KiB
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);
|
|
}
|
|
}
|