Compare commits

..

14 Commits

Author SHA256 Message Date
Utkarsh Khedkar
33571a5fd7 Resolve merge conflicts 2026-02-17 14:44:47 +05:30
Utkarsh Khedkar
94e211f87e Add Container field 2026-02-17 14:32:48 +05:30
Utkarsh Khedkar
8b6d3d5fad Account Changes 2025-12-27 11:15:00 +05:30
Utkarsh Khedkar
c89e5bdf7d changes 2025-12-25 18:11:33 +05:30
Utkarsh Khedkar
10af713fa1 Changes 2025-12-25 11:38:02 +05:30
Utkarsh Khedkar
ebb263cd36 Merge branch 'dev' of http://103.248.30.24:3000/kent-logistics/Kent-logistics-Laravel into dev 2025-12-25 10:22:36 +05:30
Utkarsh Khedkar
82d9c10130 Dashboard Changes 2025-12-25 10:22:20 +05:30
divya abdar
cb24cf575b changes in order,popup invoice ,invoice pdf,invoice edit,customer add,customer a 2025-12-25 10:19:20 +05:30
Utkarsh Khedkar
e4c07cb838 shipment Changes 2025-12-24 13:36:50 +05:30
Utkarsh Khedkar
f38a5afdd7 shipment Changes 2025-12-24 13:34:44 +05:30
divya abdar
9423c79c80 changes of dashboard and order , requests 2025-12-24 13:32:47 +05:30
divya abdar
8a958b9c48 dashboard order and request file changes 2025-12-24 10:47:20 +05:30
Utkarsh Khedkar
82882e859e changes 2025-12-23 13:09:33 +05:30
Utkarsh Khedkar
2d28e7c1d5 changes 2025-12-23 13:08:26 +05:30
144 changed files with 10015 additions and 13217 deletions

View File

@@ -1,6 +1,6 @@
APP_NAME=Laravel APP_NAME=Laravel
APP_ENV=local APP_ENV=local
APP_KEY=base64:/ulhRgiCOFjZV6xUDkXLfiR9X8iFRZ4QIiX3UJbdwY4= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost

View File

@@ -4,10 +4,10 @@ namespace App\Events;
use App\Models\ChatMessage; use App\Models\ChatMessage;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class NewChatMessage implements ShouldBroadcastNow class NewChatMessage implements ShouldBroadcast
{ {
use SerializesModels; use SerializesModels;
@@ -18,22 +18,29 @@ class NewChatMessage implements ShouldBroadcastNow
*/ */
public function __construct(ChatMessage $message) public function __construct(ChatMessage $message)
{ {
// Also load sender polymorphic relationship // Safe data only (no heavy relationships in queue)
$message->load('sender'); $this->message = [
'id' => $message->id,
'ticket_id' => $message->ticket_id,
'sender_id' => $message->sender_id,
'sender_type' => $message->sender_type,
'message' => $message->message,
'file_path' => $message->file_path,
'file_type' => $message->file_type,
'created_at' => $message->created_at->toDateTimeString(),
];
$this->message = $message; // Load sender separately for broadcastWith()
$this->sender = $message->sender;
} }
/** /**
* The channel the event should broadcast on. * The channel the event should broadcast on.
*/ */
public function broadcastOn() public function broadcastOn()
{ {
return [ return new PrivateChannel('ticket.' . $this->message->ticket_id);
new PrivateChannel('ticket.' . $this->message->ticket_id),
new PrivateChannel('admin.chat') // 👈 ADD THIS
];
} }
/** /**
@@ -41,40 +48,25 @@ class NewChatMessage implements ShouldBroadcastNow
*/ */
public function broadcastWith() public function broadcastWith()
{ {
\Log::info('APP_URL USED IN EVENT', [
'url' => config('app.url'),
]);
\Log::info("DEBUG: NewChatMessage broadcasting on channel ticket.".$this->message->ticket_id); \Log::info("DEBUG: NewChatMessage broadcasting on channel ticket.".$this->message->ticket_id);
\Log::info("EVENT BROADCAST FIRED", [
'channel' => 'ticket.'.$this->message->ticket_id,
'sender_type' => $this->message->sender_type,
'sender_id' => $this->message->sender_id,
]);
return [ return [
'id' => $this->message->id, 'id' => $this->message->id,
'ticket_id' => $this->message->ticket_id, 'ticket_id' => $this->message->ticket_id,
'sender_id' => $this->message->sender_id, 'sender_id' => $this->message->sender_id,
'sender_type' => $this->message->sender_type, 'sender_type' => $this->message->sender_type,
'message' => $this->message->message, 'message' => $this->message->message,
'client_id' => $this->message->client_id, 'file_url' => $this->message->file_path
? asset('storage/' . $this->message->file_path)
// ✅ relative path only : null,
'file_path' => $this->message->file_path ?? null, 'file_type' => $this->message->file_type,
'file_type' => $this->message->file_type ?? null,
'sender' => [ 'sender' => [
'id' => $this->message->sender->id, 'id' => $this->message->sender->id,
'name' => $this->getSenderName(), 'name' => $this->getSenderName(),
'is_admin' => $this->message->sender_type === \App\Models\Admin::class, 'is_admin' => $this->message->sender_type === \App\Models\Admin::class,
], ],
'created_at' => $this->message->created_at->toDateTimeString(), 'created_at' => $this->message->created_at->toDateTimeString(),
]; ];
} }
/** /**
@@ -92,10 +84,4 @@ class NewChatMessage implements ShouldBroadcastNow
// Admin model has ->name // Admin model has ->name
return $sender->name ?? "Admin"; return $sender->name ?? "Admin";
} }
public function broadcastAs()
{
return 'NewChatMessage';
}
} }

View File

@@ -1,63 +0,0 @@
<?php
namespace App\Exports;
use Illuminate\Contracts\View\View;
use Maatwebsite\Excel\Concerns\FromView;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class InvoicesExport implements FromView
{
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function view(): View
{
$request = $this->request;
$invoices = DB::table('invoices')
->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
->select(
'invoices.invoice_number',
'invoices.invoice_date',
'invoices.mark_no',
'containers.container_number',
'containers.container_date',
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name'),
'invoices.final_amount',
'invoices.final_amount_with_gst',
'invoices.status as invoice_status'
)
->when($request->filled('search'), function ($q) use ($request) {
$search = trim($request->search);
$q->where(function ($qq) use ($search) {
$qq->where('invoices.invoice_number', 'like', "%{$search}%")
->orWhere('invoices.mark_no', 'like', "%{$search}%")
->orWhere('containers.container_number', 'like', "%{$search}%")
->orWhere('mark_list.company_name', 'like', "%{$search}%")
->orWhere('mark_list.customer_name', 'like', "%{$search}%");
});
})
->when($request->filled('status'), function ($q) use ($request) {
$q->where('invoices.status', $request->status);
})
->when($request->filled('from_date'), function ($q) use ($request) {
$q->whereDate('invoices.invoice_date', '>=', $request->from_date);
})
->when($request->filled('to_date'), function ($q) use ($request) {
$q->whereDate('invoices.invoice_date', '<=', $request->to_date);
})
->orderByDesc('containers.container_date')
->orderByDesc('invoices.id')
->get();
return view('admin.pdf.invoices_excel', compact('invoices'));
}
}

View File

@@ -5,10 +5,6 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Models\Container;
use App\Models\Invoice;
use App\Models\User;
use App\Models\MarkList;
class AdminAuthController extends Controller class AdminAuthController extends Controller
{ {
@@ -39,11 +35,12 @@ class AdminAuthController extends Controller
'password' => $request->password, 'password' => $request->password,
]; ];
// attempt login
if (Auth::guard('admin')->attempt($credentials)) { if (Auth::guard('admin')->attempt($credentials)) {
$request->session()->regenerate(); $request->session()->regenerate();
$user = Auth::guard('admin')->user(); $user = Auth::guard('admin')->user();
return redirect()->route('admin.dashboard')
->with('success', 'Welcome back, ' . $user->name . '!'); return redirect()->route('admin.dashboard')->with('success', 'Welcome back, ' . $user->name . '!');
} }
return back()->withErrors(['login' => 'Invalid login credentials.']); return back()->withErrors(['login' => 'Invalid login credentials.']);
@@ -54,25 +51,6 @@ class AdminAuthController extends Controller
Auth::guard('admin')->logout(); Auth::guard('admin')->logout();
$request->session()->invalidate(); $request->session()->invalidate();
$request->session()->regenerateToken(); $request->session()->regenerateToken();
return redirect()->route('admin.login') return redirect()->route('admin.login')->with('success', 'Logged out successfully.');
->with('success', 'Logged out successfully.');
}
public function profile()
{
$user = Auth::guard('admin')->user();
// ── Real Stats ──
$stats = [
'total_containers' => Container::count(),
'total_invoices' => Invoice::count(),
'paid_invoices' => Invoice::where('status', 'paid')->count(),
'pending_invoices' => Invoice::where('status', 'pending')->count(),
'total_customers' => User::count(),
'total_marklist' => MarkList::count(),
'active_marklist' => MarkList::where('status', 'active')->count(),
];
return view('admin.profile', compact('user', 'stats'));
} }
} }

View File

@@ -3,14 +3,35 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
<<<<<<< HEAD
use App\Models\SupportTicket;
use App\Models\ChatMessage;
use App\Events\NewChatMessage;
use Illuminate\Http\Request;
=======
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\SupportTicket; use App\Models\SupportTicket;
use App\Models\ChatMessage; use App\Models\ChatMessage;
use App\Events\NewChatMessage; use App\Events\NewChatMessage;
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
class AdminChatController extends Controller class AdminChatController extends Controller
{ {
/** /**
<<<<<<< HEAD
* Page 1: List all customer chat tickets
*/
public function index()
{
$tickets = SupportTicket::with(['user', 'messages' => function($query) {
$query->latest()->limit(1);
}])
->orderBy('updated_at', 'desc')
->get();
return view('admin.chat_support', compact('tickets'));
}
=======
* Page 1: List all active user chats * Page 1: List all active user chats
*/ */
public function index() public function index()
@@ -28,11 +49,26 @@ class AdminChatController extends Controller
return view('admin.chat_support', compact('tickets')); return view('admin.chat_support', compact('tickets'));
} }
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
/** /**
* Page 2: Open chat window for a specific user * Page 2: Open chat window for a specific user
*/ */
public function openChat($ticketId) public function openChat($ticketId)
<<<<<<< HEAD
{
$ticket = SupportTicket::with('user')->findOrFail($ticketId);
$messages = ChatMessage::where('ticket_id', $ticketId)
->orderBy('created_at', 'asc')
->with('sender')
->get();
return view('admin.chat_window', compact('ticket', 'messages'));
}
/**
* Admin sends a message to the user (FIXED - LIVE CHAT)
=======
{ {
$ticket = SupportTicket::with('user')->findOrFail($ticketId); $ticket = SupportTicket::with('user')->findOrFail($ticketId);
@@ -53,6 +89,7 @@ class AdminChatController extends Controller
/** /**
* Admin sends a message to the user * Admin sends a message to the user
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
*/ */
public function sendMessage(Request $request, $ticketId) public function sendMessage(Request $request, $ticketId)
{ {
@@ -69,9 +106,12 @@ class AdminChatController extends Controller
'sender_id' => $admin->id, 'sender_id' => $admin->id,
'sender_type' => \App\Models\Admin::class, 'sender_type' => \App\Models\Admin::class,
'message' => $request->message, 'message' => $request->message,
<<<<<<< HEAD
=======
'read_by_admin' => true, 'read_by_admin' => true,
'read_by_user' => false, 'read_by_user' => false,
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
]; ];
// File Upload // File Upload
@@ -85,11 +125,20 @@ class AdminChatController extends Controller
$message = ChatMessage::create($data); $message = ChatMessage::create($data);
$message->load('sender'); $message->load('sender');
<<<<<<< HEAD
\Log::info("DEBUG: ChatController sendMessage called", [ \Log::info("DEBUG: ChatController sendMessage called", [
=======
\Log::info("DEBUG: ChatController sendMessage called", [
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
'ticket_id' => $ticketId, 'ticket_id' => $ticketId,
'payload' => $request->all() 'payload' => $request->all()
]); ]);
<<<<<<< HEAD
// 🔥 LIVE CHAT - Queue bypass (100% working)
broadcast(new NewChatMessage($message))->toOthers();
=======
// Broadcast real-time // Broadcast real-time
broadcast(new NewChatMessage($message)); broadcast(new NewChatMessage($message));
@@ -97,6 +146,7 @@ class AdminChatController extends Controller
'ticket_id' => $ticketId, 'ticket_id' => $ticketId,
'payload' => $request->all() 'payload' => $request->all()
]); ]);
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'message' => $message 'message' => $message

View File

