diff --git a/app/Http/Controllers/Admin/AdminAccountController.php b/app/Http/Controllers/Admin/AdminAccountController.php new file mode 100644 index 0000000..d760951 --- /dev/null +++ b/app/Http/Controllers/Admin/AdminAccountController.php @@ -0,0 +1,248 @@ +orderBy('id', 'desc') + ->get(); + + return response()->json([ + 'success' => true, + 'entries' => $entries + ]); + } + + /** + * 🚀 2. Get available consolidated orders + */ + public function getAvailableOrders() + { + $orders = Order::whereDoesntHave('entries') + ->orderBy('id', 'desc') + ->get(); + + return response()->json([ + 'success' => true, + 'orders' => $orders + ]); + } + + /** + * 🚀 3. Create new entry + */ + public function accountCreateOrder(Request $request) + { + $data = $request->validate([ + 'description' => 'required|string|max:255', + 'region' => 'required|string|max:50', + 'amount' => 'required|numeric|min:1', + 'entry_date' => 'nullable|date', + 'selected_orders' => 'nullable|array', + 'selected_orders.*'=> 'integer|exists:orders,id', + ]); + + return DB::transaction(function () use ($data) { + + $entryDate = $data['entry_date'] ?? now()->toDateString(); + + // Count selected consolidated orders + $orderQuantity = !empty($data['selected_orders']) + ? count($data['selected_orders']) + : 0; + + // Generate entry No: PAY-2025-001 + $prefix = 'PAY-' . date('Y') . '-'; + $last = Entry::where('entry_no', 'like', $prefix . '%') + ->orderBy('id', 'desc') + ->first(); + + $next = $last + ? intval(substr($last->entry_no, strrpos($last->entry_no, '-') + 1)) + 1 + : 1; + + $entryNo = $prefix . str_pad($next, 7, '0', STR_PAD_LEFT); + + // Create entry + $entry = Entry::create([ + 'entry_no' => $entryNo, + 'description' => $data['description'], + 'region' => $data['region'], + 'order_quantity' => $orderQuantity, + 'amount' => $data['amount'], + 'pending_amount' => $data['amount'], + 'entry_date' => $entryDate, + 'payment_status' => 'unpaid', + 'toggle_pos' => 0, + 'dispatch_status' => 'pending', + ]); + + // Attach consolidated orders + if (!empty($data['selected_orders'])) { + $entry->orders()->attach($data['selected_orders']); + } + + $entry->load('orders'); + + return response()->json([ + 'success' => true, + 'message' => 'Entry Created Successfully', + 'entry' => $entry + ], 201); + }); + } + + /** + * 🚀 4. Toggle payment switch + */ + public function togglePayment(Request $request) + { + $request->validate([ + 'entry_no' => 'required|string|exists:entries,entry_no', + 'toggle_pos' => 'required|integer|in:0,1,2', + ]); + + $entry = Entry::where('entry_no', $request->entry_no)->firstOrFail(); + + $map = [ + 0 => 'unpaid', + 1 => 'pending', + 2 => 'paid' + ]; + + $entry->update([ + 'toggle_pos' => $request->toggle_pos, + 'payment_status' => $map[$request->toggle_pos], + ]); + + return response()->json([ + 'success' => true, + 'entry' => $entry + ]); + } + + /** + * 🚀 5. Add Installment + */ + public function addInstallment(Request $request) + { + $data = $request->validate([ + 'entry_no' => 'required|exists:entries,entry_no', + 'proc_date' => 'nullable|date', + 'amount' => 'required|numeric|min:1', + 'status' => 'required|string' + ]); + + return DB::transaction(function () use ($data) { + + $entry = Entry::where('entry_no', $data['entry_no']) + ->lockForUpdate() + ->firstOrFail(); + + $amount = floatval($data['amount']); + + if ($amount > $entry->pending_amount) { + return response()->json([ + 'success' => false, + 'message' => 'Installment cannot exceed pending amount.' + ], 422); + } + + $installment = Installment::create([ + 'entry_id' => $entry->id, + 'proc_date' => $data['proc_date'] ?? now()->toDateString(), + 'amount' => $amount, + 'description'=> $entry->description, + 'region' => $entry->region, + 'status' => $data['status'] + ]); + + $entry->pending_amount -= $amount; + + if ($entry->pending_amount <= 0.001) { + $entry->pending_amount = 0; + $entry->dispatch_status = 'dispatched'; + } + + $entry->save(); + + return response()->json([ + 'success' => true, + 'entry' => $entry, + 'installment' => $installment + ]); + }); + } + + /** + * 🚀 6. Update Installment Status + */ + public function updateInstallmentStatus(Request $request) + { + $data = $request->validate([ + 'installment_id' => 'required|exists:installments,id', + 'status' => 'required|string|max:50' + ]); + + return DB::transaction(function () use ($data) { + + $installment = Installment::lockForUpdate()->findOrFail($data['installment_id']); + + $installment->status = $data['status']; + $installment->save(); + + $entry = Entry::lockForUpdate()->find($installment->entry_id); + + // If ANY installment is not delivered — entry is NOT delivered + if ($entry->installments()->where('status', '!=', 'Delivered')->exists()) { + // entry still in progress + $entry->dispatch_status = 'pending'; + } else { + // all installments delivered + $entry->dispatch_status = 'delivered'; + } + + $entry->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Installment updated successfully', + 'installment' => $installment, + 'entry' => $entry + ]); + }); + } + + + /** + * 🚀 6. Entry Details (installment history) + */ + public function getEntryDetails($entry_no) + { + $entry = Entry::with('installments') + ->where('entry_no', $entry_no) + ->firstOrFail(); + + $totalProcessed = $entry->amount - $entry->pending_amount; + + return response()->json([ + 'success' => true, + 'entry' => $entry, + 'total_processed' => $totalProcessed, + 'pending' => $entry->pending_amount, + ]); + } +} diff --git a/app/Http/Controllers/Admin/AdminReportController.php b/app/Http/Controllers/Admin/AdminReportController.php new file mode 100644 index 0000000..c47cb62 --- /dev/null +++ b/app/Http/Controllers/Admin/AdminReportController.php @@ -0,0 +1,56 @@ +join('shipment_items', 'shipment_items.order_id', '=', 'orders.id') + ->join('shipments', 'shipments.id', '=', 'shipment_items.shipment_id') + ->join('invoices', 'invoices.order_id', '=', 'orders.id') + ->leftJoin('mark_list', 'mark_list.mark_no', '=', 'orders.mark_no') + ->leftJoin('users', 'users.customer_id', '=', 'mark_list.customer_id') + + ->select( + 'orders.id as order_pk', + 'orders.order_id', + 'orders.mark_no', + 'orders.origin', + 'orders.destination', + + 'shipments.id as shipment_pk', + 'shipments.shipment_id', + 'shipments.status as shipment_status', + 'shipments.shipment_date', + + 'invoices.invoice_number', + 'invoices.invoice_date', + 'invoices.final_amount', + 'invoices.status as invoice_status', + + 'mark_list.company_name', + 'mark_list.customer_name' + ) + + ->orderBy('shipments.shipment_date', 'desc') + ->get(); + + return view('admin.reports', compact('reports')); + } +} diff --git a/app/Http/Controllers/Admin/ShipmentController.php b/app/Http/Controllers/Admin/ShipmentController.php index 28dc465..c0883fd 100644 --- a/app/Http/Controllers/Admin/ShipmentController.php +++ b/app/Http/Controllers/Admin/ShipmentController.php @@ -147,11 +147,22 @@ class ShipmentController extends Controller 'status' => 'required|string' ]); + // 1) Update shipment status $shipment = Shipment::findOrFail($request->shipment_id); $shipment->status = $request->status; $shipment->save(); - return redirect()->back()->with('success', "Shipment status updated to {$shipment->statusLabel()}"); + // 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." + ); } + } diff --git a/app/Models/Entry.php b/app/Models/Entry.php new file mode 100644 index 0000000..498ee8e --- /dev/null +++ b/app/Models/Entry.php @@ -0,0 +1,37 @@ +hasMany(Installment::class); + } + + // An entry can have multiple orders (consolidated orders) + public function orders() + { + return $this->belongsToMany(Order::class, 'entry_order', 'entry_id', 'order_id') + ->withTimestamps(); + } +} diff --git a/app/Models/EntryOrder.php b/app/Models/EntryOrder.php new file mode 100644 index 0000000..54b7974 --- /dev/null +++ b/app/Models/EntryOrder.php @@ -0,0 +1,28 @@ +belongsTo(Entry::class); + } + + public function order() + { + return $this->belongsTo(Order::class); + } +} diff --git a/app/Models/Installment.php b/app/Models/Installment.php new file mode 100644 index 0000000..8c92b2b --- /dev/null +++ b/app/Models/Installment.php @@ -0,0 +1,25 @@ +belongsTo(Entry::class); + } +} diff --git a/app/Models/Order.php b/app/Models/Order.php index fdfd683..3113fa7 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -39,4 +39,11 @@ class Order extends Model { return $this->hasOne(MarkList::class, 'mark_no', 'mark_no'); } + + public function entries() + { + return $this->belongsToMany(Entry::class, 'entry_order', 'order_id', 'entry_id') + ->withTimestamps(); + } + } diff --git a/database/migrations/2025_01_01_000000_create_entries_table.php b/database/migrations/2025_01_01_000000_create_entries_table.php new file mode 100644 index 0000000..89dbd1d --- /dev/null +++ b/database/migrations/2025_01_01_000000_create_entries_table.php @@ -0,0 +1,46 @@ +id(); + + $table->string('entry_no')->unique(); // PAY-2024-001 + $table->string('description'); + $table->string('region'); + + $table->unsignedInteger('order_quantity')->default(0); // selected consolidated order count + + $table->decimal('amount', 12, 2); + $table->decimal('pending_amount', 12, 2); // always <= amount + + $table->date('entry_date'); // auto-today default by controller + + // Toggle-based payment states + $table->enum('payment_status', ['unpaid', 'pending', 'paid'])->default('unpaid'); + $table->tinyInteger('toggle_pos')->default(0); // 0 left, 1 middle, 2 right + + // Dispatch state (for second table) + $table->enum('dispatch_status', [ + 'pending', + 'loading', + 'packed', + 'dispatched', + 'delivered' + ])->default('pending'); + + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('entries'); + } +}; diff --git a/database/migrations/2025_01_01_000001_create_installments_table.php b/database/migrations/2025_01_01_000001_create_installments_table.php new file mode 100644 index 0000000..864fd26 --- /dev/null +++ b/database/migrations/2025_01_01_000001_create_installments_table.php @@ -0,0 +1,40 @@ +id(); + + $table->foreignId('entry_id') + ->constrained('entries') + ->onDelete('cascade'); + + $table->date('proc_date'); // processing date + $table->decimal('amount', 12, 2); + + $table->string('description')->nullable(); + $table->string('region')->nullable(); + + $table->enum('status', [ + 'Pending', + 'Loading', + 'Packed', + 'Dispatched', + 'Delivered' + ])->default('Pending'); + + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('installments'); + } +}; diff --git a/database/migrations/2025_01_01_000002_create_entry_order_table.php b/database/migrations/2025_01_01_000002_create_entry_order_table.php new file mode 100644 index 0000000..85559b4 --- /dev/null +++ b/database/migrations/2025_01_01_000002_create_entry_order_table.php @@ -0,0 +1,30 @@ +id(); + + $table->foreignId('entry_id') + ->constrained('entries') + ->onDelete('cascade'); + + $table->foreignId('order_id') + ->constrained('orders') + ->onDelete('cascade'); + + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('entry_order'); + } +}; diff --git a/public/invoices/invoice-INV-2025-000007.pdf b/public/invoices/invoice-INV-2025-000007.pdf new file mode 100644 index 0000000..c40bfac Binary files /dev/null and b/public/invoices/invoice-INV-2025-000007.pdf differ diff --git a/public/invoices/invoice-INV-2025-000008.pdf b/public/invoices/invoice-INV-2025-000008.pdf new file mode 100644 index 0000000..155edfe Binary files /dev/null and b/public/invoices/invoice-INV-2025-000008.pdf differ diff --git a/resources/views/admin/account.blade.php b/resources/views/admin/account.blade.php index ca4e0aa..768db26 100644 --- a/resources/views/admin/account.blade.php +++ b/resources/views/admin/account.blade.php @@ -3,447 +3,330 @@ @section('page-title', 'Account Dashboard') @section('content') +
Viewing: China Region Orders
-Workflow: Manage payments and dispatch process effectively
+Viewing: All Regions — Workflow: Manage payments and dispatch.
| Entry No | Date | Description | -Order Quantity | Region | Toggle | Amount | Status | +Order Quantity | Region | Payment | Amount | Status |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| PAY-2023-042 | 2024-07-19 | -Electronic Cams | 3 | China | -- | ₹3,000 | -Unpaid | -|||||
| PAY-2023-041 | 2024-07-19 | -Electronic Cams | 2 | China | -- | ₹2,000 | -Paid | -|||||
| Loading entries... | ||||||||||||
| Pending | |||||||
|---|---|---|---|---|---|---|---|
| PAY-2023-042 | -2024-07-19 | -Electronic Cams | -China | -₹3,000 | -Loading | -Completed | -- |
| PAY-2023-041 | -2024-07-19 | -Electronic Cams | -China | -₹2,000 | -Dispatched | -₹1,000 | -- |
| Loading entries... | |||||||
Complete view of all installments for this entry.
+| Installment | @@ -613,399 +382,506 @@ tr:hover td { background-color:#f9fafc;}Region | Amount | Status | -Pending | ||
|---|---|---|---|---|---|---|
| Original Entry | -2024-01-15 | -Electronics Comp | -China | -$2,000 | -Loading | -$1,500 | -
| Installment 2 | -2024-01-15 | -Electronics Comp | -China | -$1,000 | -Dispatched | -$1,500 | -
| Installment 3 | -2024-01-15 | -Electronics Comp | -China | -$500 | -Dispatched | -$1,500 | -
| No installments yet | ||||||