ultimatepos/Modules/Tms/Utils/TmsUtil.php

367 lines
12 KiB
PHP

<?php
namespace Modules\Tms\Utils;
use App\Business;
use App\BusinessLocation;
use App\Contact;
use App\Transaction;
use App\User;
use App\Utils\ModuleUtil;
use App\Utils\Util;
use Modules\Tms\Models\Carrier;
use Modules\Tms\Models\Driver;
use Modules\Tms\Models\Shipment;
use Modules\Tms\Models\Vehicle;
class TmsUtil
{
public function __construct(
protected Util $commonUtil,
protected ModuleUtil $moduleUtil
) {}
public function getBusinessId(): int
{
return (int) request()->session()->get('user.business_id');
}
public function getBusiness(): ?Business
{
$business_id = $this->getBusinessId();
return $business_id ? Business::find($business_id) : null;
}
public function isModuleActive(): bool
{
if (! \Module::has('Tms') || ! \Module::isEnabled('Tms')) {
return false;
}
return (bool) $this->moduleUtil->isModuleInstalled('Tms');
}
public function getTmsSettings(?int $business_id = null): array
{
$business_id = $business_id ?? $this->getBusinessId();
$business = Business::find($business_id);
$defaults = [
'ref_prefix' => 'TMS',
'auto_create_on_sale' => false,
'sync_transaction_shipping' => true,
'default_carrier_id' => null,
'auto_post_freight_journal' => false,
'freight_expense_account_id' => null,
'freight_payment_account_id' => null,
'customs_ref_prefix' => 'CUS',
'auto_post_customs_journal' => false,
'customs_duty_expense_account_id' => null,
'customs_payment_account_id' => null,
'sync_customs_shipment_status' => true,
'auto_link_customs_shipment' => true,
'auto_create_shipment_on_customs_import' => true,
'require_customs_cleared_before_transit' => true,
'exclude_uncleared_from_routes' => true,
'sync_mission_status' => true,
];
if (! $business || empty($business->tms_settings)) {
return $defaults;
}
$stored = json_decode($business->tms_settings, true);
return is_array($stored) ? array_merge($defaults, $stored) : $defaults;
}
public function shipmentStatuses(): array
{
return [
'draft' => __('tms::lang.status_draft'),
'scheduled' => __('tms::lang.status_scheduled'),
'at_customs' => __('tms::lang.status_at_customs'),
'in_transit' => __('tms::lang.status_in_transit'),
'delivered' => __('tms::lang.status_delivered'),
'cancelled' => __('tms::lang.status_cancelled'),
];
}
public function mapCustomsStatusToShipment(string $customsStatus, ?string $currentShipmentStatus = null): ?string
{
$locked = in_array($currentShipmentStatus, ['in_transit', 'delivered'], true);
return match ($customsStatus) {
'draft', 'declared', 'assessment', 'payment_pending' => $locked ? null : 'at_customs',
'cleared' => $locked ? null : 'scheduled',
'released' => 'in_transit',
'cancelled' => 'cancelled',
default => null,
};
}
public function vehicleTypes(): array
{
return [
'truck' => __('tms::lang.vehicle_truck'),
'van' => __('tms::lang.vehicle_van'),
'pickup' => __('tms::lang.vehicle_pickup'),
'trailer' => __('tms::lang.vehicle_trailer'),
'other' => __('tms::lang.vehicle_other'),
];
}
public function vehicleStatuses(): array
{
return [
'available' => __('tms::lang.vehicle_available'),
'in_use' => __('tms::lang.vehicle_in_use'),
'maintenance' => __('tms::lang.vehicle_maintenance'),
'inactive' => __('tms::lang.vehicle_inactive'),
];
}
public function shipmentTypes(): array
{
return [
'manual' => __('tms::lang.type_manual'),
'sell' => __('tms::lang.type_sell'),
'stock_transfer' => __('tms::lang.type_stock_transfer'),
'import' => __('tms::lang.type_import'),
'export' => __('tms::lang.type_export'),
'field_service' => __('tms::lang.type_field_service'),
'project_delivery' => __('tms::lang.type_project_delivery'),
];
}
public function carrierTypes(): array
{
return [
'internal' => __('tms::lang.carrier_internal'),
'external' => __('tms::lang.carrier_external'),
];
}
public function generateRefNo(int $business_id): string
{
$settings = $this->getTmsSettings($business_id);
$prefix = $settings['ref_prefix'] ?? 'TMS';
$count = Shipment::where('business_id', $business_id)->count() + 1;
return $prefix.'-'.str_pad((string) $count, 5, '0', STR_PAD_LEFT);
}
public function generateCustomsRefNo(int $business_id): string
{
$settings = $this->getTmsSettings($business_id);
$prefix = $settings['customs_ref_prefix'] ?? 'CUS';
$count = \Modules\Tms\Models\CustomsClearance::where('business_id', $business_id)->count() + 1;
return $prefix.'-'.str_pad((string) $count, 5, '0', STR_PAD_LEFT);
}
public function customsStatuses(): array
{
return [
'draft' => __('tms::lang.customs_status_draft'),
'declared' => __('tms::lang.customs_status_declared'),
'assessment' => __('tms::lang.customs_status_assessment'),
'payment_pending' => __('tms::lang.customs_status_payment_pending'),
'cleared' => __('tms::lang.customs_status_cleared'),
'released' => __('tms::lang.customs_status_released'),
'cancelled' => __('tms::lang.customs_status_cancelled'),
];
}
public function customsTypes(): array
{
return [
'import' => __('tms::lang.customs_type_import'),
'export' => __('tms::lang.customs_type_export'),
];
}
public function customsDocumentTypes(): array
{
return [
'commercial_invoice' => __('tms::lang.doc_commercial_invoice'),
'packing_list' => __('tms::lang.doc_packing_list'),
'bill_of_lading' => __('tms::lang.doc_bill_of_lading'),
'certificate_of_origin' => __('tms::lang.doc_certificate_of_origin'),
'customs_declaration' => __('tms::lang.doc_customs_declaration'),
'other' => __('tms::lang.doc_other'),
];
}
public function getBrokersDropdown(int $business_id): array
{
return \Modules\Tms\Models\CustomsBroker::where('business_id', $business_id)
->where('is_active', true)
->orderBy('name')
->pluck('name', 'id')
->toArray();
}
public function getShipmentsDropdown(int $business_id): array
{
return Shipment::where('business_id', $business_id)
->orderByDesc('id')
->limit(200)
->get()
->mapWithKeys(fn ($s) => [$s->id => $s->ref_no])
->toArray();
}
public function getLinkableTransactionsDropdown(int $business_id, ?int $include_id = null): array
{
$typeLabels = [
'sell' => __('tms::lang.type_sell'),
'sell_transfer' => __('tms::lang.type_stock_transfer'),
'purchase' => __('tms::lang.type_import'),
];
$transactions = Transaction::where('business_id', $business_id)
->whereIn('type', ['sell', 'sell_transfer', 'purchase'])
->where('status', '!=', 'draft')
->with('contact:id,name,supplier_business_name')
->orderByDesc('transaction_date')
->limit(500)
->get(['id', 'ref_no', 'invoice_no', 'type', 'contact_id', 'transaction_date']);
$items = [];
foreach ($transactions as $txn) {
$items[$txn->id] = $this->formatTransactionDropdownLabel($txn, $typeLabels);
}
if ($include_id && ! isset($items[$include_id])) {
$txn = Transaction::where('business_id', $business_id)
->whereIn('type', ['sell', 'sell_transfer', 'purchase'])
->with('contact:id,name,supplier_business_name')
->find($include_id);
if ($txn) {
$items[$txn->id] = $this->formatTransactionDropdownLabel($txn, $typeLabels);
}
}
return $items;
}
protected function formatTransactionDropdownLabel(Transaction $txn, array $typeLabels): string
{
$number = $txn->invoice_no ?: $txn->ref_no;
$label = $number.' — '.($typeLabels[$txn->type] ?? $txn->type);
$contactName = $txn->contact?->supplier_business_name ?: $txn->contact?->name;
if ($contactName) {
$label .= ' ('.$contactName.')';
}
if ($txn->transaction_date) {
$label .= ' ['.\Carbon::parse($txn->transaction_date)->format('Y-m-d').']';
}
return $label;
}
public function getUsersDropdown(int $business_id): array
{
$users = User::forDropdown($business_id, false, false, true);
return is_array($users) ? $users : $users->toArray();
}
public function getLocationsDropdown(int $business_id): array
{
$locations = BusinessLocation::forDropdown($business_id, true);
return is_array($locations) ? $locations : $locations->toArray();
}
public function getCustomersDropdown(int $business_id): array
{
return Contact::customersDropdown($business_id, false)->toArray();
}
public function getSuppliersDropdown(int $business_id): array
{
return Contact::suppliersDropdown($business_id, false)->toArray();
}
public function getVehiclesDropdown(int $business_id): array
{
return Vehicle::where('business_id', $business_id)
->where('is_active', true)
->orderBy('plate_number')
->get()
->mapWithKeys(fn ($v) => [$v->id => $v->plate_number.' — '.($this->vehicleTypes()[$v->vehicle_type] ?? $v->vehicle_type)])
->toArray();
}
public function getDriversDropdown(int $business_id): array
{
return Driver::where('business_id', $business_id)
->where('is_active', true)
->orderBy('name')
->pluck('name', 'id')
->toArray();
}
public function getCarriersDropdown(int $business_id): array
{
return Carrier::where('business_id', $business_id)
->where('is_active', true)
->orderBy('name')
->pluck('name', 'id')
->toArray();
}
public function mapStatusToTransactionShipping(string $tms_status): ?string
{
return match ($tms_status) {
'scheduled' => 'ordered',
'at_customs' => 'ordered',
'in_transit' => 'shipped',
'delivered' => 'delivered',
'cancelled' => 'cancelled',
default => null,
};
}
public function jsonOrRedirect(\Illuminate\Http\Request $request, bool $success, string $msg, ?string $redirect = null)
{
$output = ['success' => $success, 'msg' => $msg];
if ($request->ajax() || $request->wantsJson()) {
return response()->json($output);
}
$flash = ['success' => $success ? 1 : 0, 'msg' => $msg];
if ($success) {
return redirect()->to($redirect ?? url()->previous())->with('status', $flash);
}
return redirect()->back()->with('status', $flash);
}
public function isAccountingEnabled($business_id): bool
{
if (! class_exists(\Modules\Accounting\Entities\AccountingAccount::class)) {
return false;
}
return $this->moduleUtil->isModuleInstalled('Accounting')
&& $this->moduleUtil->hasThePermissionInSubscription($business_id, 'accounting_module');
}
public function getAccountingAccountsDropdown($business_id): array
{
if (! $this->isAccountingEnabled($business_id)) {
return [];
}
return \Modules\Accounting\Entities\AccountingAccount::forDropdown($business_id)->toArray();
}
}