@@ -22,8 +22,7 @@ class AdminCustomerController extends Controller
$query = User::with([ $query = User::with([
'marks', 'marks',
'orders', 'orders',
'invoices.installments', 'invoices.installments' // 🔥 IMPORTANT
'invoices.chargeGroups', // 🔥 for order total calculation
])->orderBy('id', 'desc'); ])->orderBy('id', 'desc');
if (!empty($search)) { if (!empty($search)) {

View File

@@ -2,70 +2,37 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; // ⬅️ हे नक्की असू दे use App\Http\Controllers\Controller;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceItem; use App\Models\InvoiceItem;
use App\Models\InvoiceChargeGroup;
use App\Models\InvoiceChargeGroupItem;
use App\Models\InvoiceInstallment; use App\Models\InvoiceInstallment;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Mpdf\Mpdf; use Mpdf\Mpdf;
class AdminInvoiceController extends Controller class AdminInvoiceController extends Controller
{ {
// ------------------------------------------------------------- // -------------------------------------------------------------
// INDEX (LIST ALL INVOICES WITH FILTERS) // INVOICE LIST PAGE
// ------------------------------------------------------------- // -------------------------------------------------------------
public function index(Request $request) public function index()
{ {
$query = Invoice::query(); $invoices = Invoice::with(['items', 'customer', 'container'])
->latest()
if ($request->filled('search')) { ->get();
$search = $request->search;
$query->where(function ($q) use ($search) {
$q->where('invoice_number', 'like', "%{$search}%")
->orWhere('customer_name', 'like', "%{$search}%");
});
}
if ($request->filled('status') && $request->status !== 'all') {
$query->where('status', $request->status);
}
if ($request->filled('start_date')) {
$query->whereDate('invoice_date', '>=', $request->start_date);
}
if ($request->filled('end_date')) {
$query->whereDate('invoice_date', '<=', $request->end_date);
}
$invoices = $query->latest()->get();
return view('admin.invoice', compact('invoices')); return view('admin.invoice', compact('invoices'));
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// POPUP VIEW // POPUP VIEW (AJAX)
// ------------------------------------------------------------- // -------------------------------------------------------------
public function popup($id) public function popup($id)
{ {
$invoice = Invoice::with([ $invoice = Invoice::with(['items', 'customer', 'container'])->findOrFail($id);
'items',
'chargeGroups.items',
])->findOrFail($id);
$shipment = null; $shipment = null;
$groupedItemIds = $invoice->chargeGroups return view('admin.popup_invoice', compact('invoice', 'shipment'));
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
->unique()
->values()
->toArray();
return view('admin.popup_invoice', compact('invoice', 'shipment', 'groupedItemIds'));
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
@@ -73,49 +40,24 @@ class AdminInvoiceController extends Controller
// ------------------------------------------------------------- // -------------------------------------------------------------
public function edit($id) public function edit($id)
{ {
$invoice = Invoice::with([ $invoice = Invoice::with(['items', 'customer', 'container'])->findOrFail($id);
'items',
'customer',
'container',
'chargeGroups.items',
'installments',
])->findOrFail($id);
// ✅ Customer details sync
if ($invoice->customer) {
$needsUpdate = [];
if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') {
$needsUpdate['customer_email'] = $invoice->customer->email;
}
if (empty($invoice->customer_address) || $invoice->customer_address === 'TEST ADDRESS') {
$needsUpdate['customer_address'] = $invoice->customer->address;
}
if (empty($invoice->pincode) || $invoice->pincode === '999999') {
$needsUpdate['pincode'] = $invoice->customer->pincode;
}
if (!empty($needsUpdate)) {
$invoice->update($needsUpdate);
$invoice->refresh();
}
}
$shipment = null; $shipment = null;
$groupedItemIds = $invoice->chargeGroups // ADD THIS SECTION: Calculate customer's total due across all invoices
->flatMap(function ($group) { $customerTotalDue = Invoice::where('customer_id', $invoice->customer_id)
return $group->items->pluck('invoice_item_id'); ->where('status', '!=', 'cancelled')
}) ->where('status', '!=', 'void')
->unique() ->sum('final_amount_with_gst');
->values()
->toArray();
return view('admin.invoice_edit', compact('invoice', 'shipment', 'groupedItemIds')); // Pass the new variable to the view
return view('admin.invoice_edit', compact('invoice', 'shipment', 'customerTotalDue'));
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// UPDATE INVOICE (HEADER ONLY) // UPDATE INVOICE (HEADER LEVEL)
// -------------------------------------------------------------
// -------------------------------------------------------------
// UPDATE INVOICE (HEADER LEVEL)
// ------------------------------------------------------------- // -------------------------------------------------------------
public function update(Request $request, $id) public function update(Request $request, $id)
{ {
@@ -126,34 +68,85 @@ class AdminInvoiceController extends Controller
$invoice = Invoice::findOrFail($id); $invoice = Invoice::findOrFail($id);
// 1) VALIDATION
$data = $request->validate([ $data = $request->validate([
'invoice_date' => 'required|date', 'invoice_date' => 'required|date',
'due_date' => 'required|date|after_or_equal:invoice_date', 'due_date' => 'required|date|after_or_equal:invoice_date',
'status' => 'required|in:pending,paying,paid,overdue', 'final_amount' => 'required|numeric|min:0',
'tax_type' => 'required|in:gst,igst',
'tax_percent' => 'required|numeric|min:0|max:28',
'status' => 'required|in:pending,paid,overdue',
'notes' => 'nullable|string', 'notes' => 'nullable|string',
]); ]);
Log::info('✅ Validated Invoice Header Update Data', $data); Log::info('✅ Validated Invoice Update Data', $data);
$invoice->update($data); // 2) CALCULATE GST / TOTALS
$invoice->refresh(); $finalAmount = (float) $data['final_amount'];
$taxPercent = (float) $data['tax_percent'];
Log::info('🔍 Invoice AFTER HEADER UPDATE', [ if ($data['tax_type'] === 'gst') {
Log::info('🟢 GST Selected', compact('taxPercent'));
$data['cgst_percent'] = $taxPercent / 2;
$data['sgst_percent'] = $taxPercent / 2;
$data['igst_percent'] = 0;
} else {
Log::info('🔵 IGST Selected', compact('taxPercent'));
$data['cgst_percent'] = 0;
$data['sgst_percent'] = 0;
$data['igst_percent'] = $taxPercent;
}
$gstAmount = ($finalAmount * $taxPercent) / 100;
$data['gst_amount'] = $gstAmount;
$data['final_amount_with_gst'] = $finalAmount + $gstAmount;
$data['gst_percent'] = $taxPercent;
Log::info('📌 Final Calculated Invoice Values', [
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'charge_groups_total' => $invoice->charge_groups_total, 'final_amount' => $finalAmount,
'gst_amount' => $invoice->gst_amount, 'gst_amount' => $data['gst_amount'],
'grand_total_with_charges'=> $invoice->grand_total_with_charges, 'final_amount_with_gst' => $data['final_amount_with_gst'],
'tax_type' => $data['tax_type'],
'cgst_percent' => $data['cgst_percent'],
'sgst_percent' => $data['sgst_percent'],
'igst_percent' => $data['igst_percent'],
]); ]);
// 3) UPDATE DB
$invoice->update($data);
Log::info('✅ Invoice Updated Successfully', [
'invoice_id' => $invoice->id,
]);
// 4) LOG ACTUAL DB VALUES
$invoice->refresh();
Log::info('🔍 Invoice AFTER UPDATE (DB values)', [
'invoice_id' => $invoice->id,
'final_amount' => $invoice->final_amount,
'gst_percent' => $invoice->gst_percent,
'gst_amount' => $invoice->gst_amount,
'final_amount_with_gst' => $invoice->final_amount_with_gst,
'tax_type' => $invoice->tax_type,
'cgst_percent' => $invoice->cgst_percent,
'sgst_percent' => $invoice->sgst_percent,
'igst_percent' => $invoice->igst_percent,
]);
// 5) REGENERATE PDF
$this->generateInvoicePDF($invoice); $this->generateInvoicePDF($invoice);
return redirect() return redirect()
->route('admin.invoices.edit', $invoice->id) ->route('admin.invoices.index')
->with('success', 'Invoice updated & PDF generated successfully.'); ->with('success', 'Invoice updated & PDF generated successfully.');
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// UPDATE INVOICE ITEMS (फक्त items save) // 🔹 UPDATE INVOICE ITEMS (price + ttl_amount)
// ------------------------------------------------------------- // -------------------------------------------------------------
public function updateItems(Request $request, Invoice $invoice) public function updateItems(Request $request, Invoice $invoice)
{ {
@@ -168,7 +161,9 @@ class AdminInvoiceController extends Controller
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'], 'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
]); ]);
foreach ($data['items'] as $itemId => $itemData) { $itemsInput = $data['items'];
foreach ($itemsInput as $itemId => $itemData) {
$item = InvoiceItem::where('id', $itemId) $item = InvoiceItem::where('id', $itemId)
->where('invoice_id', $invoice->id) ->where('invoice_id', $invoice->id)
->first(); ->first();
@@ -186,20 +181,50 @@ class AdminInvoiceController extends Controller
$item->save(); $item->save();
} }
Log::info('✅ Invoice items updated (no totals recalculation)', [ $newBaseAmount = InvoiceItem::where('invoice_id', $invoice->id)
->sum('ttl_amount');
$taxType = $invoice->tax_type;
$cgstPercent = (float) ($invoice->cgst_percent ?? 0);
$sgstPercent = (float) ($invoice->sgst_percent ?? 0);
$igstPercent = (float) ($invoice->igst_percent ?? 0);
$gstPercent = 0;
if ($taxType === 'gst') {
$gstPercent = $cgstPercent + $sgstPercent;
} elseif ($taxType === 'igst') {
$gstPercent = $igstPercent;
}
$gstAmount = $newBaseAmount * $gstPercent / 100;
$finalWithGst = $newBaseAmount + $gstAmount;
$invoice->final_amount = $newBaseAmount;
$invoice->gst_amount = $gstAmount;
$invoice->final_amount_with_gst = $finalWithGst;
$invoice->gst_percent = $gstPercent;
$invoice->save();
Log::info('✅ Invoice items updated & totals recalculated', [
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'final_amount' => $invoice->final_amount,
'gst_amount' => $invoice->gst_amount,
'final_amount_with_gst' => $invoice->final_amount_with_gst,
'tax_type' => $invoice->tax_type,
'cgst_percent' => $invoice->cgst_percent,
'sgst_percent' => $invoice->sgst_percent,
'igst_percent' => $invoice->igst_percent,
]); ]);
return back()->with('success', 'Invoice items updated successfully.'); return back()->with('success', 'Invoice items updated successfully.');
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// PDF GENERATION (EXISTING - केवळ chargeGroups load ला confirm कर) // PDF GENERATION USING mPDF
// ------------------------------------------------------------- // -------------------------------------------------------------
public function generateInvoicePDF($invoice) public function generateInvoicePDF($invoice)
{ {
// ✅ यामध्ये chargeGroups आणि installments load कर $invoice->load(['items', 'customer', 'container']);
$invoice->load(['items', 'customer', 'container', 'chargeGroups.items', 'installments']);
$shipment = null; $shipment = null;
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf'; $fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
@@ -232,6 +257,17 @@ class AdminInvoiceController extends Controller
$invoice->update(['pdf_path' => 'invoices/' . $fileName]); $invoice->update(['pdf_path' => 'invoices/' . $fileName]);
} }
public function downloadInvoice($id)
{
$invoice = Invoice::findOrFail($id);
// ALWAYS regenerate to reflect latest HTML/CSS
$this->generateInvoicePDF($invoice);
$invoice->refresh();
return response()->download(public_path($invoice->pdf_path));
}
// ------------------------------------------------------------- // -------------------------------------------------------------
// INSTALLMENTS (ADD) // INSTALLMENTS (ADD)
// ------------------------------------------------------------- // -------------------------------------------------------------
@@ -245,11 +281,8 @@ class AdminInvoiceController extends Controller
]); ]);
$invoice = Invoice::findOrFail($invoice_id); $invoice = Invoice::findOrFail($invoice_id);
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$paidTotal = $invoice->installments()->sum('amount'); $paidTotal = $invoice->installments()->sum('amount');
$remaining = $grandTotal - $paidTotal; $remaining = $invoice->final_amount_with_gst - $paidTotal;
if ($request->amount > $remaining) { if ($request->amount > $remaining) {
return response()->json([ return response()->json([
@@ -267,40 +300,23 @@ class AdminInvoiceController extends Controller
]); ]);
$newPaid = $paidTotal + $request->amount; $newPaid = $paidTotal + $request->amount;
$remaining = max(0, $grandTotal - $newPaid);
$isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay()); if ($newPaid >= $invoice->final_amount_with_gst) {
$invoice->update(['status' => 'paid']);
if ($grandTotal > 0 && $newPaid >= $grandTotal) { $this->generateInvoicePDF($invoice);
$newStatus = 'paid';
} elseif ($newPaid > 0 && $isOverdue) {
$newStatus = 'overdue';
} elseif ($newPaid > 0 && !$isOverdue) {
$newStatus = 'paying';
} elseif ($newPaid <= 0 && $isOverdue) {
$newStatus = 'overdue';
} else {
$newStatus = 'pending';
} }
$invoice->update([
'payment_method' => $request->payment_method,
'reference_no' => $request->reference_no,
'status' => $newStatus,
]);
return response()->json([ return response()->json([
'status' => 'success', 'status' => 'success',
'message' => 'Installment added successfully.', 'message' => 'Installment added successfully.',
'installment' => $installment, 'installment' => $installment,
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $newPaid, 'totalPaid' => $newPaid,
'remaining' => $remaining, 'gstAmount' => $invoice->gst_amount,
'newStatus' => $newStatus, 'finalAmountWithGst' => $invoice->final_amount_with_gst,
'isCompleted' => $remaining <= 0, 'baseAmount' => $invoice->final_amount,
'isZero' => $newPaid == 0, 'remaining' => max(0, $invoice->final_amount_with_gst - $newPaid),
'isCompleted' => $newPaid >= $invoice->final_amount_with_gst,
]); ]);
} }
@@ -313,192 +329,25 @@ class AdminInvoiceController extends Controller
$invoice = $installment->invoice; $invoice = $installment->invoice;
$installment->delete(); $installment->delete();
$invoice->refresh();
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$paidTotal = $invoice->installments()->sum('amount'); $paidTotal = $invoice->installments()->sum('amount');
$remaining = max(0, $grandTotal - $paidTotal); $remaining = $invoice->final_amount_with_gst - $paidTotal;
$isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay()); if ($remaining > 0 && $invoice->status === 'paid') {
$invoice->update(['status' => 'pending']);
if ($grandTotal > 0 && $paidTotal >= $grandTotal) { $this->generateInvoicePDF($invoice);
$newStatus = 'paid';
} elseif ($paidTotal > 0 && $isOverdue) {
$newStatus = 'overdue';
} elseif ($paidTotal > 0 && !$isOverdue) {
$newStatus = 'paying';
} elseif ($paidTotal <= 0 && $isOverdue) {
$newStatus = 'overdue';
} else {
$newStatus = 'pending';
} }
$invoice->update(['status' => $newStatus]);
return response()->json([ return response()->json([
'status' => 'success', 'status' => 'success',
'message' => 'Installment deleted.', 'message' => 'Installment deleted.',
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $paidTotal, 'totalPaid' => $paidTotal,
'gstAmount' => $invoice->gst_amount,
'finalAmountWithGst' => $invoice->final_amount_with_gst,
'baseAmount' => $invoice->final_amount,
'remaining' => $remaining, 'remaining' => $remaining,
'newStatus' => $newStatus,
'isZero' => $paidTotal == 0, 'isZero' => $paidTotal == 0,
]); ]);
} }
// -------------------------------------------------------------
// CHARGE GROUP SAVE
// -------------------------------------------------------------
public function storeChargeGroup(Request $request, $invoiceId)
{
Log::info('🟡 storeChargeGroup HIT', [
'invoice_id' => $invoiceId,
'payload' => $request->all(),
]);
$invoice = Invoice::with('items', 'chargeGroups')->findOrFail($invoiceId);
$data = $request->validate([
'groupname' => 'required|string|max:255',
'basistype' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
'basisvalue' => 'required|numeric',
'rate' => 'required|numeric|min:0.0001',
'autototal' => 'required|numeric|min:0.01',
'itemids' => 'required|array',
'itemids.*' => 'integer|exists:invoice_items,id',
'tax_type' => 'nullable|in:none,gst,igst',
'gst_percent' => 'nullable|numeric|min:0|max:28',
'total_with_gst' => 'nullable|numeric|min:0',
]);
Log::info('✅ storeChargeGroup VALIDATED', $data);
$exists = InvoiceChargeGroup::where('invoice_id', $invoice->id)
->where('group_name', $data['groupname'])
->exists();
if ($exists) {
return back()
->withErrors(['groupname' => 'This group name is already used for this invoice.'])
->withInput();
}
$taxType = $data['tax_type'] ?? 'gst';
$gstPercent = $data['gst_percent'] ?? 0;
$baseTotal = $data['autototal'];
$totalWithGst = $data['total_with_gst'] ?? $baseTotal;
if ($totalWithGst == 0 && $gstPercent > 0) {
$gstAmount = ($baseTotal * $gstPercent) / 100;
$totalWithGst = $baseTotal + $gstAmount;
}
$group = InvoiceChargeGroup::create([
'invoice_id' => $invoice->id,
'group_name' => $data['groupname'],
'basis_type' => $data['basistype'],
'basis_value' => $data['basisvalue'],
'rate' => $data['rate'],
'total_charge' => $baseTotal,
'tax_type' => $taxType,
'gst_percent' => $gstPercent,
'total_with_gst' => $totalWithGst,
]);
foreach ($data['itemids'] as $itemId) {
InvoiceChargeGroupItem::create([
'group_id' => $group->id,
'invoice_item_id' => $itemId,
]);
}
$invoice->load('chargeGroups');
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge');
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst');
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase;
$invoiceGstPercent = $group->gst_percent ?? 0;
$invoiceTaxType = $group->tax_type ?? 'gst';
$cgstPercent = 0;
$sgstPercent = 0;
$igstPercent = 0;
if ($invoiceTaxType === 'gst') {
$cgstPercent = $invoiceGstPercent / 2;
$sgstPercent = $invoiceGstPercent / 2;
} elseif ($invoiceTaxType === 'igst') {
$igstPercent = $invoiceGstPercent;
}
$invoice->update([
'charge_groups_total' => $chargeGroupsBase,
'gst_amount' => $chargeGroupsGst,
'gst_percent' => $invoiceGstPercent,
'tax_type' => $invoiceTaxType,
'cgst_percent' => $cgstPercent,
'sgst_percent' => $sgstPercent,
'igst_percent' => $igstPercent,
'final_amount' => $chargeGroupsBase,
'final_amount_with_gst' => $chargeGroupsWithG,
'grand_total_with_charges' => $chargeGroupsWithG,
]);
Log::info('✅ Invoice updated from Charge Group (CG total + GST)', [
'invoice_id' => $invoice->id,
'charge_groups_total' => $chargeGroupsBase,
'gst_amount' => $chargeGroupsGst,
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
]);
return response()->json([
'success' => true,
'message' => 'Charge group saved successfully.',
'group_id' => $group->id,
]);
}
// ============================================
// 🆕 PDF DOWNLOAD (Direct browser download)
// ============================================
public function downloadPdf($id)
{
$invoice = Invoice::with(['items', 'customer', 'container', 'chargeGroups.items', 'installments'])
->findOrFail($id);
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
$folder = public_path('invoices/');
$filePath = $folder . $fileName;
// जर PDF exist नसेल तर generate कर
if (!file_exists($filePath)) {
$this->generateInvoicePDF($invoice);
}
return response()->download($filePath, $fileName);
}
// ============================================
// 🆕 EXCEL DOWNLOAD (CSV format - simple)
// ============================================
public function share($id)
{
$invoice = Invoice::findOrFail($id);
// इथे तुला जसं share करायचंय तसं logic टाक:
// उदा. public link generate करून redirect कर, किंवा WhatsApp deeplink, इ.
$url = route('admin.invoices.popup', $invoice->id); // example: popup link share
return redirect()->away('https://wa.me/?text=' . urlencode($url));
}
} }

