ultimatepos/Modules/Tms/Services/TmsCustomsService.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);
}
}