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(); } }