View File

@@ -10,118 +10,26 @@ use App\Models\MarkList;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceItem; use App\Models\InvoiceItem;
use App\Models\User; use App\Models\User;
use App\Models\Container;
use App\Models\Admin;
use App\Models\Shipment;
use PDF; use PDF;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
use App\Exports\OrdersExport; use App\Exports\OrdersExport;
use App\Imports\OrderItemsPreviewImport; use App\Imports\OrderItemsPreviewImport;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\DB;
use App\Exports\InvoicesExport;
class AdminOrderController extends Controller class AdminOrderController extends Controller
{ {
/* --------------------------- /* ---------------------------
* DASHBOARD (old UI: stats + recent orders) * LIST / DASHBOARD
* ---------------------------*/ * ---------------------------*/
public function dashboard() public function index()
{ {
// ── Order counts (from Invoice Management / Orders table) ──
$totalOrders = Order::count();
// "Pending" म्हणजे delivered नसलेले सर्व orders
// Order तयार होतो तेव्हा status = 'order_placed' असतो, 'pending' नाही
$deliveredStatuses = ['delivered'];
$pendingOrders = Order::whereNotIn('status', $deliveredStatuses)->count();
// ── Invoice counts ──
$totalContainers = Container::count();
$totalInvoices = Invoice::count();
$paidInvoices = Invoice::where('status', 'paid')->count();
$pendingInvoices = Invoice::where('status', 'pending')->count();
$overdueInvoices = Invoice::where('status', 'overdue')->count();
$totalRevenue = Invoice::sum('final_amount_with_gst');
// ── User / Staff counts ──
$activeCustomers = User::where('status', 'active')->count();
$inactiveCustomers = User::where('status', 'inactive')->count();
$totalStaff = Admin::where('type', 'staff')->count();
$markList = MarkList::where('status', 'active')->get();
$orders = Order::latest()->get(); $orders = Order::latest()->get();
$markList = MarkList::where('status', 'active')->get();
return view('admin.dashboard', compact( return view('admin.dashboard', compact('orders', 'markList'));
'totalOrders',
'pendingOrders',
'totalContainers',
'totalInvoices',
'paidInvoices',
'pendingInvoices',
'overdueInvoices',
'totalRevenue',
'activeCustomers',
'inactiveCustomers',
'totalStaff',
'orders',
'markList'
));
}
/* ---------------------------
* LIST (new: Invoices Management for Orders page)
* ---------------------------*/
public function index(Request $request)
{
$invoices = DB::table('invoices')
->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
->select(
'invoices.id',
'invoices.invoice_number',
'invoices.invoice_date',
'invoices.final_amount',
'invoices.final_amount_with_gst',
'invoices.status as invoice_status',
'invoices.mark_no',
'invoices.container_id', // <<< हे नक्की घाल
'containers.container_number',
'containers.container_date',
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name')
)
->when($request->filled('search'), function ($q) use ($request) {
$search = trim($request->search);
$q->where(function ($qq) use ($search) {
$qq->where('invoices.invoice_number', 'like', "%{$search}%")
->orWhere('containers.container_number', 'like', "%{$search}%")
->orWhere('invoices.mark_no', 'like', "%{$search}%")
->orWhere('mark_list.company_name', 'like', "%{$search}%")
->orWhere('mark_list.customer_name', 'like', "%{$search}%");
});
})
->when($request->filled('status'), function ($q) use ($request) {
$q->where('invoices.status', $request->status);
})
->orderByDesc('invoices.invoice_date')
->orderByDesc('invoices.id')
->get();
// ── Real DB counts (filter-independent) ──
$totalInvoices = \App\Models\Invoice::count();
$paidInvoices = \App\Models\Invoice::where('status', 'paid')->count();
$pendingInvoices = \App\Models\Invoice::where('status', 'pending')->count();
$overdueInvoices = \App\Models\Invoice::where('status', 'overdue')->count();
return view('admin.orders', compact(
'invoices',
'totalInvoices',
'paidInvoices',
'pendingInvoices',
'overdueInvoices'
));
} }
/* --------------------------- /* ---------------------------
@@ -133,9 +41,52 @@ class AdminOrderController extends Controller
return view('admin.orders_create', compact('markList')); return view('admin.orders_create', compact('markList'));
} }
/* --------------------------- /**
* SHOW / POPUP * Store a new order and optionally create initial invoice
* ---------------------------*/ */
public function store(Request $request)
{
$data = $request->validate([
'mark_no' => 'required|string',
'origin' => 'nullable|string',
'destination' => 'nullable|string',
// totals optional when creating without items
'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',
]);
//If you want to auto-create an invoice at order creation, uncomment:
// $this->createInvoice($order);
return redirect()->route('admin.orders.show', $order->id)
->with('success', 'Order created successfully.');
}
// ---------------------------
// SHOW / POPUP
// ---------------------------
public function show($id) public function show($id)
{ {
$order = Order::with('items', 'markList')->findOrFail($id); $order = Order::with('items', 'markList')->findOrFail($id);
@@ -153,7 +104,15 @@ class AdminOrderController extends Controller
$user = User::where('customer_id', $order->markList->customer_id)->first(); $user = User::where('customer_id', $order->markList->customer_id)->first();
} }
return view('admin.popup', compact('order', 'user')); $data['order_id'] = $order->id;
OrderItem::create($data);
// recalc totals and save to order
$this->recalcTotals($order);
// $this->updateInvoiceFromOrder($order); // <-- NEW
return redirect()->back()->with('success', 'Item added and totals updated.');
} }
/* --------------------------- /* ---------------------------
@@ -174,6 +133,7 @@ class AdminOrderController extends Controller
'shop_no' => 'nullable|string', 'shop_no' => 'nullable|string',
]); ]);
// ✅ BACKEND CALCULATION
$ctn = (float) ($data['ctn'] ?? 0); $ctn = (float) ($data['ctn'] ?? 0);
$qty = (float) ($data['qty'] ?? 0); $qty = (float) ($data['qty'] ?? 0);
$price = (float) ($data['price'] ?? 0); $price = (float) ($data['price'] ?? 0);
@@ -190,10 +150,12 @@ class AdminOrderController extends Controller
OrderItem::create($data); OrderItem::create($data);
$this->recalcTotals($order); $this->recalcTotals($order);
$this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item added and totals updated.'); return redirect()->back()->with('success', 'Item added and totals updated.');
} }
public function deleteItem($id) public function deleteItem($id)
{ {
$item = OrderItem::findOrFail($id); $item = OrderItem::findOrFail($id);
@@ -202,6 +164,7 @@ class AdminOrderController extends Controller
$item->delete(); $item->delete();
$this->recalcTotals($order); $this->recalcTotals($order);
$this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item deleted and totals updated.'); return redirect()->back()->with('success', 'Item deleted and totals updated.');
} }
@@ -214,6 +177,7 @@ class AdminOrderController extends Controller
$item->restore(); $item->restore();
$this->recalcTotals($order); $this->recalcTotals($order);
$this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item restored and totals updated.'); return redirect()->back()->with('success', 'Item restored and totals updated.');
} }
@@ -372,6 +336,7 @@ class AdminOrderController extends Controller
$order = Order::with([ $order = Order::with([
'markList', 'markList',
'items', 'items',
'invoice.items',
'shipments' => function ($q) use ($id) { 'shipments' => function ($q) use ($id) {
$q->whereHas('orders', function ($oq) use ($id) { $q->whereHas('orders', function ($oq) use ($id) {
$oq->where('orders.id', $id) $oq->where('orders.id', $id)
@@ -451,6 +416,29 @@ class AdminOrderController extends Controller
} }
$invoiceData = null; $invoiceData = null;
if ($order->invoice) {
$invoice = $order->invoice;
$invoiceData = [
'invoice_no' => $invoice->invoice_number,
'status' => $invoice->status,
'invoice_date' => $invoice->invoice_date,
'due_date' => $invoice->due_date,
'customer' => [
'name' => $invoice->customer_name,
'mobile' => $invoice->customer_mobile,
'email' => $invoice->customer_email,
'address' => $invoice->customer_address,
'pincode' => $invoice->pincode,
],
'items' => $invoice->items,
'summary' => [
'amount' => $invoice->final_amount,
'cgst' => 0,
'sgst' => 0,
'total' => $invoice->final_amount_with_gst,
],
];
}
return view('admin.see_order', compact( return view('admin.see_order', compact(
'order', 'order',
@@ -461,13 +449,14 @@ class AdminOrderController extends Controller
} }
/* --------------------------- /* ---------------------------
* FILTERED LIST + EXPORTS (old orders listing) * FILTERED LIST + EXPORTS
* ---------------------------*/ * ---------------------------*/
public function orderShow() public function orderShow()
{ {
$orders = Order::with([ $orders = Order::with([
'markList', 'markList',
'shipments', 'shipments',
'invoice'
])->latest('id')->get(); ])->latest('id')->get();
return view('admin.orders', compact('orders')); return view('admin.orders', compact('orders'));
@@ -476,7 +465,7 @@ class AdminOrderController extends Controller
private function buildOrdersQueryFromRequest(Request $request) private function buildOrdersQueryFromRequest(Request $request)
{ {
$query = Order::query() $query = Order::query()
->with(['markList', 'shipments']); ->with(['markList', 'invoice', 'shipments']);
if ($request->filled('search')) { if ($request->filled('search')) {
$search = trim($request->search); $search = trim($request->search);
@@ -489,12 +478,23 @@ class AdminOrderController extends Controller
->orWhere('origin', 'like', "%{$search}%") ->orWhere('origin', 'like', "%{$search}%")
->orWhere('destination', 'like', "%{$search}%"); ->orWhere('destination', 'like', "%{$search}%");
}) })
->orWhereHas('invoice', function ($q3) use ($search) {
$q3->where('invoice_number', 'like', "%{$search}%");
})
->orWhereHas('shipments', function ($q4) use ($search) { ->orWhereHas('shipments', function ($q4) use ($search) {
$q4->where('shipments.shipment_id', 'like', "%{$search}%"); $q4->where('shipments.shipment_id', 'like', "%{$search}%");
}); });
}); });
} }
if ($request->filled('status')) {
$query->where(function ($q) use ($request) {
$q->whereHas('invoice', function ($q2) use ($request) {
$q2->where('status', $request->status);
})->orWhereDoesntHave('invoice');
});
}
if ($request->filled('shipment')) { if ($request->filled('shipment')) {
$query->where(function ($q) use ($request) { $query->where(function ($q) use ($request) {
$q->whereHas('shipments', function ($q2) use ($request) { $q->whereHas('shipments', function ($q2) use ($request) {
@@ -516,83 +516,62 @@ class AdminOrderController extends Controller
public function downloadPdf(Request $request) public function downloadPdf(Request $request)
{ {
$invoices = DB::table('invoices') $orders = $this->buildOrdersQueryFromRequest($request)->get();
->leftJoin('containers', 'containers.id', '=', 'invoices.container_id') $filters = [
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no') 'search' => $request->search,
->select( 'status' => $request->status,
'invoices.invoice_number', 'shipment' => $request->shipment,
'invoices.invoice_date', 'from' => $request->from_date,
'invoices.mark_no', 'to' => $request->to_date,
'containers.container_number', ];
'containers.container_date',
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name'),
'invoices.final_amount',
'invoices.final_amount_with_gst',
'invoices.status as invoice_status'
)
->when($request->filled('search'), function ($q) use ($request) {
$search = trim($request->search);
$q->where(function ($qq) use ($search) {
$qq->where('invoices.invoice_number', 'like', "%{$search}%")
->orWhere('invoices.mark_no', 'like', "%{$search}%")
->orWhere('containers.container_number', 'like', "%{$search}%")
->orWhere('mark_list.company_name', 'like', "%{$search}%")
->orWhere('mark_list.customer_name', 'like', "%{$search}%");
});
})
->when($request->filled('status'), function ($q) use ($request) {
$q->where('invoices.status', $request->status);
})
->when($request->filled('from_date'), function ($q) use ($request) {
$q->whereDate('invoices.invoice_date', '>=', $request->from_date);
})
->when($request->filled('to_date'), function ($q) use ($request) {
$q->whereDate('invoices.invoice_date', '<=', $request->to_date);
})
->orderByDesc('containers.container_date')
->orderByDesc('invoices.id')
->get();
$pdf = PDF::loadView('admin.pdf.invoices_report', compact('invoices')) $pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters'))
->setPaper('a4', 'landscape'); ->setPaper('a4', 'landscape');
return $pdf->download( return $pdf->download(
'invoices-report-' . now()->format('Y-m-d') . '.pdf' 'orders-report-' . now()->format('Y-m-d') . '.pdf'
); );
} }
public function downloadExcel(Request $request) public function downloadExcel(Request $request)
{ {
return Excel::download( return Excel::download(
new InvoicesExport($request), new OrdersExport($request),
'invoices-report-' . now()->format('Y-m-d') . '.xlsx' 'orders-report-' . now()->format('Y-m-d') . '.xlsx'
); );
} }
/* -------------------------------------------------- /* --------------------------------------------------
* NEW: Create Order + Invoice directly from popup * NEW: Create Order + Invoice directly from popup
* route: admin.orders.temp.add (Create New Order form)
* --------------------------------------------------*/ * --------------------------------------------------*/
public function addTempItem(Request $request) public function addTempItem(Request $request)
{ {
// 1) order-level fields
$request->validate([ $request->validate([
'mark_no' => 'required', 'mark_no' => 'required',
'origin' => 'nullable', 'origin' => 'required',
'destination' => 'nullable', 'destination' => 'required',
]); ]);
// 2) multi-row items
$items = $request->validate([ $items = $request->validate([
'items' => 'required|array', 'items' => 'required|array',
'items.*.description' => 'required|string', 'items.*.description' => 'required|string',
'items.*.ctn' => 'nullable|numeric', 'items.*.ctn' => 'nullable|numeric',
'items.*.qty' => 'nullable|numeric', 'items.*.qty' => 'nullable|numeric',
'items.*.unit' => 'nullable|string', 'items.*.unit' => 'nullable|string',
'items.*.price' => 'nullable|numeric', 'items.*.price' => 'nullable|numeric',
'items.*.cbm' => 'nullable|numeric', 'items.*.cbm' => 'nullable|numeric',
'items.*.kg' => 'nullable|numeric', 'items.*.kg' => 'nullable|numeric',
'items.*.shop_no' => 'nullable|string', 'items.*.shop_no' => 'nullable|string',
])['items']; ])['items'];
// रिकामे rows काढा
$items = array_filter($items, function ($row) { $items = array_filter($items, function ($row) {
return trim($row['description'] ?? '') !== ''; return trim($row['description'] ?? '') !== '';
}); });
@@ -601,20 +580,25 @@ class AdminOrderController extends Controller
return back()->with('error', 'Add at least one item.'); return back()->with('error', 'Add at least one item.');
} }
// ✅ BACKEND CALCULATION (DO NOT TRUST FRONTEND)
foreach ($items as &$item) { foreach ($items as &$item) {
$ctn = (float) ($item['ctn'] ?? 0); $ctn = (float) ($item['ctn'] ?? 0);
$qty = (float) ($item['qty'] ?? 0); $qty = (float) ($item['qty'] ?? 0);
$price = (float) ($item['price'] ?? 0); $price = (float) ($item['price'] ?? 0);
$cbm = (float) ($item['cbm'] ?? 0); $cbm = (float) ($item['cbm'] ?? 0);
$kg = (float) ($item['kg'] ?? 0); $kg = (float) ($item['kg'] ?? 0);
// Calculated fields
$item['ttl_qty'] = $ctn * $qty; $item['ttl_qty'] = $ctn * $qty;
$item['ttl_amount'] = $item['ttl_qty'] * $price; $item['ttl_amount'] = $item['ttl_qty'] * $price;
$item['ttl_cbm'] = $cbm * $ctn; $item['ttl_cbm'] = $cbm * $ctn;
$item['ttl_kg'] = $ctn * $kg; $item['ttl_kg'] = $ctn * $kg;
} }
unset($item); unset($item); // VERY IMPORTANT
// 3) totals
$total_ctn = array_sum(array_column($items, 'ctn')); $total_ctn = array_sum(array_column($items, 'ctn'));
$total_qty = array_sum(array_column($items, 'qty')); $total_qty = array_sum(array_column($items, 'qty'));
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty')); $total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
@@ -624,8 +608,10 @@ class AdminOrderController extends Controller
$total_kg = array_sum(array_column($items, 'kg')); $total_kg = array_sum(array_column($items, 'kg'));
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg')); $total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
// 4) order id generate
$orderId = $this->generateOrderId(); $orderId = $this->generateOrderId();
// 5) order create
$order = Order::create([ $order = Order::create([
'order_id' => $orderId, 'order_id' => $orderId,
'mark_no' => $request->mark_no, 'mark_no' => $request->mark_no,
@@ -642,6 +628,7 @@ class AdminOrderController extends Controller
'status' => 'order_placed', 'status' => 'order_placed',
]); ]);
// 6) order items
foreach ($items as $item) { foreach ($items as $item) {
OrderItem::create([ OrderItem::create([
'order_id' => $order->id, 'order_id' => $order->id,
@@ -660,55 +647,81 @@ class AdminOrderController extends Controller
]); ]);
} }
// 7) invoice number
$invoiceNumber = $this->generateInvoiceNumber(); $invoiceNumber = $this->generateInvoiceNumber();
$markList = MarkList::where('mark_no', $order->mark_no)->first(); // 1. Auto-generate invoice number
$customer = null; // $lastInvoice = \App\Models\Invoice::latest()->first();
if ($markList && $markList->customer_id) { // $nextInvoice = $lastInvoice ? $lastInvoice->id + 1 : 1;
$customer = User::where('customer_id', $markList->customer_id)->first(); // $invoiceNumber = 'INV-' . date('Y') . '-' . str_pad($nextInvoice, 6, '0', STR_PAD_LEFT);
}
$invoice = Invoice::create([ // 2. Fetch customer (using mark list → customer_id)
'order_id' => $order->id, // $markList = MarkList::where('mark_no', $order->mark_no)->first();
'customer_id' => $customer->id ?? null, // $customer = null;
'mark_no' => $order->mark_no,
'invoice_number' => $invoiceNumber,
'invoice_date' => now(),
'due_date' => now()->addDays(10),
'payment_method' => null,
'reference_no' => null,
'status' => 'pending',
'final_amount' => $total_amount,
'gst_percent' => 0,
'gst_amount' => 0,
'final_amount_with_gst' => $total_amount,
'customer_name' => $customer->customer_name ?? null,
'company_name' => $customer->company_name ?? null,
'customer_email' => $customer->email ?? null,
'customer_mobile' => $customer->mobile_no ?? null,
'customer_address' => $customer->address ?? null,
'pincode' => $customer->pincode ?? null,
'notes' => null,
'pdf_path' => null,
]);
foreach ($order->items as $item) { // if ($markList && $markList->customer_id) {
InvoiceItem::create([ // $customer = \App\Models\User::where('customer_id', $markList->customer_id)->first();
'invoice_id' => $invoice->id, // }
'description' => $item->description,
'ctn' => $item->ctn, // 3. Create Invoice Record
'qty' => $item->qty, // $invoice = \App\Models\Invoice::create([
'ttl_qty' => $item->ttl_qty, // 'order_id' => $order->id,
'unit' => $item->unit, // 'customer_id' => $customer->id ?? null,
'price' => $item->price, // 'mark_no' => $order->mark_no,
'ttl_amount' => $item->ttl_amount,
'cbm' => $item->cbm, // 'invoice_number' => $invoiceNumber,
'ttl_cbm' => $item->ttl_cbm, // 'invoice_date' => now(),
'kg' => $item->kg, // 'due_date' => now()->addDays(10),
'ttl_kg' => $item->ttl_kg,
'shop_no' => $item->shop_no, // 'payment_method' => null,
]); // 'reference_no' => null,
} // 'status' => 'pending',
// 'final_amount' => $total_amount,
// 'gst_percent' => 0,
// 'gst_amount' => 0,
// 'final_amount_with_gst' => $total_amount,
// // snapshot customer fields
// 'customer_name' => $customer->customer_name ?? null,
// 'company_name' => $customer->company_name ?? null,
// 'customer_email' => $customer->email ?? null,
// 'customer_mobile' => $customer->mobile_no ?? null,
// 'customer_address' => $customer->address ?? null,
// 'pincode' => $customer->pincode ?? null,
// 'notes' => null,
// ]);
// 4. Clone order items into invoice_items
// foreach ($order->items as $item) {
// \App\Models\InvoiceItem::create([
// 'invoice_id' => $invoice->id,
// 'description' => $item->description,
// 'ctn' => $item->ctn,
// 'qty' => $item->qty,
// 'ttl_qty' => $item->ttl_qty,
// 'unit' => $item->unit,
// 'price' => $item->price,
// 'ttl_amount' => $item->ttl_amount,
// 'cbm' => $item->cbm,
// 'ttl_cbm' => $item->ttl_cbm,
// 'kg' => $item->kg,
// 'ttl_kg' => $item->ttl_kg,
// 'shop_no' => $item->shop_no,
// ]);
// }
// 5. TODO: PDF generation (I will add this later)
// $invoice->pdf_path = null; // placeholder for now
// $invoice->save();
// =======================
// END INVOICE CREATION
// =======================
// CLEAR TEMP DATA
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
return redirect()->route('admin.orders.index') return redirect()->route('admin.orders.index')
->with('success', 'Order + Invoice created successfully.'); ->with('success', 'Order + Invoice created successfully.');
@@ -733,6 +746,7 @@ class AdminOrderController extends Controller
'shop_no' => 'nullable|string', 'shop_no' => 'nullable|string',
]); ]);
// ✅ BACKEND CALCULATION
$ctn = (float) ($request->ctn ?? 0); $ctn = (float) ($request->ctn ?? 0);
$qty = (float) ($request->qty ?? 0); $qty = (float) ($request->qty ?? 0);
$price = (float) ($request->price ?? 0); $price = (float) ($request->price ?? 0);
@@ -755,10 +769,49 @@ class AdminOrderController extends Controller
]); ]);
$this->recalcTotals($order); $this->recalcTotals($order);
$this->updateInvoiceFromOrder($order);
return back()->with('success', 'Item updated successfully'); return back()->with('success', 'Item updated successfully');
} }
private function updateInvoiceFromOrder(Order $order)
{
$invoice = Invoice::where('order_id', $order->id)->first();
if (!$invoice) {
return;
}
$invoice->final_amount = $order->ttl_amount;
$invoice->gst_percent = 0;
$invoice->gst_amount = 0;
$invoice->final_amount_with_gst = $order->ttl_amount;
$invoice->save();
InvoiceItem::where('invoice_id', $invoice->id)->delete();
foreach ($order->items as $item) {
InvoiceItem::create([
'invoice_id' => $invoice->id,
'description' => $item->description,
'ctn' => $item->ctn,
'qty' => $item->qty,
'ttl_qty' => $item->ttl_qty,
'unit' => $item->unit,
'price' => $item->price,
'ttl_amount' => $item->ttl_amount,
'cbm' => $item->cbm,
'ttl_cbm' => $item->ttl_cbm,
'kg' => $item->kg,
'ttl_kg' => $item->ttl_kg,
'shop_no' => $item->shop_no,
]);
}
}
public function uploadExcelPreview(Request $request) public function uploadExcelPreview(Request $request)
{ {
try { try {
@@ -786,4 +839,6 @@ class AdminOrderController extends Controller
], 500); ], 500);
} }
} }
} }

