2025-11-14 13:55:01 +05:30
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
|
|
2025-12-03 15:36:04 +05:30
|
|
|
use Illuminate\Support\Facades\DB;
|
2025-11-14 13:55:01 +05:30
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use App\Models\Shipment;
|
|
|
|
|
use App\Models\ShipmentItem;
|
|
|
|
|
use App\Models\Order;
|
|
|
|
|
use Carbon\Carbon;
|
|
|
|
|
|
|
|
|
|
class ShipmentController extends Controller
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Show shipment page (Create Shipment + Shipment List)
|
|
|
|
|
*/
|
2025-12-01 10:38:52 +05:30
|
|
|
public function index()
|
2025-11-14 13:55:01 +05:30
|
|
|
{
|
|
|
|
|
// 1) Get all used order IDs
|
|
|
|
|
$usedOrderIds = ShipmentItem::pluck('order_id')->toArray();
|
|
|
|
|
|
|
|
|
|
// 2) Load available orders (not used in any shipment)
|
|
|
|
|
$availableOrders = Order::whereNotIn('id', $usedOrderIds)->get();
|
|
|
|
|
|
|
|
|
|
// 3) Load all shipments for listing
|
|
|
|
|
$shipments = Shipment::latest()->get();
|
|
|
|
|
|
|
|
|
|
// Return your file: resources/views/admin/shipment.blade.php
|
|
|
|
|
return view('admin.shipments', compact('availableOrders', 'shipments'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Store new shipment
|
|
|
|
|
*/
|
|
|
|
|
public function store(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$request->validate([
|
|
|
|
|
'origin' => 'required|string',
|
|
|
|
|
'destination' => 'required|string',
|
|
|
|
|
'shipment_date' => 'required|date',
|
|
|
|
|
'order_ids' => 'required|array|min:1',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// -----------------------------
|
|
|
|
|
// PREVENT DUPLICATE ORDERS
|
|
|
|
|
// -----------------------------
|
|
|
|
|
foreach ($request->order_ids as $id) {
|
|
|
|
|
if (ShipmentItem::where('order_id', $id)->exists()) {
|
|
|
|
|
return back()->with('error', "Order ID $id is already assigned to a shipment.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -----------------------------
|
|
|
|
|
// GENERATE UNIQUE SHIPMENT ID
|
|
|
|
|
// -----------------------------
|
|
|
|
|
$year = date('y');
|
|
|
|
|
$prefix = "SHIP-$year-";
|
|
|
|
|
|
|
|
|
|
$lastShipment = Shipment::latest('id')->first();
|
|
|
|
|
$nextNumber = $lastShipment ? intval(substr($lastShipment->shipment_id, -8)) + 1 : 1;
|
|
|
|
|
|
|
|
|
|
$newShipmentId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
|
|
|
|
|
|
|
|
|
|
// -----------------------------
|
|
|
|
|
// CALCULATE TOTALS
|
|
|
|
|
// -----------------------------
|
|
|
|
|
$orders = Order::whereIn('id', $request->order_ids)->get();
|
|
|
|
|
|
|
|
|
|
$total_ctn = $orders->sum('ctn');
|
|
|
|
|
$total_qty = $orders->sum('qty');
|
|
|
|
|
$total_ttl_qty = $orders->sum('ttl_qty');
|
|
|
|
|
$total_amount = $orders->sum('ttl_amount');
|
|
|
|
|
$total_cbm = $orders->sum('cbm');
|
|
|
|
|
$total_ttl_cbm = $orders->sum('ttl_cbm');
|
|
|
|
|
$total_kg = $orders->sum('kg');
|
|
|
|
|
$total_ttl_kg = $orders->sum('ttl_kg');
|
|
|
|
|
|
|
|
|
|
// -----------------------------
|
|
|
|
|
// CREATE SHIPMENT
|
|
|
|
|
//-------------------------------
|
|
|
|
|
$shipment = Shipment::create([
|
|
|
|
|
'shipment_id' => $newShipmentId,
|
|
|
|
|
'origin' => $request->origin,
|
|
|
|
|
'destination' => $request->destination,
|
|
|
|
|
'status' => Shipment::STATUS_PENDING,
|
|
|
|
|
'shipment_date' => $request->shipment_date,
|
|
|
|
|
|
|
|
|
|
'total_ctn' => $total_ctn,
|
|
|
|
|
'total_qty' => $total_qty,
|
|
|
|
|
'total_ttl_qty' => $total_ttl_qty,
|
|
|
|
|
'total_amount' => $total_amount,
|
|
|
|
|
'total_cbm' => $total_cbm,
|
|
|
|
|
'total_ttl_cbm' => $total_ttl_cbm,
|
|
|
|
|
'total_kg' => $total_kg,
|
|
|
|
|
'total_ttl_kg' => $total_ttl_kg,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// -----------------------------
|
|
|
|
|
// INSERT SHIPMENT ITEMS
|
|
|
|
|
// -----------------------------
|
|
|
|
|
foreach ($orders as $order) {
|
|
|
|
|
ShipmentItem::create([
|
|
|
|
|
'shipment_id' => $shipment->id,
|
|
|
|
|
'order_id' => $order->id,
|
|
|
|
|
'order_ctn' => $order->ctn,
|
|
|
|
|
'order_qty' => $order->qty,
|
|
|
|
|
'order_ttl_qty' => $order->ttl_qty,
|
|
|
|
|
'order_ttl_amount' => $order->ttl_amount,
|
2025-12-03 15:36:04 +05:30
|
|
|
'order_cbm' => $order->cbm,
|
|
|
|
|
'order_ttl_cbm' => $order->ttl_cbm,
|
|
|
|
|
'order_kg' => $order->kg,
|
2025-11-14 13:55:01 +05:30
|
|
|
'order_ttl_kg' => $order->ttl_kg,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->back()->with('success', "Shipment $newShipmentId created successfully!");
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-03 15:36:04 +05:30
|
|
|
public function edit($id)
|
|
|
|
|
{
|
|
|
|
|
$shipment = Shipment::with('orders')->findOrFail($id);
|
|
|
|
|
|
|
|
|
|
return view('admin.shipments.show', [
|
|
|
|
|
'shipment' => $shipment,
|
|
|
|
|
'orders' => $shipment->orders,
|
|
|
|
|
'isViewMode' => false // This is the edit mode
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 13:55:01 +05:30
|
|
|
public function show($id)
|
|
|
|
|
{
|
2025-12-03 15:36:04 +05:30
|
|
|
$mode = request()->get('mode', 'view');
|
2025-11-14 13:55:01 +05:30
|
|
|
|
2025-12-03 15:36:04 +05:30
|
|
|
$shipment = Shipment::with(['items.order'])->findOrFail($id);
|
2025-11-14 13:55:01 +05:30
|
|
|
|
2025-12-03 15:36:04 +05:30
|
|
|
// Get orders from shipment items
|
|
|
|
|
$orders = collect();
|
|
|
|
|
foreach ($shipment->items as $item) {
|
|
|
|
|
if ($item->order) {
|
|
|
|
|
$orders->push($item->order);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get orders not assigned to any shipment (available orders)
|
|
|
|
|
$usedOrderIds = ShipmentItem::pluck('order_id')->toArray();
|
|
|
|
|
$availableOrders = Order::whereNotIn('id', $usedOrderIds)->get();
|
|
|
|
|
|
|
|
|
|
return view('admin.view-shipment', [
|
2025-11-14 13:55:01 +05:30
|
|
|
'shipment' => $shipment,
|
2025-12-03 15:36:04 +05:30
|
|
|
'orders' => $orders,
|
|
|
|
|
'mode' => $mode,
|
|
|
|
|
'availableOrders' => $availableOrders
|
2025-11-14 13:55:01 +05:30
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-03 15:36:04 +05:30
|
|
|
public function addOrders(Request $request, $id)
|
|
|
|
|
{
|
|
|
|
|
$request->validate([
|
|
|
|
|
'order_ids' => 'required|array|min:1'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$shipment = Shipment::findOrFail($id);
|
|
|
|
|
$orderIds = $request->order_ids;
|
|
|
|
|
|
|
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$orders = Order::whereIn('id', $orderIds)->get();
|
|
|
|
|
$addedOrders = [];
|
|
|
|
|
|
|
|
|
|
foreach ($orders as $order) {
|
|
|
|
|
// Prevent duplicates
|
|
|
|
|
if (ShipmentItem::where('shipment_id', $shipment->id)->where('order_id', $order->id)->exists()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ShipmentItem::create([
|
|
|
|
|
'shipment_id' => $shipment->id,
|
|
|
|
|
'order_id' => $order->id,
|
|
|
|
|
'order_ctn' => $order->ctn,
|
|
|
|
|
'order_qty' => $order->qty,
|
|
|
|
|
'order_ttl_qty' => $order->ttl_qty,
|
|
|
|
|
'order_ttl_amount' => $order->ttl_amount,
|
|
|
|
|
'order_cbm' => $order->cbm,
|
|
|
|
|
'order_ttl_cbm' => $order->ttl_cbm,
|
|
|
|
|
'order_kg' => $order->kg,
|
|
|
|
|
'order_ttl_kg' => $order->ttl_kg,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$addedOrders[] = $order;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Recalculate totals
|
|
|
|
|
$this->recalculateShipmentTotals($shipment->id);
|
|
|
|
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
|
|
|
|
$shipment->refresh();
|
|
|
|
|
$shipment->load('items.order');
|
|
|
|
|
|
|
|
|
|
// Get updated orders list
|
|
|
|
|
$updatedOrders = collect();
|
|
|
|
|
foreach ($shipment->items as $item) {
|
|
|
|
|
if ($item->order) {
|
|
|
|
|
$updatedOrders->push($item->order);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => 'Orders added to shipment successfully.',
|
|
|
|
|
'shipment' => $shipment,
|
|
|
|
|
'orders' => $addedOrders
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
DB::rollBack();
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'message' => 'Failed to add orders: ' . $e->getMessage()
|
|
|
|
|
], 500);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 13:55:01 +05:30
|
|
|
/**
|
|
|
|
|
* Update Shipment status from action button
|
|
|
|
|
*/
|
|
|
|
|
public function updateStatus(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$request->validate([
|
|
|
|
|
'shipment_id' => 'required|exists:shipments,id',
|
|
|
|
|
'status' => 'required|string'
|
|
|
|
|
]);
|
|
|
|
|
|
2025-11-21 16:07:43 +05:30
|
|
|
// 1) Update shipment status
|
2025-11-14 13:55:01 +05:30
|
|
|
$shipment = Shipment::findOrFail($request->shipment_id);
|
|
|
|
|
$shipment->status = $request->status;
|
|
|
|
|
$shipment->save();
|
|
|
|
|
|
2025-11-21 16:07:43 +05:30
|
|
|
// 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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->back()->with(
|
|
|
|
|
'success',
|
|
|
|
|
"Shipment status updated to {$shipment->statusLabel()} and related orders updated."
|
|
|
|
|
);
|
2025-11-14 13:55:01 +05:30
|
|
|
}
|
|
|
|
|
|
2025-12-01 10:38:52 +05:30
|
|
|
/**
|
|
|
|
|
* Update shipment details
|
|
|
|
|
*/
|
|
|
|
|
public function update(Request $request, $id)
|
|
|
|
|
{
|
|
|
|
|
$shipment = Shipment::findOrFail($id);
|
|
|
|
|
|
|
|
|
|
$data = $request->validate([
|
|
|
|
|
'origin' => 'required|string',
|
|
|
|
|
'destination' => 'required|string',
|
|
|
|
|
'shipment_date' => 'required|date',
|
|
|
|
|
'status' => 'required|string',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$shipment->update($data);
|
|
|
|
|
|
|
|
|
|
// If it's an AJAX request, return JSON response
|
|
|
|
|
if ($request->ajax() || $request->wantsJson()) {
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => 'Shipment updated successfully.'
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->back()->with('success', 'Shipment updated successfully.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Delete shipment permanently
|
|
|
|
|
*/
|
|
|
|
|
public function destroy($id, Request $request)
|
|
|
|
|
{
|
|
|
|
|
$shipment = Shipment::findOrFail($id);
|
|
|
|
|
|
|
|
|
|
// Delete shipment items
|
|
|
|
|
ShipmentItem::where('shipment_id', $shipment->id)->delete();
|
|
|
|
|
|
|
|
|
|
// Delete shipment itself
|
|
|
|
|
$shipment->delete();
|
2025-11-21 16:07:43 +05:30
|
|
|
|
2025-12-01 10:38:52 +05:30
|
|
|
// If it's an AJAX request, return JSON response
|
|
|
|
|
if ($request->ajax() || $request->wantsJson()) {
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => 'Shipment deleted successfully.'
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->route('admin.shipments')
|
|
|
|
|
->with('success', 'Shipment deleted successfully.');
|
|
|
|
|
}
|
2025-12-03 15:36:04 +05:30
|
|
|
|
|
|
|
|
public function removeOrder(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$request->validate([
|
|
|
|
|
'shipment_id' => 'required|exists:shipments,id',
|
|
|
|
|
'order_id' => 'required|exists:orders,id'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$shipmentId = $request->shipment_id;
|
|
|
|
|
$orderId = $request->order_id;
|
|
|
|
|
|
|
|
|
|
// Get order data before deletion
|
|
|
|
|
$order = Order::findOrFail($orderId);
|
|
|
|
|
|
|
|
|
|
// Delete pivot entry
|
|
|
|
|
ShipmentItem::where('shipment_id', $shipmentId)
|
|
|
|
|
->where('order_id', $orderId)
|
|
|
|
|
->delete();
|
|
|
|
|
|
|
|
|
|
// Recalculate totals
|
|
|
|
|
$shipment = $this->recalculateShipmentTotals($shipmentId);
|
|
|
|
|
|
|
|
|
|
if ($request->ajax() || $request->wantsJson()) {
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => 'Order removed successfully.',
|
|
|
|
|
'order' => $order,
|
|
|
|
|
'shipment' => $shipment
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return back()->with('success', 'Order removed successfully.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function recalculateShipmentTotals($shipmentId)
|
|
|
|
|
{
|
|
|
|
|
$shipment = Shipment::with('items')->findOrFail($shipmentId);
|
|
|
|
|
|
|
|
|
|
// Use the shipment items to calculate totals
|
|
|
|
|
$shipment->total_ctn = $shipment->items->sum('order_ctn');
|
|
|
|
|
$shipment->total_qty = $shipment->items->sum('order_qty');
|
|
|
|
|
$shipment->total_ttl_qty = $shipment->items->sum('order_ttl_qty');
|
|
|
|
|
$shipment->total_amount = $shipment->items->sum('order_ttl_amount');
|
|
|
|
|
$shipment->total_cbm = $shipment->items->sum('order_cbm');
|
|
|
|
|
$shipment->total_ttl_cbm = $shipment->items->sum('order_ttl_cbm');
|
|
|
|
|
$shipment->total_kg = $shipment->items->sum('order_kg');
|
|
|
|
|
$shipment->total_ttl_kg = $shipment->items->sum('order_ttl_kg');
|
|
|
|
|
|
|
|
|
|
$shipment->save();
|
|
|
|
|
$shipment->refresh(); // Refresh to get updated data
|
|
|
|
|
|
|
|
|
|
return $shipment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper method to get available orders for a shipment
|
|
|
|
|
public function getAvailableOrders($shipmentId)
|
|
|
|
|
{
|
|
|
|
|
$shipment = Shipment::findOrFail($shipmentId);
|
|
|
|
|
|
|
|
|
|
// Get all used order IDs
|
|
|
|
|
$usedOrderIds = ShipmentItem::pluck('order_id')->toArray();
|
|
|
|
|
|
|
|
|
|
// Remove orders that are already in this shipment
|
|
|
|
|
$shipmentOrderIds = $shipment->items->pluck('order_id')->toArray();
|
|
|
|
|
$availableOrderIds = array_diff($usedOrderIds, $shipmentOrderIds);
|
|
|
|
|
|
|
|
|
|
$availableOrders = Order::whereNotIn('id', $availableOrderIds)->get();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'availableOrders' => $availableOrders
|
|
|
|
|
]);
|
|
|
|
|
}
|
2025-12-01 10:38:52 +05:30
|
|
|
}
|