220 lines
7.6 KiB
PHP
220 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace Modules\Tms\Services;
|
|
|
|
use App\Transaction;
|
|
use Modules\Tms\Models\CustomsClearance;
|
|
use Modules\Tms\Models\CustomsItem;
|
|
use Modules\Tms\Models\CustomsStatusLog;
|
|
use Modules\Tms\Models\Shipment;
|
|
use Modules\Tms\Utils\TmsUtil;
|
|
|
|
class TmsCustomsService
|
|
{
|
|
public function __construct(
|
|
protected TmsUtil $tmsUtil,
|
|
protected TmsAccountingService $accountingService,
|
|
protected TmsCustomsShipmentSyncService $customsSyncService
|
|
) {}
|
|
|
|
public function createFromTransaction(Transaction $transaction, array $extra = []): ?CustomsClearance
|
|
{
|
|
$typeMap = [
|
|
'purchase' => 'import',
|
|
'sell' => 'export',
|
|
];
|
|
|
|
if (! isset($typeMap[$transaction->type])) {
|
|
return null;
|
|
}
|
|
|
|
$business_id = (int) $transaction->business_id;
|
|
|
|
$existing = CustomsClearance::where('business_id', $business_id)
|
|
->where('transaction_id', $transaction->id)
|
|
->first();
|
|
|
|
if ($existing) {
|
|
return $existing;
|
|
}
|
|
|
|
$clearance = CustomsClearance::create([
|
|
'business_id' => $business_id,
|
|
'ref_no' => $this->tmsUtil->generateCustomsRefNo($business_id),
|
|
'clearance_type' => $typeMap[$transaction->type],
|
|
'transaction_id' => $transaction->id,
|
|
'contact_id' => $transaction->contact_id,
|
|
'customs_value' => (float) ($transaction->final_total ?? 0),
|
|
'status' => 'draft',
|
|
'created_by' => auth()->id(),
|
|
...$extra,
|
|
]);
|
|
|
|
$this->importItemsFromTransaction($clearance, $transaction);
|
|
$this->recalculateTotals($clearance);
|
|
$this->logStatus($clearance, 'draft', __('tms::lang.auto_created_from_transaction'));
|
|
|
|
return $this->customsSyncService->linkByTransaction($clearance->fresh(['items']));
|
|
}
|
|
|
|
public function createFromShipment(Shipment $shipment, array $extra = []): CustomsClearance
|
|
{
|
|
$business_id = (int) $shipment->business_id;
|
|
|
|
$existing = CustomsClearance::where('business_id', $business_id)
|
|
->where('shipment_id', $shipment->id)
|
|
->first();
|
|
|
|
if ($existing) {
|
|
return $existing;
|
|
}
|
|
|
|
$clearance = CustomsClearance::create([
|
|
'business_id' => $business_id,
|
|
'ref_no' => $this->tmsUtil->generateCustomsRefNo($business_id),
|
|
'clearance_type' => $extra['clearance_type'] ?? 'import',
|
|
'shipment_id' => $shipment->id,
|
|
'transaction_id' => $shipment->transaction_id,
|
|
'contact_id' => $shipment->contact_id,
|
|
'customs_value' => (float) ($shipment->freight_cost ?? 0),
|
|
'status' => 'draft',
|
|
'created_by' => auth()->id(),
|
|
...$extra,
|
|
]);
|
|
|
|
if ($shipment->transaction_id) {
|
|
$transaction = Transaction::find($shipment->transaction_id);
|
|
if ($transaction) {
|
|
$this->importItemsFromTransaction($clearance, $transaction);
|
|
}
|
|
}
|
|
|
|
$this->recalculateTotals($clearance);
|
|
$this->logStatus($clearance, 'draft', __('tms::lang.created_from_shipment', ['ref' => $shipment->ref_no]));
|
|
|
|
$this->customsSyncService->linkMutually($clearance, $shipment);
|
|
$this->customsSyncService->syncShipmentFromCustoms($clearance->fresh());
|
|
|
|
return $clearance->fresh(['items', 'shipment']);
|
|
}
|
|
|
|
public function syncItems(CustomsClearance $clearance, array $items): void
|
|
{
|
|
$clearance->items()->delete();
|
|
|
|
foreach ($items as $row) {
|
|
if (empty($row['description'])) {
|
|
continue;
|
|
}
|
|
|
|
$qty = (float) ($row['quantity'] ?? 1);
|
|
$value = (float) ($row['customs_value'] ?? 0);
|
|
$dutyRate = (float) ($row['duty_rate'] ?? 0);
|
|
$vatRate = (float) ($row['vat_rate'] ?? 0);
|
|
$dutyAmount = round($value * $dutyRate / 100, 4);
|
|
$vatAmount = round(($value + $dutyAmount) * $vatRate / 100, 4);
|
|
|
|
CustomsItem::create([
|
|
'customs_clearance_id' => $clearance->id,
|
|
'description' => $row['description'],
|
|
'hs_code' => $row['hs_code'] ?? null,
|
|
'quantity' => $qty,
|
|
'unit' => $row['unit'] ?? null,
|
|
'customs_value' => $value,
|
|
'duty_rate' => $dutyRate,
|
|
'duty_amount' => $dutyAmount,
|
|
'vat_rate' => $vatRate,
|
|
'vat_amount' => $vatAmount,
|
|
]);
|
|
}
|
|
|
|
$this->recalculateTotals($clearance->fresh());
|
|
}
|
|
|
|
public function recalculateTotals(CustomsClearance $clearance): void
|
|
{
|
|
$clearance->load('items');
|
|
|
|
$customsValue = $clearance->items->sum('customs_value');
|
|
$dutyAmount = $clearance->items->sum('duty_amount');
|
|
$vatAmount = $clearance->items->sum('vat_amount');
|
|
$other = (float) $clearance->other_charges;
|
|
|
|
$clearance->update([
|
|
'customs_value' => $customsValue > 0 ? $customsValue : $clearance->customs_value,
|
|
'duty_amount' => $dutyAmount,
|
|
'vat_amount' => $vatAmount,
|
|
'total_duties' => $dutyAmount + $vatAmount + $other,
|
|
]);
|
|
}
|
|
|
|
public function updateStatus(CustomsClearance $clearance, string $status, ?string $note = null): CustomsClearance
|
|
{
|
|
$updates = ['status' => $status];
|
|
|
|
if ($status === 'declared' && ! $clearance->declared_at) {
|
|
$updates['declared_at'] = now();
|
|
}
|
|
|
|
if ($status === 'cleared' && ! $clearance->cleared_at) {
|
|
$updates['cleared_at'] = now();
|
|
}
|
|
|
|
if ($status === 'released' && ! $clearance->released_at) {
|
|
$updates['released_at'] = now();
|
|
}
|
|
|
|
$clearance->update($updates);
|
|
$this->logStatus($clearance, $status, $note);
|
|
|
|
if (in_array($status, ['cleared', 'released'], true)) {
|
|
$this->accountingService->postCustomsDutiesOnClearance(
|
|
$clearance->fresh(),
|
|
(int) (auth()->id() ?? $clearance->created_by ?? 0)
|
|
);
|
|
}
|
|
|
|
$this->customsSyncService->syncShipmentFromCustoms($clearance->fresh());
|
|
|
|
return $clearance->fresh();
|
|
}
|
|
|
|
public function logStatus(CustomsClearance $clearance, string $status, ?string $note = null): CustomsStatusLog
|
|
{
|
|
return CustomsStatusLog::create([
|
|
'customs_clearance_id' => $clearance->id,
|
|
'status' => $status,
|
|
'note' => $note,
|
|
'created_by' => auth()->id(),
|
|
'logged_at' => now(),
|
|
]);
|
|
}
|
|
|
|
protected function importItemsFromTransaction(CustomsClearance $clearance, Transaction $transaction): void
|
|
{
|
|
$lines = $transaction->type === 'purchase'
|
|
? $transaction->purchase_lines
|
|
: ($transaction->sell_lines ?? collect());
|
|
|
|
if (! $lines || $lines->isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
$items = [];
|
|
foreach ($lines as $line) {
|
|
$product = $line->product ?? null;
|
|
$items[] = [
|
|
'description' => $product?->name ?? ($line->product?->name ?? __('tms::lang.item')),
|
|
'hs_code' => $product?->sku ?? null,
|
|
'quantity' => (float) ($line->quantity ?? 1),
|
|
'unit' => $line->sub_unit?->short_name ?? $product?->unit?->short_name ?? null,
|
|
'customs_value' => (float) ($line->purchase_price_inc_tax ?? $line->unit_price_inc_tax ?? $line->unit_price ?? 0) * (float) ($line->quantity ?? 1),
|
|
'duty_rate' => 0,
|
|
'vat_rate' => 0,
|
|
];
|
|
}
|
|
|
|
$this->syncItems($clearance, $items);
|
|
}
|
|
}
|