View File

@@ -3,199 +3,54 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Order;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
use Mpdf\Mpdf;
class AdminReportController extends Controller class AdminReportController extends Controller
{ {
// UI साठी main action /**
public function containerReport(Request $request) * Display the reports page with joined data
*/
public function index(Request $request)
{ {
$reports = $this->buildContainerReportQuery($request)->get(); // -------------------------------
// FETCH REPORT DATA
// ONLY orders that have BOTH:
// 1. Invoice
// 2. Shipment
// -------------------------------
$reports = DB::table('orders')
->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')); return view('admin.reports', compact('reports'));
} }
// ही common query — filters accept करते
protected function buildContainerReportQuery(Request $request = null)
{
$query = DB::table('invoices')
->join('containers', 'containers.id', '=', 'invoices.container_id')
->leftJoinSub(
DB::table('invoice_installments')
->select('invoice_id', DB::raw('COALESCE(SUM(amount), 0) as total_paid'))
->groupBy('invoice_id'),
'inst',
'inst.invoice_id',
'=',
'invoices.id'
)
->select(
'containers.id as container_id',
'containers.container_number',
'containers.container_date',
'containers.container_name',
DB::raw('COUNT(DISTINCT invoices.mark_no) as total_mark_nos'),
DB::raw('COUNT(DISTINCT invoices.customer_id) as total_customers'),
DB::raw('COUNT(invoices.id) as total_invoices'),
DB::raw('COALESCE(SUM(invoices.charge_groups_total), 0) as total_invoice_amount'),
DB::raw('COALESCE(SUM(invoices.gst_amount), 0) as total_gst_amount'),
DB::raw('COALESCE(SUM(invoices.grand_total_with_charges), 0) as total_payable'),
DB::raw('COALESCE(SUM(inst.total_paid), 0) as total_paid'),
DB::raw('GREATEST(0, COALESCE(SUM(invoices.grand_total_with_charges), 0) - COALESCE(SUM(inst.total_paid), 0)) as total_remaining'),
DB::raw("
CASE
WHEN COUNT(invoices.id) > 0
AND SUM(CASE WHEN invoices.status != 'paid' THEN 1 ELSE 0 END) = 0
THEN 'paid'
WHEN SUM(CASE WHEN invoices.status = 'overdue' THEN 1 ELSE 0 END) > 0
THEN 'overdue'
WHEN SUM(CASE WHEN invoices.status = 'paying' THEN 1 ELSE 0 END) > 0
THEN 'paying'
ELSE 'pending'
END as container_status
")
)
->groupBy(
'containers.id',
'containers.container_number',
'containers.container_date',
'containers.container_name'
)
->orderBy('containers.container_date', 'desc')
->orderBy('containers.id', 'desc');
// ── Filters ──────────────────────────────────────────────────────
if ($request) {
if ($request->filled('from_date')) {
$query->whereDate('containers.container_date', '>=', $request->from_date);
}
if ($request->filled('to_date')) {
$query->whereDate('containers.container_date', '<=', $request->to_date);
}
if ($request->filled('status')) {
// container_status हे aggregate expression आहे,
// त्यामुळे HAVING clause वापरतो
$query->havingRaw("
CASE
WHEN COUNT(invoices.id) > 0
AND SUM(CASE WHEN invoices.status != 'paid' THEN 1 ELSE 0 END) = 0
THEN 'paid'
WHEN SUM(CASE WHEN invoices.status = 'overdue' THEN 1 ELSE 0 END) > 0
THEN 'overdue'
WHEN SUM(CASE WHEN invoices.status = 'paying' THEN 1 ELSE 0 END) > 0
THEN 'paying'
ELSE 'pending'
END = ?
", [$request->status]);
}
}
return $query;
}
// ---- Excel export ----
public function containerReportExcel(Request $request)
{
$reports = $this->buildContainerReportQuery($request)->get();
$headings = [
'Container No',
'Container Date',
'Total Mark Nos',
'Total Customers',
'Total Invoices',
'Invoice Amount (Before GST)',
'GST Amount',
'Payable Amount (Incl. GST)',
'Paid Amount',
'Remaining Amount',
'Container Status',
];
$rows = $reports->map(function ($r) {
return [
$r->container_number,
$r->container_date,
$r->total_mark_nos,
$r->total_customers,
$r->total_invoices,
$r->total_invoice_amount,
$r->total_gst_amount,
$r->total_payable,
$r->total_paid,
$r->total_remaining,
$r->container_status,
];
})->toArray();
$export = new class($headings, $rows) implements
\Maatwebsite\Excel\Concerns\FromArray,
\Maatwebsite\Excel\Concerns\WithHeadings
{
private $headings;
private $rows;
public function __construct($headings, $rows)
{
$this->headings = $headings;
$this->rows = $rows;
}
public function array(): array
{
return $this->rows;
}
public function headings(): array
{
return $this->headings;
}
};
return Excel::download(
$export,
'container-report-' . now()->format('Ymd-His') . '.xlsx'
);
}
// ---- PDF export ----
public function containerReportPdf(Request $request)
{
$reports = $this->buildContainerReportQuery($request)->get();
$html = view('admin.reports', [
'reports' => $reports,
'isPdf' => true,
])->render();
$mpdf = new \Mpdf\Mpdf([
'mode' => 'utf-8',
'format' => 'A4-L',
'default_font' => 'dejavusans',
'margin_top' => 8,
'margin_right' => 8,
'margin_bottom' => 10,
'margin_left' => 8,
]);
$mpdf->SetHTMLHeader('');
$mpdf->SetHTMLFooter('');
$mpdf->WriteHTML($html);
$fileName = 'container-report-' . now()->format('Ymd-His') . '.pdf';
return response($mpdf->Output($fileName, 'S'), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
]);
}
} }

View File

@@ -85,6 +85,7 @@ public function approveProfileUpdate($id)
$req = \App\Models\UpdateRequest::findOrFail($id); $req = \App\Models\UpdateRequest::findOrFail($id);
$user = \App\Models\User::findOrFail($req->user_id); $user = \App\Models\User::findOrFail($req->user_id);
// FIX: Ensure data is array
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true); $newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
foreach ($newData as $key => $value) { foreach ($newData as $key => $value) {
@@ -95,18 +96,8 @@ public function approveProfileUpdate($id)
} }
} }
// Update user table
$user->save(); $user->save();
// Update mark_list table
\App\Models\MarkList::where('customer_id', $user->customer_id)
->update([
'customer_name' => $user->customer_name,
'company_name' => $user->company_name,
'mobile_no' => $user->mobile_no
]);
// Update request status
$req->status = 'approved'; $req->status = 'approved';
$req->admin_note = 'Approved by admin on ' . now(); $req->admin_note = 'Approved by admin on ' . now();
$req->save(); $req->save();

