diff --git a/app/Http/Controllers/Admin/AdminInvoiceController.php b/app/Http/Controllers/Admin/AdminInvoiceController.php index 0209ff0..19d313e 100644 --- a/app/Http/Controllers/Admin/AdminInvoiceController.php +++ b/app/Http/Controllers/Admin/AdminInvoiceController.php @@ -26,15 +26,13 @@ class AdminInvoiceController extends Controller // ------------------------------------------------------------- public function popup($id) { - $invoice = Invoice::with(['items', 'order'])->findOrFail($id); + $invoice = Invoice::with(['items', 'order', 'installments'])->findOrFail($id); - // Find actual Shipment record - $shipment = \App\Models\Shipment::whereHas('items', function ($q) use ($invoice) { - $q->where('order_id', $invoice->order_id); - }) - ->first(); + $shipment = \App\Models\Shipment::whereHas('items', function ($q) use ($invoice) { + $q->where('order_id', $invoice->order_id); + })->first(); - return view('admin.popup_invoice', compact('invoice', 'shipment')); + return view('admin.popup_invoice', compact('invoice', 'shipment')); } // ------------------------------------------------------------- @@ -145,6 +143,22 @@ class AdminInvoiceController extends Controller $invoice->update(['pdf_path' => 'invoices/' . $fileName]); } + public function downloadInvoice($id) +{ + $invoice = Invoice::findOrFail($id); + + // Generate PDF if missing + if ( + !$invoice->pdf_path || + !file_exists(public_path($invoice->pdf_path)) + ) { + $this->generateInvoicePDF($invoice); + $invoice->refresh(); + } + + return response()->download(public_path($invoice->pdf_path)); +} + // ------------------------------------------------------------- // INSTALLMENTS (ADD/DELETE) // ------------------------------------------------------------- diff --git a/app/Http/Controllers/Admin/AdminOrderController.php b/app/Http/Controllers/Admin/AdminOrderController.php index 486040f..f116b96 100644 --- a/app/Http/Controllers/Admin/AdminOrderController.php +++ b/app/Http/Controllers/Admin/AdminOrderController.php @@ -13,6 +13,11 @@ use App\Models\User; use PDF; use Maatwebsite\Excel\Facades\Excel; use App\Exports\OrdersExport; +use App\Imports\OrderItemsPreviewImport; + + +use Illuminate\Validation\ValidationException; + class AdminOrderController extends Controller { @@ -36,44 +41,6 @@ class AdminOrderController extends Controller return view('admin.orders_create', compact('markList')); } - public function store(Request $request) - { - $data = $request->validate([ - 'mark_no' => 'required|string', - 'origin' => 'nullable|string', - 'destination' => 'nullable|string', - 'ctn' => 'nullable|numeric', - 'qty' => 'nullable|numeric', - 'ttl_qty' => 'nullable|numeric', - 'ttl_amount' => 'nullable|numeric', - 'cbm' => 'nullable|numeric', - 'ttl_cbm' => 'nullable|numeric', - 'kg' => 'nullable|numeric', - 'ttl_kg' => 'nullable|numeric', - ]); - - $order = Order::create([ - 'order_id' => $this->generateOrderId(), - 'mark_no' => $data['mark_no'], - 'origin' => $data['origin'] ?? null, - 'destination'=> $data['destination'] ?? null, - 'ctn' => $data['ctn'] ?? 0, - 'qty' => $data['qty'] ?? 0, - 'ttl_qty' => $data['ttl_qty'] ?? 0, - 'ttl_amount' => $data['ttl_amount'] ?? 0, - 'cbm' => $data['cbm'] ?? 0, - 'ttl_cbm' => $data['ttl_cbm'] ?? 0, - 'kg' => $data['kg'] ?? 0, - 'ttl_kg' => $data['ttl_kg'] ?? 0, - 'status' => 'pending', - ]); - - $this->createInvoice($order); - - return redirect()->route('admin.orders.show', $order->id) - ->with('success', 'Order created successfully.'); - } - /* --------------------------- * SHOW / POPUP * ---------------------------*/ @@ -580,7 +547,7 @@ class AdminOrderController extends Controller 'ttl_cbm' => $total_ttl_cbm, 'kg' => $total_kg, 'ttl_kg' => $total_ttl_kg, - 'status' => 'pending', + 'status' => 'order_placed', ]); // 6) order items @@ -723,4 +690,36 @@ class AdminOrderController extends Controller ]); } } + + + +public function uploadExcelPreview(Request $request) +{ + try { + $request->validate([ + 'excel' => 'required|file|mimes:xlsx,xls' + ]); + + $import = new OrderItemsPreviewImport(); + Excel::import($import, $request->file('excel')); + + return response()->json([ + 'success' => true, + 'items' => $import->rows + ]); + } catch (ValidationException $e) { + return response()->json([ + 'success' => false, + 'message' => 'Invalid Excel file format' + ], 422); + } catch (\Throwable $e) { + \Log::error($e); + return response()->json([ + 'success' => false, + 'message' => 'Server error' + ], 500); + } +} + + } diff --git a/app/Http/Controllers/Admin/ShipmentController.php b/app/Http/Controllers/Admin/ShipmentController.php index 6feffef..781507e 100644 --- a/app/Http/Controllers/Admin/ShipmentController.php +++ b/app/Http/Controllers/Admin/ShipmentController.php @@ -20,7 +20,11 @@ class ShipmentController extends Controller $usedOrderIds = ShipmentItem::pluck('order_id')->toArray(); // 2) Load available orders (not used in any shipment) - $availableOrders = Order::whereNotIn('id', $usedOrderIds)->get(); + $availableOrders = Order::whereNotIn('id', $usedOrderIds) + ->where('status', '!=', 'order_placed') + ->get(); + + // 3) Load all shipments for listing $shipments = Shipment::latest()->get(); @@ -65,6 +69,16 @@ class ShipmentController extends Controller // CALCULATE TOTALS // ----------------------------- $orders = Order::whereIn('id', $request->order_ids)->get(); + foreach ($orders as $order) { + if ($order->status === 'order_placed') { + return back()->with( + 'error', + "Order {$order->order_id} is not ready for shipment" + ); + } + } + + $total_ctn = $orders->sum('ctn'); $total_qty = $orders->sum('qty'); @@ -82,7 +96,7 @@ class ShipmentController extends Controller 'shipment_id' => $newShipmentId, 'origin' => $request->origin, 'destination' => $request->destination, - 'status' => Shipment::STATUS_PENDING, + 'status' => Shipment::STATUS_SHIPMENT_READY, 'shipment_date' => $request->shipment_date, 'total_ctn' => $total_ctn, @@ -135,29 +149,35 @@ class ShipmentController extends Controller * Update Shipment status from action button */ public function updateStatus(Request $request) - { - $request->validate([ - 'shipment_id' => 'required|exists:shipments,id', - 'status' => 'required|string' - ]); +{ + $request->validate([ + 'shipment_id' => 'required|exists:shipments,id', + 'status' => 'required|string' + ]); - // 1) Update shipment status - $shipment = Shipment::findOrFail($request->shipment_id); - $shipment->status = $request->status; - $shipment->save(); + $shipment = Shipment::findOrFail($request->shipment_id); + $shipment->status = $request->status; + $shipment->save(); - // 2) Update ALL related orders' status - foreach ($shipment->orders as $order) { - $order->status = $shipment->status; // status is string: pending, in_transit, dispatched, delivered - $order->save(); + // ✅ Sync shipment status to orders ONLY after shipment exists + foreach ($shipment->orders as $order) { + + // Prevent rollback or overwrite + if ($order->status === 'delivered') { + continue; } - return redirect()->back()->with( - 'success', - "Shipment status updated to {$shipment->statusLabel()} and related orders updated." - ); + $order->status = $shipment->status; + $order->save(); } + return redirect()->back()->with( + 'success', + "Shipment status updated to {$shipment->statusLabel()}." + ); +} + + /** * Update shipment details */ @@ -266,11 +286,23 @@ public function addOrders(Request $request, Shipment $shipment) 'order_ids' => 'required|array|min:1', ]); - // फक्त न वापरलेले orders घ्या $orders = Order::whereIn('id', $request->order_ids)->get(); foreach ($orders as $order) { - // pivot मध्ये insert + + if ($order->status === 'order_placed') { + return back()->with( + 'error', + "Order {$order->order_id} is not ready for shipment" + ); + } + + + // Prevent duplicates + if (ShipmentItem::where('order_id', $order->id)->exists()) { + continue; + } + ShipmentItem::create([ 'shipment_id' => $shipment->id, 'order_id' => $order->id, @@ -282,23 +314,25 @@ public function addOrders(Request $request, Shipment $shipment) ]); } - // totals + // Recalculate totals $orderIds = ShipmentItem::where('shipment_id', $shipment->id)->pluck('order_id'); $allOrders = Order::whereIn('id', $orderIds)->get(); - $shipment->total_ctn = $allOrders->sum('ctn'); - $shipment->total_qty = $allOrders->sum('qty'); - $shipment->total_ttl_qty = $allOrders->sum('ttl_qty'); - $shipment->total_cbm = $allOrders->sum('cbm'); - $shipment->total_ttl_cbm = $allOrders->sum('ttl_cbm'); - $shipment->total_kg = $allOrders->sum('kg'); - $shipment->total_ttl_kg = $allOrders->sum('ttl_kg'); - $shipment->total_amount = $allOrders->sum('ttl_amount'); - $shipment->save(); + $shipment->update([ + 'total_ctn' => $allOrders->sum('ctn'), + 'total_qty' => $allOrders->sum('qty'), + 'total_ttl_qty' => $allOrders->sum('ttl_qty'), + 'total_cbm' => $allOrders->sum('cbm'), + 'total_ttl_cbm' => $allOrders->sum('ttl_cbm'), + 'total_kg' => $allOrders->sum('kg'), + 'total_ttl_kg' => $allOrders->sum('ttl_kg'), + 'total_amount' => $allOrders->sum('ttl_amount'), + ]); return redirect() ->route('admin.shipments.dummy', $shipment->id) ->with('success', 'Orders added to shipment successfully.'); } + } \ No newline at end of file diff --git a/app/Http/Controllers/user/UserOrderController.php b/app/Http/Controllers/user/UserOrderController.php index d18f7ae..da77e68 100644 --- a/app/Http/Controllers/user/UserOrderController.php +++ b/app/Http/Controllers/user/UserOrderController.php @@ -289,6 +289,44 @@ public function invoiceDetails($invoice_id) ]); } +public function confirmOrder($order_id) +{ + $user = JWTAuth::parseToken()->authenticate(); + + if (! $user) { + return response()->json([ + 'success' => false, + 'message' => 'Unauthorized' + ], 401); + } + + $order = $user->orders() + ->where('order_id', $order_id) + ->first(); + + if (! $order) { + return response()->json([ + 'success' => false, + 'message' => 'Order not found' + ], 404); + } + + // 🚫 Only allow confirm from order_placed + if ($order->status !== 'order_placed') { + return response()->json([ + 'success' => false, + 'message' => 'Order cannot be confirmed' + ], 422); + } + + $order->status = 'order_confirmed'; + $order->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Order confirmed successfully' + ]); +} diff --git a/app/Imports/OrderItemsPreviewImport.php b/app/Imports/OrderItemsPreviewImport.php new file mode 100644 index 0000000..0865b6f --- /dev/null +++ b/app/Imports/OrderItemsPreviewImport.php @@ -0,0 +1,26 @@ +first()->map(fn ($h) => strtolower(trim($h)))->toArray(); + + foreach ($collection->skip(1) as $row) { + $item = []; + foreach ($header as $i => $key) { + $item[$key] = $row[$i] ?? null; + } + + if (!empty($item['description'])) { + $this->rows[] = $item; + } + } + } +} diff --git a/app/Models/Order.php b/app/Models/Order.php index 029cf35..9362328 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -64,5 +64,25 @@ class Order extends Model } + const STATUS_LABELS = [ + 'order_placed' => 'Order Placed', + 'order_confirmed' => 'Order Confirmed', + 'supplier_warehouse' => 'Supplier Warehouse', + 'consolidate_warehouse'=> 'Consolidate Warehouse', + 'export_custom' => 'Export Custom', + 'international_transit'=> 'International Transit', + 'arrived_india' => 'Arrived at India', + 'import_custom' => 'Import Custom', + 'warehouse' => 'Warehouse', + 'domestic_distribution'=> 'Domestic Distribution', + 'out_for_delivery' => 'Out for Delivery', + 'delivered' => 'Delivered', + ]; + + public function getStatusLabelAttribute() + { + return self::STATUS_LABELS[$this->status] + ?? ucfirst(str_replace('_', ' ', $this->status)); + } } diff --git a/app/Models/Shipment.php b/app/Models/Shipment.php index 95bb5d5..3c5cacd 100644 --- a/app/Models/Shipment.php +++ b/app/Models/Shipment.php @@ -45,25 +45,6 @@ class Shipment extends Model return $this->belongsToMany(Order::class, 'shipment_items', 'shipment_id', 'order_id'); } - // --------------------------- - // STATUS CONSTANTS - // --------------------------- - - const STATUS_PENDING = 'pending'; - const STATUS_IN_TRANSIT = 'in_transit'; - const STATUS_DISPATCHED = 'dispatched'; - const STATUS_DELIVERED = 'delivered'; - - public static function statusOptions() - { - return [ - self::STATUS_PENDING => 'Pending', - self::STATUS_IN_TRANSIT => 'In Transit', - self::STATUS_DISPATCHED => 'Dispatched', - self::STATUS_DELIVERED => 'Delivered', - ]; - } - // --------------------------- // HELPERS // --------------------------- @@ -73,8 +54,38 @@ class Shipment extends Model return $this->items()->count(); } + // --------------------------- + // STATUS CONSTANTS (LOGISTICS FLOW) + // --------------------------- + const STATUS_SHIPMENT_READY = 'shipment_ready'; + const STATUS_EXPORT_CUSTOM = 'export_custom'; + const STATUS_INTERNATIONAL_TRANSIT= 'international_transit'; + const STATUS_ARRIVED_INDIA = 'arrived_india'; + const STATUS_IMPORT_CUSTOM = 'import_custom'; + const STATUS_WAREHOUSE = 'warehouse'; + const STATUS_DOMESTIC_DISTRIBUTION= 'domestic_distribution'; + const STATUS_OUT_FOR_DELIVERY = 'out_for_delivery'; + const STATUS_DELIVERED = 'delivered'; + + public static function statusOptions() + { + return [ + self::STATUS_SHIPMENT_READY => 'Shipment Ready', + self::STATUS_EXPORT_CUSTOM => 'Export Custom', + self::STATUS_INTERNATIONAL_TRANSIT => 'International Transit', + self::STATUS_ARRIVED_INDIA => 'Arrived at India', + self::STATUS_IMPORT_CUSTOM => 'Import Custom', + self::STATUS_WAREHOUSE => 'Warehouse', + self::STATUS_DOMESTIC_DISTRIBUTION => 'Domestic Distribution', + self::STATUS_OUT_FOR_DELIVERY => 'Out for Delivery', + self::STATUS_DELIVERED => 'Delivered', + ]; + } + public function statusLabel() { - return self::statusOptions()[$this->status] ?? ucfirst($this->status); + return self::statusOptions()[$this->status] + ?? ucfirst(str_replace('_', ' ', $this->status)); } + } diff --git a/public/invoices/invoice-INV-2025-000029.pdf b/public/invoices/invoice-INV-2025-000029.pdf index 212a927..80c41f2 100644 Binary files a/public/invoices/invoice-INV-2025-000029.pdf and b/public/invoices/invoice-INV-2025-000029.pdf differ diff --git a/public/invoices/invoice-INV-2025-000031.pdf b/public/invoices/invoice-INV-2025-000031.pdf new file mode 100644 index 0000000..87950b3 Binary files /dev/null and b/public/invoices/invoice-INV-2025-000031.pdf differ diff --git a/public/invoices/invoice-INV-2025-000032.pdf b/public/invoices/invoice-INV-2025-000032.pdf index 7ffbb92..c681ac0 100644 Binary files a/public/invoices/invoice-INV-2025-000032.pdf and b/public/invoices/invoice-INV-2025-000032.pdf differ diff --git a/public/invoices/invoice-INV-2025-000044.pdf b/public/invoices/invoice-INV-2025-000044.pdf new file mode 100644 index 0000000..63c4fa5 Binary files /dev/null and b/public/invoices/invoice-INV-2025-000044.pdf differ diff --git a/public/invoices/invoice-INV-2025-000046.pdf b/public/invoices/invoice-INV-2025-000046.pdf new file mode 100644 index 0000000..39c5a85 Binary files /dev/null and b/public/invoices/invoice-INV-2025-000046.pdf differ diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 3d413e3..02f5a9e 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -1284,6 +1284,7 @@ body, .container-fluid {