View File

@@ -9,9 +9,6 @@ use App\Models\Invoice;
use App\Models\InvoiceItem; use App\Models\InvoiceItem;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
use Carbon\Carbon;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Storage;
class ContainerController extends Controller class ContainerController extends Controller
{ {
@@ -193,7 +190,6 @@ class ContainerController extends Controller
'kg_col' => null, 'kg_col' => null,
'totalkg_col' => null, 'totalkg_col' => null,
'itemno_col' => null, 'itemno_col' => null,
'shopno_col' => null,
]; ];
foreach ($header as $colIndex => $headingText) { foreach ($header as $colIndex => $headingText) {
@@ -234,11 +230,6 @@ class ContainerController extends Controller
strpos($normalized, 'ITEM') !== false strpos($normalized, 'ITEM') !== false
) { ) {
$essentialColumns['itemno_col'] = $colIndex; $essentialColumns['itemno_col'] = $colIndex;
} elseif (
strpos($normalized, 'SHOPNO') !== false ||
strpos($normalized, 'SHOP') !== false
) {
$essentialColumns['shopno_col'] = $colIndex;
} }
} }
@@ -282,96 +273,7 @@ class ContainerController extends Controller
]; ];
} }
if (empty($cleanedRows)) { // MARK CHECK: strict - collect ALL marks + unmatched rows
return back()
->withErrors(['excel_file' => 'No valid item rows found in Excel.'])
->withInput();
}
// FORMULA CHECK
$cleanNumber = function ($value) {
if (is_string($value)) {
$value = str_replace(',', '', trim($value));
}
return is_numeric($value) ? (float)$value : 0;
};
$formulaErrors = [];
foreach ($cleanedRows as $item) {
$row = $item['row'];
$offset = $item['offset'];
$ctn = $essentialColumns['ctn_col'] !== null ? $cleanNumber($row[$essentialColumns['ctn_col']] ?? 0) : 0;
$qty = $essentialColumns['qty_col'] !== null ? $cleanNumber($row[$essentialColumns['qty_col']] ?? 0) : 0;
$ttlQ = $essentialColumns['totalqty_col'] !== null ? $cleanNumber($row[$essentialColumns['totalqty_col']] ?? 0) : 0;
$cbm = $essentialColumns['cbm_col'] !== null ? $cleanNumber($row[$essentialColumns['cbm_col']] ?? 0) : 0;
$ttlC = $essentialColumns['totalcbm_col'] !== null ? $cleanNumber($row[$essentialColumns['totalcbm_col']] ?? 0) : 0;
$kg = $essentialColumns['kg_col'] !== null ? $cleanNumber($row[$essentialColumns['kg_col']] ?? 0) : 0;
$ttlK = $essentialColumns['totalkg_col'] !== null ? $cleanNumber($row[$essentialColumns['totalkg_col']] ?? 0) : 0;
$price = $essentialColumns['price_col'] !== null ? $cleanNumber($row[$essentialColumns['price_col']] ?? 0) : 0;
$ttlAmount = $essentialColumns['amount_col'] !== null ? $cleanNumber($row[$essentialColumns['amount_col']] ?? 0) : 0;
$desc = $essentialColumns['desc_col'] !== null ? (string)($row[$essentialColumns['desc_col']] ?? '') : '';
$mark = $essentialColumns['itemno_col'] !== null ? (string)($row[$essentialColumns['itemno_col']] ?? '') : '';
$expTtlQty = $qty * $ctn;
$expTtlCbm = $cbm * $ctn;
$expTtlKg = $kg * $ctn;
$expTtlAmount = ($qty * $ctn) * $price;
$rowErrors = [];
if (abs($ttlQ - $expTtlQty) > 0.01) {
$rowErrors['TOTAL QTY'] = [
'actual' => $ttlQ,
'expected' => $expTtlQty,
];
}
if (abs($ttlC - $expTtlCbm) > 0.0005) {
$rowErrors['TOTAL CBM'] = [
'actual' => $ttlC,
'expected' => $expTtlCbm,
];
}
if (abs($ttlK - $expTtlKg) > 0.01) {
$rowErrors['TOTAL KG'] = [
'actual' => $ttlK,
'expected' => $expTtlKg,
];
}
if ($essentialColumns['amount_col'] !== null && $essentialColumns['price_col'] !== null) {
if (abs($ttlAmount - $expTtlAmount) > 0.01) {
$rowErrors['TOTAL AMOUNT'] = [
'actual' => $ttlAmount,
'expected' => $expTtlAmount,
];
}
}
if (!empty($rowErrors)) {
$rowData = [];
foreach ($header as $colIndex => $headingText) {
$value = $row[$colIndex] ?? null;
if (is_string($value)) $value = trim($value);
$rowData[$headingText] = $value;
}
$formulaErrors[] = [
'excel_row' => $headerRowIndex + 1 + $offset,
'mark_no' => $mark,
'description' => $desc,
'errors' => $rowErrors,
'data' => $rowData,
];
}
}
// MARK CHECK
$marksFromExcel = []; $marksFromExcel = [];
foreach ($cleanedRows as $item) { foreach ($cleanedRows as $item) {
$row = $item['row']; $row = $item['row'];
@@ -397,8 +299,6 @@ class ContainerController extends Controller
$unmatchedMarks = array_values(array_diff($marksFromExcel, $validMarks)); $unmatchedMarks = array_values(array_diff($marksFromExcel, $validMarks));
$markErrors = [];
if (!empty($unmatchedMarks)) { if (!empty($unmatchedMarks)) {
foreach ($cleanedRows as $item) { foreach ($cleanedRows as $item) {
$row = $item['row']; $row = $item['row'];
@@ -416,24 +316,21 @@ class ContainerController extends Controller
$rowData[$headingText] = $value; $rowData[$headingText] = $value;
} }
$markErrors[] = [ $unmatchedRowsData[] = [
'excel_row' => $headerRowIndex + 1 + $offset, 'excel_row' => $headerRowIndex + 1 + $offset,
'mark_no' => $rowMark, 'mark_no' => $rowMark,
'data' => $rowData, 'data' => $rowData,
]; ];
} }
}
if (!empty($formulaErrors) || !empty($markErrors)) {
return back() return back()
->withErrors(['excel_file' => 'Some mark numbers are not found in Mark List. Container not created.'])
->withInput() ->withInput()
->with([ ->with('unmatched_rows', $unmatchedRowsData);
'formula_errors' => $formulaErrors,
'mark_errors' => $markErrors,
]);
} }
// STEP 1: Marks → customers mapping + grouping // STEP 1: Marks → customers mapping + grouping
$markRecords = MarkList::whereIn('mark_no', $marksFromExcel) $markRecords = MarkList::whereIn('mark_no', $marksFromExcel)
->where('status', 'active') ->where('status', 'active')
->get(); ->get();
@@ -481,6 +378,7 @@ class ContainerController extends Controller
} }
// STEP 2: Container + ContainerRows save // STEP 2: Container + ContainerRows save
$container = Container::create([ $container = Container::create([
'container_name' => $request->container_name, 'container_name' => $request->container_name,
'container_number' => $request->container_number, 'container_number' => $request->container_number,
@@ -514,6 +412,7 @@ class ContainerController extends Controller
} }
// STEP 3: per-customer invoices + invoice items // STEP 3: per-customer invoices + invoice items
$invoiceCount = 0; $invoiceCount = 0;
foreach ($groupedByCustomer as $customerId => $rowsForCustomer) { foreach ($groupedByCustomer as $customerId => $rowsForCustomer) {
@@ -524,20 +423,12 @@ class ContainerController extends Controller
$firstMark = $rowsForCustomer[0]['mark']; $firstMark = $rowsForCustomer[0]['mark'];
$snap = $markToSnapshot[$firstMark] ?? null; $snap = $markToSnapshot[$firstMark] ?? null;
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
$invoice = new Invoice(); $invoice = new Invoice();
$invoice->container_id = $container->id; $invoice->container_id = $container->id;
$invoice->customer_id = $customerUser->id ?? null; // $invoice->customer_id = $customerId;
$invoice->mark_no = $firstMark;
$invoice->invoice_number = $this->generateInvoiceNumber(); $invoice->invoice_number = $this->generateInvoiceNumber();
$invoice->invoice_date = now()->toDateString();
$invoice->invoice_date = $container->container_date; $invoice->due_date = null;
$invoice->due_date = Carbon::parse($invoice->invoice_date)
->addDays(10)
->format('Y-m-d');
if ($snap) { if ($snap) {
$invoice->customer_name = $snap['customer_name'] ?? null; $invoice->customer_name = $snap['customer_name'] ?? null;
@@ -545,30 +436,29 @@ class ContainerController extends Controller
$invoice->customer_mobile = $snap['mobile_no'] ?? null; $invoice->customer_mobile = $snap['mobile_no'] ?? null;
} }
if ($customerUser) {
$invoice->customer_email = $customerUser->email ?? null;
$invoice->customer_address = $customerUser->address ?? null;
$invoice->pincode = $customerUser->pincode ?? null;
}
$invoice->final_amount = 0; $invoice->final_amount = 0;
$invoice->gst_percent = 0; $invoice->gst_percent = 0;
$invoice->gst_amount = 0; $invoice->gst_amount = 0;
$invoice->final_amount_with_gst = 0; $invoice->final_amount_with_gst = 0;
$invoice->customer_email = null;
$invoice->customer_address = null;
$invoice->pincode = null;
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark')); $uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
$invoice->notes = 'Auto-created from Container ' . $container->container_number $invoice->notes = 'Auto-created from Container ' . $container->container_number
. ' for Mark(s): ' . implode(', ', $uniqueMarks); . ' for Mark(s): ' . implode(', ', $uniqueMarks);
$invoice->pdf_path = null; $invoice->pdf_path = null;
$invoice->status = 'pending'; $invoice->status = 'pending';
$invoice->save(); $invoice->save();
$invoiceCount++; $invoiceCount++;
$totalAmount = 0;
foreach ($rowsForCustomer as $item) { foreach ($rowsForCustomer as $item) {
$row = $item['row']; $row = $item['row'];
$offset = $item['offset'];
$mark = $item['mark']; // ✅ mark_no from Excel
$description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null; $description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
$ctn = $essentialColumns['ctn_col'] !== null ? (int)($row[$essentialColumns['ctn_col']] ?? 0) : 0; $ctn = $essentialColumns['ctn_col'] !== null ? (int)($row[$essentialColumns['ctn_col']] ?? 0) : 0;
@@ -582,14 +472,8 @@ class ContainerController extends Controller
$kg = $essentialColumns['kg_col'] !== null ? (float)($row[$essentialColumns['kg_col']] ?? 0) : 0; $kg = $essentialColumns['kg_col'] !== null ? (float)($row[$essentialColumns['kg_col']] ?? 0) : 0;
$ttlKg = $essentialColumns['totalkg_col'] !== null ? (float)($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg; $ttlKg = $essentialColumns['totalkg_col'] !== null ? (float)($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg;
$shopNo = $essentialColumns['shopno_col'] !== null ? ($row[$essentialColumns['shopno_col']] ?? null) : null;
$rowIndex = $headerRowIndex + 1 + $offset;
InvoiceItem::create([ InvoiceItem::create([
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'container_id' => $container->id,
'container_row_index' => $rowIndex,
'description'=> $description, 'description'=> $description,
'ctn' => $ctn, 'ctn' => $ctn,
'qty' => $qty, 'qty' => $qty,
@@ -601,10 +485,18 @@ class ContainerController extends Controller
'ttl_cbm' => $ttlCbm, 'ttl_cbm' => $ttlCbm,
'kg' => $kg, 'kg' => $kg,
'ttl_kg' => $ttlKg, 'ttl_kg' => $ttlKg,
'shop_no' => $shopNo, 'shop_no' => null,
'mark_no' => $mark, // ✅ save mark_no from Excel
]); ]);
$totalAmount += $ttlAmount;
} }
$invoice->final_amount = $totalAmount;
$invoice->gst_percent = 0;
$invoice->gst_amount = 0;
$invoice->final_amount_with_gst = $totalAmount;
$invoice->save();
} }
$msg = "Container '{$container->container_number}' created with {$savedCount} rows and {$invoiceCount} customer invoice(s)."; $msg = "Container '{$container->container_number}' created with {$savedCount} rows and {$invoiceCount} customer invoice(s).";
@@ -614,17 +506,7 @@ class ContainerController extends Controller
public function show(Container $container) public function show(Container $container)
{ {
$container->load('rows'); $container->load('rows');
return view('admin.container_show', compact('container'));
$lockedRowIndexes = \App\Models\Invoice::whereIn('invoices.status', ['paid', 'paying'])
->where('invoices.container_id', $container->id)
->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id')
->pluck('invoice_items.container_row_index')
->filter()
->unique()
->values()
->toArray();
return view('admin.container_show', compact('container', 'lockedRowIndexes'));
} }
public function updateRows(Request $request, Container $container) public function updateRows(Request $request, Container $container)
@@ -636,170 +518,17 @@ class ContainerController extends Controller
->where('id', $rowId) ->where('id', $rowId)
->first(); ->first();
if (!$row) { if (!$row) continue;
continue;
}
$data = $row->data ?? []; $data = $row->data ?? [];
foreach ($cols as $colHeader => $value) { foreach ($cols as $colHeader => $value) {
$data[$colHeader] = $value; $data[$colHeader] = $value;
} }
$row->update(['data' => $data]);
$normalizedMap = []; $row->update([
foreach ($data as $key => $value) { 'data' => $data,
if ($key === null || $key === '') { ]);
continue;
}
$normKey = strtoupper((string)$key);
$normKey = str_replace([' ', '/', '-', '.'], '', $normKey);
$normalizedMap[$normKey] = $value;
}
$getFirstNumeric = function (array $map, array $possibleKeys) {
foreach ($possibleKeys as $search) {
$normSearch = strtoupper($search);
$normSearch = str_replace([' ', '/', '-', '.'], '', $normSearch);
foreach ($map as $nKey => $value) {
if (strpos($nKey, $normSearch) !== false) {
if (is_numeric($value)) {
return (float)$value;
}
if (is_string($value) && is_numeric(trim($value))) {
return (float)trim($value);
}
}
}
}
return 0;
};
$ctnKeys = ['CTN', 'CTNS'];
$qtyKeys = ['QTY', 'PCS', 'PIECES'];
$ttlQtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY'];
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
$amountKeys = ['AMOUNT', 'TTLAMOUNT', 'TOTALAMOUNT'];
$ctn = $getFirstNumeric($normalizedMap, $ctnKeys);
$qty = $getFirstNumeric($normalizedMap, $qtyKeys);
$ttlQ = $getFirstNumeric($normalizedMap, $ttlQtyKeys);
if ($ttlQ == 0 && $ctn && $qty) {
$ttlQ = $ctn * $qty;
}
$cbm = $getFirstNumeric($normalizedMap, ['CBM']);
$ttlC = $getFirstNumeric($normalizedMap, ['TOTALCBM', 'TTLCBM', 'ITLCBM']);
if ($ttlC == 0 && $cbm && $ctn) {
$ttlC = $cbm * $ctn;
}
$kg = $getFirstNumeric($normalizedMap, ['KG', 'WEIGHT']);
$ttlK = $getFirstNumeric($normalizedMap, ['TOTALKG', 'TTKG']);
if ($ttlK == 0 && $kg && $ctn) {
$ttlK = $kg * $ctn;
}
$price = $getFirstNumeric($normalizedMap, ['PRICE', 'RATE']);
$amount = $getFirstNumeric($normalizedMap, $amountKeys);
if ($amount == 0 && $price && $ttlQ) {
$amount = $price * $ttlQ;
}
$desc = null;
foreach (['DESCRIPTION', 'DESC'] as $dKey) {
$normD = str_replace([' ', '/', '-', '.'], '', strtoupper($dKey));
foreach ($normalizedMap as $nKey => $v) {
if (strpos($nKey, $normD) !== false) {
$desc = is_string($v) ? trim($v) : $v;
break 2;
}
}
}
$shopNo = null;
foreach (['SHOPNO', 'SHOP'] as $sKey) {
$normS = str_replace([' ', '/', '-', '.'], '', strtoupper($sKey));
foreach ($normalizedMap as $nKey => $v) {
if (strpos($nKey, $normS) !== false) {
$shopNo = is_string($v) ? trim($v) : $v;
break 2;
}
}
}
// ✅ Get mark_no
$markNo = null;
foreach (['MARKNO', 'MARK', 'ITEMNO', 'ITEM'] as $mKey) {
$normM = str_replace([' ', '/', '-', '.'], '', strtoupper($mKey));
foreach ($normalizedMap as $nKey => $v) {
if (strpos($nKey, $normM) !== false) {
$markNo = is_string($v) ? trim($v) : $v;
break 2;
}
}
}
$rowIndex = $row->row_index;
$items = InvoiceItem::where('container_id', $container->id)
->where('container_row_index', $rowIndex)
->get();
if ($items->isEmpty() && $desc) {
$items = InvoiceItem::where('container_id', $container->id)
->whereNull('container_row_index')
->where('description', $desc)
->get();
}
foreach ($items as $item) {
$item->description = $desc;
$item->ctn = $ctn;
$item->qty = $qty;
$item->ttl_qty = $ttlQ;
$item->price = $price;
$item->ttl_amount = $amount;
$item->cbm = $cbm;
$item->ttl_cbm = $ttlC;
$item->kg = $kg;
$item->ttl_kg = $ttlK;
$item->shop_no = $shopNo;
$item->mark_no = $markNo; // ✅ update mark_no
$item->save();
$invoice = $item->invoice;
if ($invoice) {
$newBaseAmount = InvoiceItem::where('invoice_id', $invoice->id)
->sum('ttl_amount');
$taxType = $invoice->tax_type;
$cgstPercent = (float) ($invoice->cgst_percent ?? 0);
$sgstPercent = (float) ($invoice->sgst_percent ?? 0);
$igstPercent = (float) ($invoice->igst_percent ?? 0);
$gstPercent = 0;
if ($taxType === 'gst') {
$gstPercent = $cgstPercent + $sgstPercent;
} elseif ($taxType === 'igst') {
$gstPercent = $igstPercent;
}
$gstAmount = $newBaseAmount * $gstPercent / 100;
$finalWithGst = $newBaseAmount + $gstAmount;
$invoice->final_amount = $newBaseAmount;
$invoice->gst_amount = $gstAmount;
$invoice->final_amount_with_gst = $finalWithGst;
$invoice->gst_percent = $gstPercent;
$invoice->save();
}
}
} }
return redirect() return redirect()
@@ -809,37 +538,17 @@ class ContainerController extends Controller
public function updateStatus(Request $request, Container $container) public function updateStatus(Request $request, Container $container)
{ {
$request->validate([ $request->validate(['status' => 'required|in:pending,in-progress,completed,cancelled']);
'status' => 'required|in:container-ready,export-custom,international-transit,arrived-at-india,import-custom,warehouse,domestic-distribution,out-for-delivery,delivered',
]);
$container->status = $request->status; $container->update(['status' => $request->status]);
$container->save();
if ($request->wantsJson() || $request->ajax()) { return redirect()->route('containers.index')->with('success', 'Status updated.');
return response()->json([
'success' => true,
'status' => $container->status,
]);
}
return back()->with('success', 'Container status updated.');
} }
public function destroy(Container $container) public function destroy(Container $container)
{ {
$container->delete(); $container->delete();
return redirect()->route('containers.index')->with('success', 'Container deleted.');
if (request()->wantsJson() || request()->ajax()) {
return response()->json([
'success' => true,
'message' => 'Container deleted',
]);
}
return redirect()
->route('containers.index')
->with('success', 'Container deleted.');
} }
private function generateInvoiceNumber(): string private function generateInvoiceNumber(): string
@@ -865,100 +574,4 @@ class ContainerController extends Controller
return 'INV-' . $year . '-' . str_pad($nextSeq, 6, '0', STR_PAD_LEFT); return 'INV-' . $year . '-' . str_pad($nextSeq, 6, '0', STR_PAD_LEFT);
} }
public function downloadPdf(Container $container)
{
$container->load('rows');
$pdf = Pdf::loadView('admin.container_pdf', [
'container' => $container,
])->setPaper('a4', 'landscape');
$fileName = 'container-'.$container->container_number.'.pdf';
return $pdf->download($fileName);
}
public function downloadExcel(Container $container)
{
if (!$container->excel_file) {
abort(404, 'Excel file not found on record.');
}
$path = $container->excel_file;
if (!Storage::exists($path)) {
abort(404, 'Excel file missing on server.');
}
$fileName = 'container-'.$container->container_number.'.xlsx';
return Storage::download($path, $fileName);
}
public function popupPopup(Container $container)
{
$container->load('rows');
$rows = $container->rows ?? collect();
$totalCtn = 0;
$totalQty = 0;
$totalCbm = 0;
$totalKg = 0;
$ctnKeys = ['CTN', 'CTNS'];
$qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES'];
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
$getFirstNumeric = function (array $data, array $possibleKeys) {
$normalizedMap = [];
foreach ($data as $key => $value) {
if ($key === null) continue;
$normKey = strtoupper((string)$key);
$normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey);
$normalizedMap[$normKey] = $value;
}
foreach ($possibleKeys as $search) {
$normSearch = strtoupper($search);
$normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch);
foreach ($normalizedMap as $nKey => $value) {
if (strpos($nKey, $normSearch) !== false) {
if (is_numeric($value)) {
return (float)$value;
}
if (is_string($value) && is_numeric(trim($value))) {
return (float)trim($value);
}
}
}
}
return 0;
};
foreach ($rows as $row) {
$data = $row->data ?? [];
if (!is_array($data)) continue;
$totalCtn += $getFirstNumeric($data, $ctnKeys);
$totalQty += $getFirstNumeric($data, $qtyKeys);
$totalCbm += $getFirstNumeric($data, $cbmKeys);
$totalKg += $getFirstNumeric($data, $kgKeys);
}
$summary = [
'total_ctn' => round($totalCtn, 2),
'total_qty' => round($totalQty, 2),
'total_cbm' => round($totalCbm, 3),
'total_kg' => round($totalKg, 2),
];
return view('admin.partials.container_popup_readonly', [
'container' => $container,
'summary' => $summary,
]);
}
} }

View File

@@ -67,9 +67,12 @@ class ChatController extends Controller
'sender_id' => auth()->id(), 'sender_id' => auth()->id(),
'sender_type' => \App\Models\User::class, 'sender_type' => \App\Models\User::class,
'message' => $request->message, 'message' => $request->message,
<<<<<<< HEAD
=======
'client_id' => $request->client_id, // ✅ ADD 'client_id' => $request->client_id, // ✅ ADD
'read_by_admin' => false, 'read_by_admin' => false,
'read_by_user' => true, 'read_by_user' => true,
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
]; ];
// Handle file upload // Handle file upload
@@ -86,7 +89,11 @@ class ChatController extends Controller
$message->load('sender'); $message->load('sender');
// Fire real-time event // Fire real-time event
<<<<<<< HEAD
broadcast(new NewChatMessage($message))->toOthers();
=======
broadcast(new NewChatMessage($message)); broadcast(new NewChatMessage($message));
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
return response()->json([ return response()->json([
'success' => true, 'success' => true,

View File

@@ -21,33 +21,23 @@ class UserOrderController extends Controller
} }
// ------------------------------------- // -------------------------------------
// Get customer invoices with containers // Get all orders
// ------------------------------------- // -------------------------------------
$invoices = $user->invoices()->with('container')->get(); $orders = $user->orders()->with('invoice')->get();
// Unique containers for this customer
$containers = $invoices->pluck('container')->filter()->unique('id');
// ------------------------------------- // -------------------------------------
// Counts based on container status // Counts
// ------------------------------------- // -------------------------------------
$totalOrders = $containers->count(); $totalOrders = $orders->count();
$delivered = $orders->where('status', 'delivered')->count();
$delivered = $containers->where('status', 'delivered')->count(); $inTransit = $orders->where('status', '!=', 'delivered')->count();
$inTransit = $containers->whereNotIn('status', [
'delivered',
'warehouse',
'domestic-distribution'
])->count();
$active = $totalOrders; $active = $totalOrders;
// ------------------------------------- // -------------------------------------
// Total Amount = sum of invoice totals // Total Amount = Invoice.total_with_gst
// ------------------------------------- // -------------------------------------
$totalAmount = $invoices->sum(function ($invoice) { $totalAmount = $orders->sum(function ($o) {
return $invoice->final_amount_with_gst ?? 0; return $o->invoice->final_amount_with_gst ?? 0;
}); });
// Format total amount in K, L, Cr // Format total amount in K, L, Cr
@@ -55,12 +45,13 @@ class UserOrderController extends Controller
return response()->json([ return response()->json([
'status' => true, 'status' => true,
'summary' => [ 'summary' => [
'active_orders' => $active, 'active_orders' => $active,
'in_transit_orders' => $inTransit, 'in_transit_orders' => $inTransit,
'delivered_orders' => $delivered, 'delivered_orders' => $delivered,
'total_value' => $formattedAmount, 'total_value' => $formattedAmount, // formatted value
'total_raw' => $totalAmount 'total_raw' => $totalAmount // original value
] ]
]); ]);
} }
@@ -99,26 +90,18 @@ class UserOrderController extends Controller
], 401); ], 401);
} }
// Get invoices with containers for this customer // Fetch orders for this user
$invoices = $user->invoices() $orders = $user->orders()
->with('container') ->with(['invoice', 'shipments'])
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->get(); ->get()
->map(function ($o) {
// Extract unique containers
$containers = $invoices->pluck('container')
->filter()
->unique('id')
->values();
$orders = $containers->map(function ($container) {
return [ return [
'order_id' => $container->id, 'order_id' => $o->order_id,
'container_number' => $container->container_number, 'status' => $o->status,
'status' => $container->status, 'amount' => $o->ttl_amount,
'container_date' => $container->container_date, 'description'=> "Order from {$o->origin} to {$o->destination}",
'created_at' => $container->created_at, 'created_at' => $o->created_at,
]; ];
}); });
@@ -132,73 +115,45 @@ public function orderDetails($order_id)
{ {
$user = JWTAuth::parseToken()->authenticate(); $user = JWTAuth::parseToken()->authenticate();
if (!$user) { $order = $user->orders()
return response()->json([
'success' => false,
'message' => 'Unauthorized'
], 401);
}
// Find container first
$container = \App\Models\Container::find($order_id);
if (!$container) {
return response()->json([
'success' => false,
'message' => 'Container not found'
], 404);
}
// Find invoice belonging to this user for this container
$invoice = \App\Models\Invoice::where('customer_id', $user->id)
->where('container_id', $container->id)
->with(['items']) ->with(['items'])
->where('order_id', $order_id)
->first(); ->first();
if (!$invoice) { if (!$order) {
return response()->json([ return response()->json(['success' => false, 'message' => 'Order not found'], 404);
'success' => false,
'message' => 'Order not found for this user'
], 404);
} }
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'order' => [ 'order' => $order
'container_id' => $container->id,
'container_number' => $container->container_number,
'container_date' => $container->container_date,
'status' => $container->status,
'invoice_id' => $invoice->id,
'items' => $invoice->items
]
]); ]);
} }
// public function orderShipment($order_id) public function orderShipment($order_id)
// { {
// $user = JWTAuth::parseToken()->authenticate(); $user = JWTAuth::parseToken()->authenticate();
// // Get order // Get order
// $order = $user->orders()->where('order_id', $order_id)->first(); $order = $user->orders()->where('order_id', $order_id)->first();
// if (!$order) { if (!$order) {
// return response()->json(['success' => false, 'message' => 'Order not found'], 404); return response()->json(['success' => false, 'message' => 'Order not found'], 404);
// } }
// // Find shipment only for this order // Find shipment only for this order
// $shipment = $order->shipments() $shipment = $order->shipments()
// ->with(['items' => function ($q) use ($order) { ->with(['items' => function ($q) use ($order) {
// $q->where('order_id', $order->id); $q->where('order_id', $order->id);
// }]) }])
// ->first(); ->first();
// return response()->json([ return response()->json([
// 'success' => true, 'success' => true,
// 'shipment' => $shipment 'shipment' => $shipment
// ]); ]);
// } }
public function orderInvoice($order_id) public function orderInvoice($order_id)
@@ -224,35 +179,23 @@ public function trackOrder($order_id)
{ {
$user = JWTAuth::parseToken()->authenticate(); $user = JWTAuth::parseToken()->authenticate();
if (!$user) { $order = $user->orders()
return response()->json([ ->with('shipments')
'success' => false, ->where('order_id', $order_id)
'message' => 'Unauthorized'
], 401);
}
// Ensure the container belongs to this customer via invoice
$invoice = \App\Models\Invoice::where('customer_id', $user->id)
->where('container_id', $order_id)
->with('container')
->first(); ->first();
if (!$invoice || !$invoice->container) { if (!$order) {
return response()->json([ return response()->json(['success' => false, 'message' => 'Order not found'], 404);
'success' => false,
'message' => 'Order not found'
], 404);
} }
$container = $invoice->container; $shipment = $order->shipments()->first();
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'track' => [ 'track' => [
'order_id' => $container->id, 'order_id' => $order->order_id,
'container_number' => $container->container_number, 'shipment_status' => $shipment->status ?? 'pending',
'status' => $container->status, 'shipment_date' => $shipment->shipment_date ?? null,
'container_date' => $container->container_date,
] ]
]); ]);
} }
@@ -346,44 +289,44 @@ public function invoiceDetails($invoice_id)
]); ]);
} }
// public function confirmOrder($order_id) public function confirmOrder($order_id)
// { {
// $user = JWTAuth::parseToken()->authenticate(); $user = JWTAuth::parseToken()->authenticate();
// if (! $user) { if (! $user) {
// return response()->json([ return response()->json([
// 'success' => false, 'success' => false,
// 'message' => 'Unauthorized' 'message' => 'Unauthorized'
// ], 401); ], 401);
// } }
// $order = $user->orders() $order = $user->orders()
// ->where('order_id', $order_id) ->where('order_id', $order_id)
// ->first(); ->first();
// if (! $order) { if (! $order) {
// return response()->json([ return response()->json([
// 'success' => false, 'success' => false,
// 'message' => 'Order not found' 'message' => 'Order not found'
// ], 404); ], 404);
// } }
// // 🚫 Only allow confirm from order_placed // 🚫 Only allow confirm from order_placed
// if ($order->status !== 'order_placed') { if ($order->status !== 'order_placed') {
// return response()->json([ return response()->json([
// 'success' => false, 'success' => false,
// 'message' => 'Order cannot be confirmed' 'message' => 'Order cannot be confirmed'
// ], 422); ], 422);
// } }
// $order->status = 'order_confirmed'; $order->status = 'order_confirmed';
// $order->save(); $order->save();
// return response()->json([ return response()->json([
// 'success' => true, 'success' => true,
// 'message' => 'Order confirmed successfully' 'message' => 'Order confirmed successfully'
// ]); ]);
// } }

View File

@@ -10,6 +10,15 @@ class ChatMessage extends Model
use HasFactory; use HasFactory;
protected $fillable = [ protected $fillable = [
<<<<<<< HEAD
'ticket_id',
'sender_id',
'sender_type', // user OR admin
'message',
'file_path',
'file_type',
];
=======
'ticket_id', 'ticket_id',
'sender_id', 'sender_id',
'sender_type', 'sender_type',
@@ -20,6 +29,9 @@ class ChatMessage extends Model
'read_by_user', 'read_by_user',
'client_id', 'client_id',
]; ];
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
/** /**
* The ticket this message belongs to. * The ticket this message belongs to.
*/ */

View File

@@ -9,6 +9,7 @@ class Invoice extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [ protected $fillable = [
'container_id', 'container_id',
'customer_id', 'customer_id',
@@ -31,13 +32,6 @@ class Invoice extends Model
'pincode', 'pincode',
'pdf_path', 'pdf_path',
'notes', 'notes',
// totals from charge groups
'charge_groups_total',
'grand_total_with_charges',
'tax_type',
'cgst_percent',
'sgst_percent',
'igst_percent',
]; ];
/**************************** /****************************
@@ -49,9 +43,16 @@ class Invoice extends Model
return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC'); return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC');
} }
// public function container() // NEW: invoice आता container वर depend
public function container()
{
return $this->belongsTo(Container::class);
}
// OLD: order() relation काढले आहे
// public function order()
// { // {
// return $this->belongsTo(Container::class); // return $this->belongsTo(Order::class);
// } // }
public function customer() public function customer()
@@ -64,16 +65,11 @@ class Invoice extends Model
return $this->hasMany(InvoiceInstallment::class); return $this->hasMany(InvoiceInstallment::class);
} }
public function chargeGroups()
{
return $this->hasMany(InvoiceChargeGroup::class, 'invoice_id');
}
/**************************** /****************************
* Helper Functions * Helper Functions
****************************/ ****************************/
// (Items based calculateTotals वापरणार नाहीस तरी ठेवू शकतोस) // Auto calculate GST fields (you can call this in controller before saving)
public function calculateTotals() public function calculateTotals()
{ {
$gst = ($this->final_amount * $this->gst_percent) / 100; $gst = ($this->final_amount * $this->gst_percent) / 100;
@@ -81,53 +77,16 @@ class Invoice extends Model
$this->final_amount_with_gst = $this->final_amount + $gst; $this->final_amount_with_gst = $this->final_amount + $gst;
} }
// Check overdue status condition
public function isOverdue() public function isOverdue()
{ {
return $this->status === 'pending' && now()->gt($this->due_date); return $this->status === 'pending' && now()->gt($this->due_date);
} }
// जर पुढे container → shipment relation असेल तर हा helper नंतर adjust करू
public function getShipment() public function getShipment()
{ {
// आधी order वरून shipment घेत होत; container flow मध्ये नंतर गरज पडल्यास बदलू
return null; return null;
} }
// ✅ Charge groups base total (WITHOUT GST)
public function getChargeGroupsTotalAttribute()
{
// base = total_charge sum
return (float) $this->chargeGroups->sum('total_charge');
}
// ✅ Grand total: Charge groups base + GST (items ignore)
public function getGrandTotalWithChargesAttribute()
{
$base = (float) ($this->charge_groups_total ?? 0);
$gst = (float) ($this->gst_amount ?? 0);
return $base + $gst;
}
public function totalPaid(): float
{
return (float) $this->installments()->sum('amount');
}
public function remainingAmount(): float
{
$grand = (float) $this->grand_total_with_charges;
$paid = (float) $this->totalPaid();
return max(0, $grand - $paid);
}
public function isLockedForEdit(): bool
{
return $this->status === 'paid';
}
public function container()
{
return $this->belongsTo(\App\Models\Container::class, 'container_id');
}
} }

View File

@@ -1,31 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class InvoiceChargeGroup extends Model
{
protected $fillable = [
'invoice_id',
'group_name',
'basis_type',
'basis_value',
'rate',
'total_charge',
'tax_type',
'gst_percent',
'total_with_gst',
];
public function invoice()
{
return $this->belongsTo(Invoice::class);
}
public function items()
{
return $this->hasMany(InvoiceChargeGroupItem::class, 'group_id');
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class InvoiceChargeGroupItem extends Model
{
protected $fillable = [
'group_id',
'invoice_item_id',
];
public function group()
{
return $this->belongsTo(InvoiceChargeGroup::class, 'group_id');
}
public function item()
{
return $this->belongsTo(InvoiceItem::class, 'invoice_item_id');
}
}

View File

@@ -11,8 +11,6 @@ class InvoiceItem extends Model
protected $fillable = [ protected $fillable = [
'invoice_id', 'invoice_id',
'container_id', // Container mapping
'container_row_index', // Container row index
'description', 'description',
'ctn', 'ctn',
@@ -29,7 +27,6 @@ class InvoiceItem extends Model
'ttl_kg', 'ttl_kg',
'shop_no', 'shop_no',
'mark_no',
]; ];
/**************************** /****************************
@@ -40,79 +37,4 @@ class InvoiceItem extends Model
{ {
return $this->belongsTo(Invoice::class); return $this->belongsTo(Invoice::class);
} }
public function chargeGroupItems()
{
return $this->hasMany(InvoiceChargeGroupItem::class, 'invoice_item_id');
}
// हे helper: पहिला group fetch करून त्यावरून rate/total काढणे
public function getChargeRateAttribute()
{
$pivot = $this->chargeGroupItems->first();
if (!$pivot || !$pivot->group) {
return 0;
}
$group = $pivot->group;
// basis नुसार या item चा basis value
$basis = 0;
switch ($group->basis_type) {
case 'ttl_qty':
$basis = $this->ttl_qty;
break;
case 'amount':
$basis = $this->ttl_amount;
break;
case 'ttl_cbm':
$basis = $this->ttl_cbm;
break;
case 'ttl_kg':
$basis = $this->ttl_kg;
break;
}
if ($basis <= 0 || ($group->basis_value ?? 0) <= 0) {
return 0;
}
// group चा rate field आधीच आहे, ते direct वापरू
return (float) $group->rate;
}
public function getChargeTotalAttribute()
{
$pivot = $this->chargeGroupItems->first();
if (!$pivot || !$pivot->group) {
return 0;
}
$group = $pivot->group;
$basis = 0;
switch ($group->basis_type) {
case 'ttl_qty':
$basis = $this->ttl_qty;
break;
case 'amount':
$basis = $this->ttl_amount;
break;
case 'ttl_cbm':
$basis = $this->ttl_cbm;
break;
case 'ttl_kg':
$basis = $this->ttl_kg;
break;
}
if ($basis <= 0 || ($group->basis_value ?? 0) <= 0) {
return 0;
}
// per unit rate
$rate = (float) $group->rate;
// item total = basis * rate
return $basis * $rate;
}
} }

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class LoadingListItem extends Model
{
protected $fillable = [
'container_id',
'mark',
'description',
'ctn',
'qty',
'total_qty',
'unit',
'price',
'cbm',
'total_cbm',
'kg',
'total_kg',
];
public function container()
{
return $this->belongsTo(Container::class);
}
}

View File

@@ -58,10 +58,10 @@ class Order extends Model
return $this->belongsToMany(\App\Models\Shipment::class, 'shipment_items', 'order_id', 'shipment_id'); return $this->belongsToMany(\App\Models\Shipment::class, 'shipment_items', 'order_id', 'shipment_id');
} }
// public function invoice() public function invoice()
// { {
// return $this->hasOne(\App\Models\Invoice::class, 'order_id', 'id'); return $this->hasOne(\App\Models\Invoice::class, 'order_id', 'id');
// } }
const STATUS_LABELS = [ const STATUS_LABELS = [

View File

@@ -89,8 +89,10 @@ class User extends Authenticatable implements JWTSubject
{ {
return []; return [];
} }
public function invoices()
{
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
}
// App\Models\User.php // App\Models\User.php
@@ -105,10 +107,6 @@ public function invoiceInstallments()
); );
} }
public function invoices()
{
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
}
} }

View File

@@ -85,7 +85,8 @@
"allow-plugins": { "allow-plugins": {
"pestphp/pest-plugin": true, "pestphp/pest-plugin": true,
"php-http/discovery": true "php-http/discovery": true
} },
"platform-check": false
}, },
"minimum-stability": "stable", "minimum-stability": "stable",
"prefer-stable": true "prefer-stable": true

65
composer.lock generated
View File

@@ -2880,6 +2880,18 @@
}, },
{ {
"name": "maennchen/zipstream-php", "name": "maennchen/zipstream-php",
<<<<<<< HEAD
"version": "3.2.1",
"source": {
"type": "git",
"url": "https://github.com/maennchen/ZipStream-PHP.git",
"reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5",
"reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5",
=======
"version": "3.1.2", "version": "3.1.2",
"source": { "source": {
"type": "git", "type": "git",
@@ -2890,21 +2902,34 @@
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f",
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f",
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-zlib": "*", "ext-zlib": "*",
<<<<<<< HEAD
"php-64bit": "^8.3"
=======
"php-64bit": "^8.2" "php-64bit": "^8.2"
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
}, },
"require-dev": { "require-dev": {
"brianium/paratest": "^7.7", "brianium/paratest": "^7.7",
"ext-zip": "*", "ext-zip": "*",
<<<<<<< HEAD
"friendsofphp/php-cs-fixer": "^3.86",
"guzzlehttp/guzzle": "^7.5",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5",
"phpunit/phpunit": "^12.0",
=======
"friendsofphp/php-cs-fixer": "^3.16", "friendsofphp/php-cs-fixer": "^3.16",
"guzzlehttp/guzzle": "^7.5", "guzzlehttp/guzzle": "^7.5",
"mikey179/vfsstream": "^1.6", "mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5", "php-coveralls/php-coveralls": "^2.5",
"phpunit/phpunit": "^11.0", "phpunit/phpunit": "^11.0",
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
"vimeo/psalm": "^6.0" "vimeo/psalm": "^6.0"
}, },
"suggest": { "suggest": {
@@ -2946,7 +2971,11 @@
], ],
"support": { "support": {
"issues": "https://github.com/maennchen/ZipStream-PHP/issues", "issues": "https://github.com/maennchen/ZipStream-PHP/issues",
<<<<<<< HEAD
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1"
=======
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2"
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
}, },
"funding": [ "funding": [
{ {
@@ -2954,7 +2983,11 @@
"type": "github" "type": "github"
} }
], ],
<<<<<<< HEAD
"time": "2025-12-10T09:58:31+00:00"
=======
"time": "2025-01-27T12:07:53+00:00" "time": "2025-01-27T12:07:53+00:00"
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
}, },
{ {
"name": "markbaker/complex", "name": "markbaker/complex",
@@ -4181,6 +4214,18 @@
}, },
{ {
"name": "phpoffice/phpspreadsheet", "name": "phpoffice/phpspreadsheet",
<<<<<<< HEAD
"version": "1.30.2",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/09cdde5e2f078b9a3358dd217e2c8cb4dac84be2",
"reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2",
=======
"version": "1.30.1", "version": "1.30.1",
"source": { "source": {
"type": "git", "type": "git",
@@ -4191,6 +4236,7 @@
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c", "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c",
"reference": "fa8257a579ec623473eabfe49731de5967306c4c", "reference": "fa8257a579ec623473eabfe49731de5967306c4c",
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -4213,12 +4259,19 @@
"markbaker/complex": "^3.0", "markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0", "markbaker/matrix": "^3.0",
"php": ">=7.4.0 <8.5.0", "php": ">=7.4.0 <8.5.0",
<<<<<<< HEAD
=======
"psr/http-client": "^1.0", "psr/http-client": "^1.0",
"psr/http-factory": "^1.0", "psr/http-factory": "^1.0",
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0" "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
}, },
"require-dev": { "require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main", "dealerdirect/phpcodesniffer-composer-installer": "dev-main",
<<<<<<< HEAD
"doctrine/instantiator": "^1.5",
=======
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0", "dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.2", "friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3", "mitoteam/jpgraph": "^10.3",
@@ -4265,6 +4318,12 @@
}, },
{ {
"name": "Adrien Crivelli" "name": "Adrien Crivelli"
<<<<<<< HEAD
},
{
"name": "Owen Leibman"
=======
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
} }
], ],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
@@ -4281,9 +4340,15 @@
], ],
"support": { "support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
<<<<<<< HEAD
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.2"
},
"time": "2026-01-11T05:58:24+00:00"
=======
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1" "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1"
}, },
"time": "2025-10-26T16:01:04+00:00" "time": "2025-10-26T16:01:04+00:00"
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
}, },
{ {
"name": "phpoption/phpoption", "name": "phpoption/phpoption",

View File

@@ -6,11 +6,6 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Name | Application Name
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
|
| This value is the name of your application, which will be used when the
| framework needs to place the application's name in a notification or
| other UI elements where an application name needs to be displayed.
|
*/ */
'name' => env('APP_NAME', 'Laravel'), 'name' => env('APP_NAME', 'Laravel'),
@@ -19,11 +14,6 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Environment | Application Environment
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/ */
'env' => env('APP_ENV', 'production'), 'env' => env('APP_ENV', 'production'),
@@ -32,11 +22,6 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Debug Mode | Application Debug Mode
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/ */
'debug' => (bool) env('APP_DEBUG', false), 'debug' => (bool) env('APP_DEBUG', false),
@@ -45,11 +30,6 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application URL | Application URL
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| the application so that it's available within Artisan commands.
|
*/ */
'url' => env('APP_URL', 'http://localhost'), 'url' => env('APP_URL', 'http://localhost'),
@@ -58,11 +38,6 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Timezone | Application Timezone
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. The timezone
| is set to "UTC" by default as it is suitable for most use cases.
|
*/ */
'timezone' => 'UTC', 'timezone' => 'UTC',
@@ -71,11 +46,6 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Locale Configuration | Application Locale Configuration
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by Laravel's translation / localization methods. This option can be
| set to any locale for which you plan to have translation strings.
|
*/ */
'locale' => env('APP_LOCALE', 'en'), 'locale' => env('APP_LOCALE', 'en'),
@@ -88,11 +58,6 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Encryption Key | Encryption Key
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
|
| This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string to ensure that all encrypted values
| are secure. You should do this prior to deploying the application.
|
*/ */
'cipher' => 'AES-256-CBC', 'cipher' => 'AES-256-CBC',
@@ -109,13 +74,6 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Maintenance Mode Driver | Maintenance Mode Driver
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/ */
'maintenance' => [ 'maintenance' => [
@@ -123,4 +81,53 @@ return [
'store' => env('APP_MAINTENANCE_STORE', 'database'), 'store' => env('APP_MAINTENANCE_STORE', 'database'),
], ],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
*/
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Http' => Illuminate\Support\Facades\Http::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
// ✅ LaravelExcel facade
'Excel' => Maatwebsite\Excel\Facades\Excel::class,
],
]; ];

380
config/excel.php Normal file
View File

@@ -0,0 +1,380 @@
<?php
use Maatwebsite\Excel\Excel;
use PhpOffice\PhpSpreadsheet\Reader\Csv;
return [
'exports' => [
/*
|--------------------------------------------------------------------------
| Chunk size
|--------------------------------------------------------------------------
|
| When using FromQuery, the query is automatically chunked.
| Here you can specify how big the chunk should be.
|
*/
'chunk_size' => 1000,
/*
|--------------------------------------------------------------------------
| Pre-calculate formulas during export
|--------------------------------------------------------------------------
*/
'pre_calculate_formulas' => false,
/*
|--------------------------------------------------------------------------
| Enable strict null comparison
|--------------------------------------------------------------------------
|
| When enabling strict null comparison empty cells ('') will
| be added to the sheet.
*/
'strict_null_comparison' => false,
/*
|--------------------------------------------------------------------------
| CSV Settings
|--------------------------------------------------------------------------
|
| Configure e.g. delimiter, enclosure and line ending for CSV exports.
|
*/
'csv' => [
'delimiter' => ',',
'enclosure' => '"',
'line_ending' => PHP_EOL,
'use_bom' => false,
'include_separator_line' => false,
'excel_compatibility' => false,
'output_encoding' => '',
'test_auto_detect' => true,
],
/*
|--------------------------------------------------------------------------
| Worksheet properties
|--------------------------------------------------------------------------
|
| Configure e.g. default title, creator, subject,...
|
*/
'properties' => [
'creator' => '',
'lastModifiedBy' => '',
'title' => '',
'description' => '',
'subject' => '',
'keywords' => '',
'category' => '',
'manager' => '',
'company' => '',
],
],
'imports' => [
/*
|--------------------------------------------------------------------------
| Read Only
|--------------------------------------------------------------------------
|
| When dealing with imports, you might only be interested in the
| data that the sheet exists. By default we ignore all styles,
| however if you want to do some logic based on style data
| you can enable it by setting read_only to false.
|
*/
'read_only' => true,
/*
|--------------------------------------------------------------------------
| Ignore Empty
|--------------------------------------------------------------------------
|
| When dealing with imports, you might be interested in ignoring
| rows that have null values or empty strings. By default rows
| containing empty strings or empty values are not ignored but can be
| ignored by enabling the setting ignore_empty to true.
|
*/
'ignore_empty' => false,
/*
|--------------------------------------------------------------------------
| Heading Row Formatter
|--------------------------------------------------------------------------
|
| Configure the heading row formatter.
| Available options: none|slug|custom
|
*/
'heading_row' => [
'formatter' => 'slug',
],
/*
|--------------------------------------------------------------------------
| CSV Settings
|--------------------------------------------------------------------------
|
| Configure e.g. delimiter, enclosure and line ending for CSV imports.
|
*/
'csv' => [
'delimiter' => null,
'enclosure' => '"',
'escape_character' => '\\',
'contiguous' => false,
'input_encoding' => Csv::GUESS_ENCODING,
],
/*
|--------------------------------------------------------------------------
| Worksheet properties
|--------------------------------------------------------------------------
|
| Configure e.g. default title, creator, subject,...
|
*/
'properties' => [
'creator' => '',
'lastModifiedBy' => '',
'title' => '',
'description' => '',
'subject' => '',
'keywords' => '',
'category' => '',
'manager' => '',
'company' => '',
],
/*
|--------------------------------------------------------------------------
| Cell Middleware
|--------------------------------------------------------------------------
|
| Configure middleware that is executed on getting a cell value
|
*/
'cells' => [
'middleware' => [
//\Maatwebsite\Excel\Middleware\TrimCellValue::class,
//\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class,
],
],
],
/*
|--------------------------------------------------------------------------
| Extension detector
|--------------------------------------------------------------------------
|
| Configure here which writer/reader type should be used when the package
| needs to guess the correct type based on the extension alone.
|
*/
'extension_detector' => [
'xlsx' => Excel::XLSX,
'xlsm' => Excel::XLSX,
'xltx' => Excel::XLSX,
'xltm' => Excel::XLSX,
'xls' => Excel::XLS,
'xlt' => Excel::XLS,
'ods' => Excel::ODS,
'ots' => Excel::ODS,
'slk' => Excel::SLK,
'xml' => Excel::XML,
'gnumeric' => Excel::GNUMERIC,
'htm' => Excel::HTML,
'html' => Excel::HTML,
'csv' => Excel::CSV,
'tsv' => Excel::TSV,
/*
|--------------------------------------------------------------------------
| PDF Extension
|--------------------------------------------------------------------------
|
| Configure here which Pdf driver should be used by default.
| Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF
|
*/
'pdf' => Excel::DOMPDF,
],
/*
|--------------------------------------------------------------------------
| Value Binder
|--------------------------------------------------------------------------
|
| PhpSpreadsheet offers a way to hook into the process of a value being
| written to a cell. In there some assumptions are made on how the
| value should be formatted. If you want to change those defaults,
| you can implement your own default value binder.
|
| Possible value binders:
|
| [x] Maatwebsite\Excel\DefaultValueBinder::class
| [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class
| [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class
|
*/
'value_binder' => [
'default' => Maatwebsite\Excel\DefaultValueBinder::class,
],
'cache' => [
/*
|--------------------------------------------------------------------------
| Default cell caching driver
|--------------------------------------------------------------------------
|
| By default PhpSpreadsheet keeps all cell values in memory, however when
| dealing with large files, this might result into memory issues. If you
| want to mitigate that, you can configure a cell caching driver here.
| When using the illuminate driver, it will store each value in the
| cache store. This can slow down the process, because it needs to
| store each value. You can use the "batch" store if you want to
| only persist to the store when the memory limit is reached.
|
| Drivers: memory|illuminate|batch
|
*/
'driver' => 'memory',
/*
|--------------------------------------------------------------------------
| Batch memory caching
|--------------------------------------------------------------------------
|
| When dealing with the "batch" caching driver, it will only
| persist to the store when the memory limit is reached.
| Here you can tweak the memory limit to your liking.
|
*/
'batch' => [
'memory_limit' => 60000,
],
/*
|--------------------------------------------------------------------------
| Illuminate cache
|--------------------------------------------------------------------------
|
| When using the "illuminate" caching driver, it will automatically use
| your default cache store. However if you prefer to have the cell
| cache on a separate store, you can configure the store name here.
| You can use any store defined in your cache config. When leaving
| at "null" it will use the default store.
|
*/
'illuminate' => [
'store' => null,
],
/*
|--------------------------------------------------------------------------
| Cache Time-to-live (TTL)
|--------------------------------------------------------------------------
|
| The TTL of items written to cache. If you want to keep the items cached
| indefinitely, set this to null. Otherwise, set a number of seconds,
| a \DateInterval, or a callable.
|
| Allowable types: callable|\DateInterval|int|null
|
*/
'default_ttl' => 10800,
],
/*
|--------------------------------------------------------------------------
| Transaction Handler
|--------------------------------------------------------------------------
|
| By default the import is wrapped in a transaction. This is useful
| for when an import may fail and you want to retry it. With the
| transactions, the previous import gets rolled-back.
|
| You can disable the transaction handler by setting this to null.
| Or you can choose a custom made transaction handler here.
|
| Supported handlers: null|db
|
*/
'transactions' => [
'handler' => 'db',
'db' => [
'connection' => null,
],
],
'temporary_files' => [
/*
|--------------------------------------------------------------------------
| Local Temporary Path
|--------------------------------------------------------------------------
|
| When exporting and importing files, we use a temporary file, before
| storing reading or downloading. Here you can customize that path.
| permissions is an array with the permission flags for the directory (dir)
| and the create file (file).
|
*/
'local_path' => storage_path('framework/cache/laravel-excel'),
/*
|--------------------------------------------------------------------------
| Local Temporary Path Permissions
|--------------------------------------------------------------------------
|
| Permissions is an array with the permission flags for the directory (dir)
| and the create file (file).
| If omitted the default permissions of the filesystem will be used.
|
*/
'local_permissions' => [
// 'dir' => 0755,
// 'file' => 0644,
],
/*
|--------------------------------------------------------------------------
| Remote Temporary Disk
|--------------------------------------------------------------------------
|
| When dealing with a multi server setup with queues in which you
| cannot rely on having a shared local temporary path, you might
| want to store the temporary file on a shared disk. During the
| queue executing, we'll retrieve the temporary file from that
| location instead. When left to null, it will always use
| the local path. This setting only has effect when using
| in conjunction with queued imports and exports.
|
*/
'remote_disk' => null,
'remote_prefix' => null,
/*
|--------------------------------------------------------------------------
| Force Resync
|--------------------------------------------------------------------------
|
| When dealing with a multi server setup as above, it's possible
| for the clean up that occurs after entire queue has been run to only
| cleanup the server that the last AfterImportJob runs on. The rest of the server
| would still have the local temporary file stored on it. In this case your
| local storage limits can be exceeded and future imports won't be processed.
| To mitigate this you can set this config value to be true, so that after every
| queued chunk is processed the local temporary file is deleted on the server that
| processed it.
|
*/
'force_resync_remote' => null,
],
];

View File

@@ -34,9 +34,6 @@ class CreateInvoiceItemsTable extends Migration
$table->timestamps(); $table->timestamps();
$table->unsignedBigInteger('container_id')->nullable()->after('invoice_id');
$table->integer('container_row_index')->nullable()->after('container_id');
// FK // FK
$table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); $table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
}); });
@@ -52,6 +49,4 @@ class CreateInvoiceItemsTable extends Migration
}); });
Schema::dropIfExists('invoice_items'); Schema::dropIfExists('invoice_items');
} }
} }

View File

@@ -9,7 +9,7 @@ return new class extends Migration
public function up(): void public function up(): void
{ {
Schema::table('containers', function (Blueprint $table) { Schema::table('containers', function (Blueprint $table) {
$table->string('status', 21) $table->string('status', 20)
->default('pending') ->default('pending')
->after('container_date'); ->after('container_date');
}); });

View File

@@ -1,37 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('invoice_charge_groups', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('invoice_id');
$table->string('group_name')->nullable(); // उदा. "FREIGHT", "HANDLING"
$table->enum('basis_type', ['ttl_qty', 'amount', 'ttl_cbm', 'ttl_kg']);
$table->decimal('basis_value', 15, 3)->default(0); // auto calculate केलेला total basis
$table->decimal('rate', 15, 3)->default(0); // per basis rate (helper)
$table->decimal('total_charge', 15, 2); // admin नी manually टाकलेला total
$table->timestamps();
$table->foreign('invoice_id')
->references('id')->on('invoices')
->onDelete('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('invoice_charge_groups');
}
};

View File

@@ -1,35 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('invoice_charge_group_items', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('group_id');
$table->unsignedBigInteger('invoice_item_id');
$table->timestamps();
$table->foreign('group_id')
->references('id')->on('invoice_charge_groups')
->onDelete('cascade');
$table->foreign('invoice_item_id')
->references('id')->on('invoice_items')
->onDelete('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('invoice_charge_group_items');
}
};

View File

@@ -1,27 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::table('invoice_items', function (Blueprint $table) {
$table->unsignedBigInteger('container_id')->nullable()->after('invoice_id');
$table->integer('container_row_index')->nullable()->after('container_id');
});
}
public function down()
{
Schema::table('invoice_items', function (Blueprint $table) {
$table->dropColumn(['container_id', 'container_row_index']);
});
}
};

View File

@@ -1,29 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('invoices', function (Blueprint $table) {
// column आधीच आहे का हे check करून, नसेल तरच add करायचा
if (!Schema::hasColumn('invoices', 'due_date')) {
$table->date('due_date')
->nullable()
->after('invoice_date');
}
});
}
public function down(): void
{
Schema::table('invoices', function (Blueprint $table) {
if (Schema::hasColumn('invoices', 'due_date')) {
$table->dropColumn('due_date');
}
});
}
};

View File

@@ -1,26 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('containers', function (Blueprint $table) {
$table->string('status', 50)->change();
});
}
public function down(): void
{
Schema::table('containers', function (Blueprint $table) {
$table->string('status', 20)->change();
});
}
};

View File

@@ -1,23 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('invoice_charge_groups', function (Blueprint $table) {
$table->string('tax_type')->nullable()->after('total_charge');
$table->decimal('gst_percent', 5, 2)->default(0)->after('tax_type');
$table->decimal('total_with_gst', 15, 2)->default(0)->after('gst_percent');
});
}
public function down(): void
{
Schema::table('invoice_charge_groups', function (Blueprint $table) {
$table->dropColumn(['tax_type', 'gst_percent', 'total_with_gst']);
});
}
};

View File

@@ -1,37 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddChargeColumnsToInvoicesTable extends Migration
{
public function up()
{
Schema::table('invoices', function (Blueprint $table) {
if (!Schema::hasColumn('invoices', 'charge_groups_total')) {
$table->decimal('charge_groups_total', 15, 2)
->nullable()
->after('final_amount_with_gst');
}
if (!Schema::hasColumn('invoices', 'grand_total_with_charges')) {
$table->decimal('grand_total_with_charges', 15, 2)
->nullable()
->after('charge_groups_total');
}
});
}
public function down()
{
Schema::table('invoices', function (Blueprint $table) {
if (Schema::hasColumn('invoices', 'charge_groups_total')) {
$table->dropColumn('charge_groups_total');
}
if (Schema::hasColumn('invoices', 'grand_total_with_charges')) {
$table->dropColumn('grand_total_with_charges');
}
});
}
}

View File

@@ -1,23 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration {
public function up(): void
{
DB::statement("
ALTER TABLE `invoices`
MODIFY `status` ENUM('pending','paying','paid','overdue')
NOT NULL DEFAULT 'pending'
");
}
public function down(): void
{
DB::statement("
ALTER TABLE `invoices`
MODIFY `status` ENUM('pending','paid','overdue')
NOT NULL DEFAULT 'pending'
");
}
};

View File

@@ -1,27 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::table('invoice_items', function (Blueprint $table) {
$table->string('mark_no')->nullable(); // after() काहीही नको
});
}
public function down()
{
Schema::table('invoice_items', function (Blueprint $table) {
$table->dropColumn('mark_no');
});
}
};

View File

@@ -25,12 +25,6 @@ class PermissionSeeder extends Seeder
// EXTRA (ORDERS) // EXTRA (ORDERS)
'orders.view', // you added this separately 'orders.view', // you added this separately
// CONTAINER
'container.view',
'container.create',
'container.update',
'container.delete',
// SHIPMENT // SHIPMENT
'shipment.view', 'shipment.view',
'shipment.create', 'shipment.create',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Some files were not shown because too many files have changed in this diff Show More