Compare commits
12 Commits
dev
...
8b6d3d5fad
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
8b6d3d5fad | ||
|
|
c89e5bdf7d | ||
|
|
10af713fa1 | ||
|
|
ebb263cd36 | ||
|
|
82d9c10130 | ||
|
|
cb24cf575b | ||
|
|
e4c07cb838 | ||
|
|
f38a5afdd7 | ||
|
|
9423c79c80 | ||
|
|
8a958b9c48 | ||
|
|
82882e859e | ||
|
|
2d28e7c1d5 |
14
.env.example
14
.env.example
@@ -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
|
||||||
|
|
||||||
@@ -20,12 +20,12 @@ LOG_STACK=single
|
|||||||
LOG_DEPRECATIONS_CHANNEL=null
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=sqlite
|
||||||
DB_HOST=127.0.0.1
|
# DB_HOST=127.0.0.1
|
||||||
DB_PORT=3306
|
# DB_PORT=3306
|
||||||
DB_DATABASE=kent_logistics6
|
# DB_DATABASE=laravel
|
||||||
DB_USERNAME=root
|
# DB_USERNAME=root
|
||||||
DB_PASSWORD=
|
# DB_PASSWORD=
|
||||||
|
|
||||||
SESSION_DRIVER=database
|
SESSION_DRIVER=database
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
|
|||||||
@@ -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'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -2,70 +2,38 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller; // ⬅️ हे नक्की असू दे
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
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 Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Mpdf\Mpdf;
|
use Mpdf\Mpdf;
|
||||||
|
use App\Models\InvoiceInstallment;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Barryvdh\DomPDF\Facade\Pdf;
|
||||||
|
|
||||||
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(['order.shipments'])->latest()->get();
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
|
||||||
$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', 'order', 'installments'])->findOrFail($id);
|
||||||
'items',
|
|
||||||
'chargeGroups.items',
|
|
||||||
])->findOrFail($id);
|
|
||||||
|
|
||||||
$shipment = null;
|
$shipment = \App\Models\Shipment::whereHas('items', function ($q) use ($invoice) {
|
||||||
|
$q->where('order_id', $invoice->order_id);
|
||||||
|
})->first();
|
||||||
|
|
||||||
$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,55 +41,27 @@ class AdminInvoiceController extends Controller
|
|||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function edit($id)
|
public function edit($id)
|
||||||
{
|
{
|
||||||
$invoice = Invoice::with([
|
$invoice = Invoice::with(['order.shipments'])->findOrFail($id);
|
||||||
'items',
|
$shipment = $invoice->order?->shipments?->first();
|
||||||
'customer',
|
|
||||||
'container',
|
|
||||||
'chargeGroups.items',
|
|
||||||
'installments',
|
|
||||||
])->findOrFail($id);
|
|
||||||
|
|
||||||
// ✅ Customer details sync
|
// ADD THIS SECTION: Calculate customer's total due across all invoices
|
||||||
if ($invoice->customer) {
|
$customerTotalDue = Invoice::where('customer_id', $invoice->customer_id)
|
||||||
$needsUpdate = [];
|
->where('status', '!=', 'cancelled')
|
||||||
|
->where('status', '!=', 'void')
|
||||||
|
->sum('final_amount_with_gst');
|
||||||
|
|
||||||
if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') {
|
// Pass the new variable to the view
|
||||||
$needsUpdate['customer_email'] = $invoice->customer->email;
|
return view('admin.invoice_edit', compact('invoice', 'shipment', 'customerTotalDue'));
|
||||||
}
|
|
||||||
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;
|
|
||||||
|
|
||||||
$groupedItemIds = $invoice->chargeGroups
|
|
||||||
->flatMap(function ($group) {
|
|
||||||
return $group->items->pluck('invoice_item_id');
|
|
||||||
})
|
|
||||||
->unique()
|
|
||||||
->values()
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
return view('admin.invoice_edit', compact('invoice', 'shipment', 'groupedItemIds'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// UPDATE INVOICE (HEADER ONLY)
|
// UPDATE INVOICE
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
Log::info('🟡 Invoice Update Request Received', [
|
Log::info("🟡 Invoice Update Request Received", [
|
||||||
'invoice_id' => $id,
|
'invoice_id' => $id,
|
||||||
'request' => $request->all(),
|
'request' => $request->all()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$invoice = Invoice::findOrFail($id);
|
$invoice = Invoice::findOrFail($id);
|
||||||
@@ -129,79 +69,69 @@ class AdminInvoiceController extends Controller
|
|||||||
$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);
|
$finalAmount = floatval($data['final_amount']);
|
||||||
$invoice->refresh();
|
$taxPercent = floatval($data['tax_percent']);
|
||||||
|
$taxAmount = 0;
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$taxAmount = ($finalAmount * $taxPercent) / 100;
|
||||||
|
|
||||||
|
$data['gst_amount'] = $taxAmount;
|
||||||
|
$data['final_amount_with_gst'] = $finalAmount + $taxAmount;
|
||||||
|
$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'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$invoice->update($data);
|
||||||
|
|
||||||
|
Log::info("✅ Invoice Updated Successfully", [
|
||||||
|
'invoice_id' => $invoice->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 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)
|
// PDF GENERATION USING mPDF
|
||||||
// -------------------------------------------------------------
|
|
||||||
public function updateItems(Request $request, Invoice $invoice)
|
|
||||||
{
|
|
||||||
Log::info('🟡 Invoice Items Update Request', [
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'payload' => $request->all(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$data = $request->validate([
|
|
||||||
'items' => ['required', 'array'],
|
|
||||||
'items.*.price' => ['required', 'numeric', 'min:0'],
|
|
||||||
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
foreach ($data['items'] as $itemId => $itemData) {
|
|
||||||
$item = InvoiceItem::where('id', $itemId)
|
|
||||||
->where('invoice_id', $invoice->id)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if (!$item) {
|
|
||||||
Log::warning('Invoice item not found or mismatched invoice', [
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'item_id' => $itemId,
|
|
||||||
]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$item->price = $itemData['price'];
|
|
||||||
$item->ttl_amount = $itemData['ttl_amount'];
|
|
||||||
$item->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info('✅ Invoice items updated (no totals recalculation)', [
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return back()->with('success', 'Invoice items updated successfully.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
|
||||||
// PDF GENERATION (EXISTING - केवळ chargeGroups load ला confirm कर)
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function generateInvoicePDF($invoice)
|
public function generateInvoicePDF($invoice)
|
||||||
{
|
{
|
||||||
// ✅ यामध्ये chargeGroups आणि installments load कर
|
$invoice->load(['items', 'order.shipments']);
|
||||||
$invoice->load(['items', 'customer', 'container', 'chargeGroups.items', 'installments']);
|
$shipment = $invoice->order?->shipments?->first();
|
||||||
$shipment = null;
|
|
||||||
|
|
||||||
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
|
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
|
||||||
$folder = public_path('invoices/');
|
$folder = public_path('invoices/');
|
||||||
|
|
||||||
@@ -210,30 +140,30 @@ class AdminInvoiceController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$filePath = $folder . $fileName;
|
$filePath = $folder . $fileName;
|
||||||
|
|
||||||
if (file_exists($filePath)) {
|
if (file_exists($filePath)) {
|
||||||
unlink($filePath);
|
unlink($filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
$mpdf = new Mpdf([
|
$mpdf = new Mpdf(['mode' => 'utf-8', 'format' => 'A4', 'default_font' => 'sans-serif']);
|
||||||
'mode' => 'utf-8',
|
$html = view('admin.pdf.invoice', ['invoice' => $invoice, 'shipment' => $shipment])->render();
|
||||||
'format' => 'A4',
|
|
||||||
'default_font' => 'sans-serif',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$html = view('admin.pdf.invoice', [
|
|
||||||
'invoice' => $invoice,
|
|
||||||
'shipment' => $shipment,
|
|
||||||
])->render();
|
|
||||||
|
|
||||||
$mpdf->WriteHTML($html);
|
$mpdf->WriteHTML($html);
|
||||||
$mpdf->Output($filePath, 'F');
|
$mpdf->Output($filePath, 'F');
|
||||||
|
|
||||||
$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/DELETE)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function storeInstallment(Request $request, $invoice_id)
|
public function storeInstallment(Request $request, $invoice_id)
|
||||||
{
|
{
|
||||||
@@ -246,15 +176,14 @@ 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;
|
// Use GST-inclusive total for all calculations/checks
|
||||||
|
$remaining = $invoice->final_amount_with_gst - $paidTotal;
|
||||||
|
|
||||||
if ($request->amount > $remaining) {
|
if ($request->amount > $remaining) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'error',
|
'status' => 'error',
|
||||||
'message' => 'Installment amount exceeds remaining balance.',
|
'message' => 'Installment amount exceeds remaining balance.'
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,238 +196,53 @@ 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());
|
// Mark as 'paid' if GST-inclusive total is cleared
|
||||||
|
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
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
|
||||||
// INSTALLMENTS (DELETE)
|
|
||||||
// -------------------------------------------------------------
|
|
||||||
public function deleteInstallment($id)
|
public function deleteInstallment($id)
|
||||||
{
|
{
|
||||||
$installment = InvoiceInstallment::findOrFail($id);
|
$installment = InvoiceInstallment::findOrFail($id);
|
||||||
$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());
|
// Update status if not fully paid anymore
|
||||||
|
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------
|
/* ---------------------------
|
||||||
@@ -160,7 +68,7 @@ class AdminOrderController extends Controller
|
|||||||
* ORDER ITEM MANAGEMENT (existing orders)
|
* ORDER ITEM MANAGEMENT (existing orders)
|
||||||
* ---------------------------*/
|
* ---------------------------*/
|
||||||
public function addItem(Request $request, $orderId)
|
public function addItem(Request $request, $orderId)
|
||||||
{
|
{
|
||||||
$order = Order::findOrFail($orderId);
|
$order = Order::findOrFail($orderId);
|
||||||
|
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
@@ -174,6 +82,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,9 +99,11 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -202,6 +113,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 +126,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.');
|
||||||
}
|
}
|
||||||
@@ -265,7 +178,7 @@ class AdminOrderController extends Controller
|
|||||||
'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
|
'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
|
||||||
'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
|
'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
|
||||||
'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
|
'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
|
||||||
'ttl_amount'=> (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
|
'ttl_amount' => (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
|
||||||
'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
|
'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
|
||||||
'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
|
'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
|
||||||
'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
|
'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
|
||||||
@@ -372,6 +285,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 +365,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 +398,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 +414,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 +427,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 +465,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 +529,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 +557,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 +577,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,14 +596,17 @@ class AdminOrderController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 7) invoice number
|
||||||
$invoiceNumber = $this->generateInvoiceNumber();
|
$invoiceNumber = $this->generateInvoiceNumber();
|
||||||
|
|
||||||
|
// 8) customer fetch
|
||||||
$markList = MarkList::where('mark_no', $order->mark_no)->first();
|
$markList = MarkList::where('mark_no', $order->mark_no)->first();
|
||||||
$customer = null;
|
$customer = null;
|
||||||
if ($markList && $markList->customer_id) {
|
if ($markList && $markList->customer_id) {
|
||||||
$customer = User::where('customer_id', $markList->customer_id)->first();
|
$customer = User::where('customer_id', $markList->customer_id)->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 9) invoice create
|
||||||
$invoice = Invoice::create([
|
$invoice = Invoice::create([
|
||||||
'order_id' => $order->id,
|
'order_id' => $order->id,
|
||||||
'customer_id' => $customer->id ?? null,
|
'customer_id' => $customer->id ?? null,
|
||||||
@@ -692,6 +631,7 @@ class AdminOrderController extends Controller
|
|||||||
'pdf_path' => null,
|
'pdf_path' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 10) invoice items
|
||||||
foreach ($order->items as $item) {
|
foreach ($order->items as $item) {
|
||||||
InvoiceItem::create([
|
InvoiceItem::create([
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
@@ -718,7 +658,7 @@ class AdminOrderController extends Controller
|
|||||||
* UPDATE ORDER ITEM (existing orders)
|
* UPDATE ORDER ITEM (existing orders)
|
||||||
* ---------------------------*/
|
* ---------------------------*/
|
||||||
public function updateItem(Request $request, $id)
|
public function updateItem(Request $request, $id)
|
||||||
{
|
{
|
||||||
$item = OrderItem::findOrFail($id);
|
$item = OrderItem::findOrFail($id);
|
||||||
$order = $item->order;
|
$order = $item->order;
|
||||||
|
|
||||||
@@ -733,6 +673,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,12 +696,51 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function uploadExcelPreview(Request $request)
|
$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)
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'excel' => 'required|file|mimes:xlsx,xls'
|
'excel' => 'required|file|mimes:xlsx,xls'
|
||||||
@@ -785,5 +765,7 @@ class AdminOrderController extends Controller
|
|||||||
'message' => 'Server error'
|
'message' => 'Server error'
|
||||||
], 500);
|
], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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 . '"',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -1,964 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Models\Container;
|
|
||||||
use App\Models\ContainerRow;
|
|
||||||
use App\Models\MarkList;
|
|
||||||
use App\Models\Invoice;
|
|
||||||
use App\Models\InvoiceItem;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Maatwebsite\Excel\Facades\Excel;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Barryvdh\DomPDF\Facade\Pdf;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
|
|
||||||
class ContainerController extends Controller
|
|
||||||
{
|
|
||||||
public function index()
|
|
||||||
{
|
|
||||||
$containers = Container::with('rows')->latest()->get();
|
|
||||||
|
|
||||||
$containers->each(function ($container) {
|
|
||||||
$rows = $container->rows;
|
|
||||||
|
|
||||||
$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 || $key === '') continue;
|
|
||||||
|
|
||||||
$normKey = strtoupper((string)$key);
|
|
||||||
$normKey = str_replace([' ', '/', '-', '.'], '', $normKey);
|
|
||||||
$normalizedMap[$normKey] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($possibleKeys as $search) {
|
|
||||||
$normSearch = strtoupper($search);
|
|
||||||
$normSearch = str_replace([' ', '/', '-', '.'], '', $normSearch);
|
|
||||||
|
|
||||||
foreach ($normalizedMap as $nKey => $value) {
|
|
||||||
if (
|
|
||||||
strpos($nKey, $normSearch) !== false &&
|
|
||||||
(is_numeric($value) || (is_string($value) && is_numeric(trim($value))))
|
|
||||||
) {
|
|
||||||
return (float) trim($value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$data = $row->data ?? [];
|
|
||||||
|
|
||||||
$totalCtn += $getFirstNumeric($data, $ctnKeys);
|
|
||||||
$totalQty += $getFirstNumeric($data, $qtyKeys);
|
|
||||||
$totalCbm += $getFirstNumeric($data, $cbmKeys);
|
|
||||||
$totalKg += $getFirstNumeric($data, $kgKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
$container->summary = [
|
|
||||||
'total_ctn' => round($totalCtn, 2),
|
|
||||||
'total_qty' => round($totalQty, 2),
|
|
||||||
'total_cbm' => round($totalCbm, 3),
|
|
||||||
'total_kg' => round($totalKg, 2),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
return view('admin.container', compact('containers'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create()
|
|
||||||
{
|
|
||||||
return view('admin.container_create');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isValidExcelFormat($rows, $header)
|
|
||||||
{
|
|
||||||
if (empty($header) || count($rows) < 2) return false;
|
|
||||||
|
|
||||||
$validKeywords = [
|
|
||||||
'MARK', 'DESCRIPTION', 'DESC', 'CTN', 'CTNS', 'QTY', 'TOTALQTY', 'ITLQTY', 'ITL QTY',
|
|
||||||
'UNIT', 'CBM', 'TOTAL CBM', 'KG', 'TOTAL KG',
|
|
||||||
'METAL BUCKLE', 'WATCH MOVEMENT', 'STEEL BOTTLE',
|
|
||||||
'MEHULPAID', 'ITEM NO', 'ITEM NO.', 'SAHILPAID', 'PINAKIN', 'GST',
|
|
||||||
'MOON LAMP', 'TRANSPARENT BOTTLE', 'PLASTIC FONDANT',
|
|
||||||
];
|
|
||||||
|
|
||||||
$headerText = implode(' ', array_map('strtoupper', $header));
|
|
||||||
$requiredHeaders = ['CTN', 'QTY', 'DESCRIPTION', 'DESC'];
|
|
||||||
|
|
||||||
$hasValidHeaders = false;
|
|
||||||
foreach ($requiredHeaders as $key) {
|
|
||||||
if (stripos($headerText, $key) !== false) {
|
|
||||||
$hasValidHeaders = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$hasValidHeaders) return false;
|
|
||||||
|
|
||||||
$dataPreview = '';
|
|
||||||
for ($i = 0; $i < min(5, count($rows)); $i++) {
|
|
||||||
$rowText = implode(' ', array_slice($rows[$i], 0, 10));
|
|
||||||
$dataPreview .= ' ' . strtoupper((string)$rowText);
|
|
||||||
}
|
|
||||||
|
|
||||||
$validMatches = 0;
|
|
||||||
foreach ($validKeywords as $keyword) {
|
|
||||||
if (stripos($headerText . $dataPreview, strtoupper($keyword)) !== false) {
|
|
||||||
$validMatches++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $validMatches >= 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function normalizeKey($value): string
|
|
||||||
{
|
|
||||||
$norm = strtoupper((string)$value);
|
|
||||||
return str_replace([' ', '/', '-', '.'], '', $norm);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function store(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'container_name' => 'required|string',
|
|
||||||
'container_number' => 'required|string|unique:containers,container_number',
|
|
||||||
'container_date' => 'required|date',
|
|
||||||
'excel_file' => 'required|file|mimes:xls,xlsx',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$file = $request->file('excel_file');
|
|
||||||
$sheets = Excel::toArray([], $file);
|
|
||||||
$rows = $sheets[0] ?? [];
|
|
||||||
|
|
||||||
if (count($rows) < 2) {
|
|
||||||
return back()
|
|
||||||
->withErrors(['excel_file' => 'Excel file is empty.'])
|
|
||||||
->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
// HEADER DETECTION
|
|
||||||
$headerRowIndex = null;
|
|
||||||
$header = [];
|
|
||||||
|
|
||||||
foreach ($rows as $i => $row) {
|
|
||||||
$trimmed = array_map(fn($v) => trim((string)$v), $row);
|
|
||||||
$nonEmpty = array_filter($trimmed, fn($v) => $v !== '');
|
|
||||||
if (empty($nonEmpty)) continue;
|
|
||||||
|
|
||||||
if (count($nonEmpty) >= 4) {
|
|
||||||
$headerRowIndex = $i;
|
|
||||||
$header = $trimmed;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($headerRowIndex === null) {
|
|
||||||
return back()
|
|
||||||
->withErrors(['excel_file' => 'Header row not found in Excel.'])
|
|
||||||
->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->isValidExcelFormat($rows, $header)) {
|
|
||||||
return back()
|
|
||||||
->withErrors(['excel_file' => 'Only MEHUL / SAHIL / PINAKIN / GST loading list formats allowed.'])
|
|
||||||
->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
// COLUMN INDEXES
|
|
||||||
$essentialColumns = [
|
|
||||||
'desc_col' => null,
|
|
||||||
'ctn_col' => null,
|
|
||||||
'qty_col' => null,
|
|
||||||
'totalqty_col' => null,
|
|
||||||
'unit_col' => null,
|
|
||||||
'price_col' => null,
|
|
||||||
'amount_col' => null,
|
|
||||||
'cbm_col' => null,
|
|
||||||
'totalcbm_col' => null,
|
|
||||||
'kg_col' => null,
|
|
||||||
'totalkg_col' => null,
|
|
||||||
'itemno_col' => null,
|
|
||||||
'shopno_col' => null,
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($header as $colIndex => $headingText) {
|
|
||||||
if (empty($headingText)) continue;
|
|
||||||
|
|
||||||
$normalized = $this->normalizeKey($headingText);
|
|
||||||
|
|
||||||
if (strpos($normalized, 'DESCRIPTION') !== false || strpos($normalized, 'DESC') !== false) {
|
|
||||||
$essentialColumns['desc_col'] = $colIndex;
|
|
||||||
} elseif (strpos($normalized, 'CTN') !== false || strpos($normalized, 'CTNS') !== false) {
|
|
||||||
$essentialColumns['ctn_col'] = $colIndex;
|
|
||||||
} elseif (
|
|
||||||
strpos($normalized, 'ITLQTY') !== false ||
|
|
||||||
strpos($normalized, 'TOTALQTY') !== false ||
|
|
||||||
strpos($normalized, 'TTLQTY') !== false
|
|
||||||
) {
|
|
||||||
$essentialColumns['totalqty_col'] = $colIndex;
|
|
||||||
} elseif (strpos($normalized, 'QTY') !== false) {
|
|
||||||
$essentialColumns['qty_col'] = $colIndex;
|
|
||||||
} elseif (strpos($normalized, 'UNIT') !== false) {
|
|
||||||
$essentialColumns['unit_col'] = $colIndex;
|
|
||||||
} elseif (strpos($normalized, 'PRICE') !== false) {
|
|
||||||
$essentialColumns['price_col'] = $colIndex;
|
|
||||||
} elseif (strpos($normalized, 'AMOUNT') !== false) {
|
|
||||||
$essentialColumns['amount_col'] = $colIndex;
|
|
||||||
} elseif (strpos($normalized, 'TOTALCBM') !== false || strpos($normalized, 'ITLCBM') !== false) {
|
|
||||||
$essentialColumns['totalcbm_col'] = $colIndex;
|
|
||||||
} elseif (strpos($normalized, 'CBM') !== false) {
|
|
||||||
$essentialColumns['cbm_col'] = $colIndex;
|
|
||||||
} elseif (strpos($normalized, 'TOTALKG') !== false || strpos($normalized, 'TTKG') !== false) {
|
|
||||||
$essentialColumns['totalkg_col'] = $colIndex;
|
|
||||||
} elseif (strpos($normalized, 'KG') !== false) {
|
|
||||||
$essentialColumns['kg_col'] = $colIndex;
|
|
||||||
} elseif (
|
|
||||||
strpos($normalized, 'MARKNO') !== false ||
|
|
||||||
strpos($normalized, 'MARK') !== false ||
|
|
||||||
strpos($normalized, 'ITEMNO') !== false ||
|
|
||||||
strpos($normalized, 'ITEM') !== false
|
|
||||||
) {
|
|
||||||
$essentialColumns['itemno_col'] = $colIndex;
|
|
||||||
} elseif (
|
|
||||||
strpos($normalized, 'SHOPNO') !== false ||
|
|
||||||
strpos($normalized, 'SHOP') !== false
|
|
||||||
) {
|
|
||||||
$essentialColumns['shopno_col'] = $colIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_null($essentialColumns['itemno_col'])) {
|
|
||||||
return back()
|
|
||||||
->withErrors(['excel_file' => 'Mark / Item column not found in Excel (expected headers like MARK NO / Mark_No / Item_No).'])
|
|
||||||
->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ROWS CLEANING
|
|
||||||
$dataRows = array_slice($rows, $headerRowIndex + 1);
|
|
||||||
$cleanedRows = [];
|
|
||||||
$unmatchedRowsData = [];
|
|
||||||
|
|
||||||
foreach ($dataRows as $offset => $row) {
|
|
||||||
$trimmedRow = array_map(fn($v) => trim((string)$v), $row);
|
|
||||||
$nonEmptyCells = array_filter($trimmedRow, fn($v) => $v !== '');
|
|
||||||
if (count($nonEmptyCells) < 2) continue;
|
|
||||||
|
|
||||||
$rowText = strtoupper(implode(' ', $trimmedRow));
|
|
||||||
if (
|
|
||||||
stripos($rowText, 'TOTAL') !== false ||
|
|
||||||
stripos($rowText, 'TTL') !== false ||
|
|
||||||
stripos($rowText, 'GRAND') !== false
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$descValue = '';
|
|
||||||
if ($essentialColumns['desc_col'] !== null) {
|
|
||||||
$descValue = trim($row[$essentialColumns['desc_col']] ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($essentialColumns['desc_col'] !== null && $descValue === '' && count($nonEmptyCells) >= 1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$cleanedRows[] = [
|
|
||||||
'row' => $row,
|
|
||||||
'offset' => $offset,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($cleanedRows)) {
|
|
||||||
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 = [];
|
|
||||||
foreach ($cleanedRows as $item) {
|
|
||||||
$row = $item['row'];
|
|
||||||
$rawMark = $row[$essentialColumns['itemno_col']] ?? null;
|
|
||||||
$mark = trim((string)($rawMark ?? ''));
|
|
||||||
if ($mark !== '') {
|
|
||||||
$marksFromExcel[] = $mark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$marksFromExcel = array_values(array_unique($marksFromExcel));
|
|
||||||
|
|
||||||
if (empty($marksFromExcel)) {
|
|
||||||
return back()
|
|
||||||
->withErrors(['excel_file' => 'No mark numbers found in Excel file.'])
|
|
||||||
->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
$validMarks = MarkList::whereIn('mark_no', $marksFromExcel)
|
|
||||||
->where('status', 'active')
|
|
||||||
->pluck('mark_no')
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
$unmatchedMarks = array_values(array_diff($marksFromExcel, $validMarks));
|
|
||||||
|
|
||||||
$markErrors = [];
|
|
||||||
|
|
||||||
if (!empty($unmatchedMarks)) {
|
|
||||||
foreach ($cleanedRows as $item) {
|
|
||||||
$row = $item['row'];
|
|
||||||
$offset = $item['offset'];
|
|
||||||
$rowMark = trim((string)($row[$essentialColumns['itemno_col']] ?? ''));
|
|
||||||
|
|
||||||
if ($rowMark === '' || !in_array($rowMark, $unmatchedMarks)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rowData = [];
|
|
||||||
foreach ($header as $colIndex => $headingText) {
|
|
||||||
$value = $row[$colIndex] ?? null;
|
|
||||||
if (is_string($value)) $value = trim($value);
|
|
||||||
$rowData[$headingText] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$markErrors[] = [
|
|
||||||
'excel_row' => $headerRowIndex + 1 + $offset,
|
|
||||||
'mark_no' => $rowMark,
|
|
||||||
'data' => $rowData,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($formulaErrors) || !empty($markErrors)) {
|
|
||||||
return back()
|
|
||||||
->withInput()
|
|
||||||
->with([
|
|
||||||
'formula_errors' => $formulaErrors,
|
|
||||||
'mark_errors' => $markErrors,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 1: Marks → customers mapping + grouping
|
|
||||||
$markRecords = MarkList::whereIn('mark_no', $marksFromExcel)
|
|
||||||
->where('status', 'active')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
$markToCustomerId = [];
|
|
||||||
$markToSnapshot = [];
|
|
||||||
|
|
||||||
foreach ($markRecords as $mr) {
|
|
||||||
$markToCustomerId[$mr->mark_no] = $mr->customer_id;
|
|
||||||
|
|
||||||
$markToSnapshot[$mr->mark_no] = [
|
|
||||||
'customer_name' => $mr->customer_name,
|
|
||||||
'company_name' => $mr->company_name,
|
|
||||||
'mobile_no' => $mr->mobile_no,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$groupedByCustomer = [];
|
|
||||||
|
|
||||||
foreach ($cleanedRows as $item) {
|
|
||||||
$row = $item['row'];
|
|
||||||
$offset = $item['offset'];
|
|
||||||
|
|
||||||
$rawMark = $row[$essentialColumns['itemno_col']] ?? null;
|
|
||||||
$mark = trim((string)($rawMark ?? ''));
|
|
||||||
|
|
||||||
if ($mark === '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$customerId = $markToCustomerId[$mark] ?? null;
|
|
||||||
if (!$customerId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($groupedByCustomer[$customerId])) {
|
|
||||||
$groupedByCustomer[$customerId] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$groupedByCustomer[$customerId][] = [
|
|
||||||
'row' => $row,
|
|
||||||
'offset' => $offset,
|
|
||||||
'mark' => $mark,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 2: Container + ContainerRows save
|
|
||||||
$container = Container::create([
|
|
||||||
'container_name' => $request->container_name,
|
|
||||||
'container_number' => $request->container_number,
|
|
||||||
'container_date' => $request->container_date,
|
|
||||||
'status' => 'pending',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$path = $file->store('containers');
|
|
||||||
$container->update(['excel_file' => $path]);
|
|
||||||
|
|
||||||
$savedCount = 0;
|
|
||||||
|
|
||||||
foreach ($cleanedRows as $item) {
|
|
||||||
$row = $item['row'];
|
|
||||||
$offset = $item['offset'];
|
|
||||||
|
|
||||||
$data = [];
|
|
||||||
foreach ($header as $colIndex => $headingText) {
|
|
||||||
$value = $row[$colIndex] ?? null;
|
|
||||||
if (is_string($value)) $value = trim($value);
|
|
||||||
$data[$headingText] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContainerRow::create([
|
|
||||||
'container_id' => $container->id,
|
|
||||||
'row_index' => $headerRowIndex + 1 + $offset,
|
|
||||||
'data' => $data,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$savedCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 3: per-customer invoices + invoice items
|
|
||||||
$invoiceCount = 0;
|
|
||||||
|
|
||||||
foreach ($groupedByCustomer as $customerId => $rowsForCustomer) {
|
|
||||||
if (empty($rowsForCustomer)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$firstMark = $rowsForCustomer[0]['mark'];
|
|
||||||
$snap = $markToSnapshot[$firstMark] ?? null;
|
|
||||||
|
|
||||||
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
|
|
||||||
|
|
||||||
$invoice = new Invoice();
|
|
||||||
$invoice->container_id = $container->id;
|
|
||||||
$invoice->customer_id = $customerUser->id ?? null;
|
|
||||||
$invoice->mark_no = $firstMark;
|
|
||||||
|
|
||||||
$invoice->invoice_number = $this->generateInvoiceNumber();
|
|
||||||
|
|
||||||
$invoice->invoice_date = $container->container_date;
|
|
||||||
|
|
||||||
$invoice->due_date = Carbon::parse($invoice->invoice_date)
|
|
||||||
->addDays(10)
|
|
||||||
->format('Y-m-d');
|
|
||||||
|
|
||||||
if ($snap) {
|
|
||||||
$invoice->customer_name = $snap['customer_name'] ?? null;
|
|
||||||
$invoice->company_name = $snap['company_name'] ?? 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->gst_percent = 0;
|
|
||||||
$invoice->gst_amount = 0;
|
|
||||||
$invoice->final_amount_with_gst = 0;
|
|
||||||
|
|
||||||
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
|
|
||||||
$invoice->notes = 'Auto-created from Container ' . $container->container_number
|
|
||||||
. ' for Mark(s): ' . implode(', ', $uniqueMarks);
|
|
||||||
$invoice->pdf_path = null;
|
|
||||||
$invoice->status = 'pending';
|
|
||||||
|
|
||||||
$invoice->save();
|
|
||||||
$invoiceCount++;
|
|
||||||
|
|
||||||
foreach ($rowsForCustomer as $item) {
|
|
||||||
$row = $item['row'];
|
|
||||||
$offset = $item['offset'];
|
|
||||||
$mark = $item['mark']; // ✅ mark_no from Excel
|
|
||||||
|
|
||||||
$description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
|
|
||||||
$ctn = $essentialColumns['ctn_col'] !== null ? (int) ($row[$essentialColumns['ctn_col']] ?? 0) : 0;
|
|
||||||
$qty = $essentialColumns['qty_col'] !== null ? (int) ($row[$essentialColumns['qty_col']] ?? 0) : 0;
|
|
||||||
$ttlQty = $essentialColumns['totalqty_col'] !== null ? (int) ($row[$essentialColumns['totalqty_col']] ?? 0) : $qty;
|
|
||||||
$unit = $essentialColumns['unit_col'] !== null ? ($row[$essentialColumns['unit_col']] ?? null) : null;
|
|
||||||
$price = $essentialColumns['price_col'] !== null ? (float) ($row[$essentialColumns['price_col']] ?? 0) : 0;
|
|
||||||
$ttlAmount = $essentialColumns['amount_col'] !== null ? (float) ($row[$essentialColumns['amount_col']] ?? 0) : 0;
|
|
||||||
$cbm = $essentialColumns['cbm_col'] !== null ? (float) ($row[$essentialColumns['cbm_col']] ?? 0) : 0;
|
|
||||||
$ttlCbm = $essentialColumns['totalcbm_col'] !== null ? (float) ($row[$essentialColumns['totalcbm_col']] ?? $cbm) : $cbm;
|
|
||||||
$kg = $essentialColumns['kg_col'] !== null ? (float) ($row[$essentialColumns['kg_col']] ?? 0) : 0;
|
|
||||||
$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([
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'container_id' => $container->id,
|
|
||||||
'container_row_index' => $rowIndex,
|
|
||||||
'description' => $description,
|
|
||||||
'ctn' => $ctn,
|
|
||||||
'qty' => $qty,
|
|
||||||
'ttl_qty' => $ttlQty,
|
|
||||||
'unit' => $unit,
|
|
||||||
'price' => $price,
|
|
||||||
'ttl_amount' => $ttlAmount,
|
|
||||||
'cbm' => $cbm,
|
|
||||||
'ttl_cbm' => $ttlCbm,
|
|
||||||
'kg' => $kg,
|
|
||||||
'ttl_kg' => $ttlKg,
|
|
||||||
'shop_no' => $shopNo,
|
|
||||||
'mark_no' => $mark, // ✅ save mark_no from Excel
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$msg = "Container '{$container->container_number}' created with {$savedCount} rows and {$invoiceCount} customer invoice(s).";
|
|
||||||
return redirect()->route('containers.index')->with('success', $msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function show(Container $container)
|
|
||||||
{
|
|
||||||
$container->load('rows');
|
|
||||||
|
|
||||||
$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)
|
|
||||||
{
|
|
||||||
$rowsInput = $request->input('rows', []);
|
|
||||||
|
|
||||||
foreach ($rowsInput as $rowId => $cols) {
|
|
||||||
$row = ContainerRow::where('container_id', $container->id)
|
|
||||||
->where('id', $rowId)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if (!$row) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $row->data ?? [];
|
|
||||||
foreach ($cols as $colHeader => $value) {
|
|
||||||
$data[$colHeader] = $value;
|
|
||||||
}
|
|
||||||
$row->update(['data' => $data]);
|
|
||||||
|
|
||||||
$normalizedMap = [];
|
|
||||||
foreach ($data as $key => $value) {
|
|
||||||
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()
|
|
||||||
->route('containers.show', $container->id)
|
|
||||||
->with('success', 'Excel rows updated successfully.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateStatus(Request $request, Container $container)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'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->save();
|
|
||||||
|
|
||||||
if ($request->wantsJson() || $request->ajax()) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'status' => $container->status,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return back()->with('success', 'Container status updated.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function destroy(Container $container)
|
|
||||||
{
|
|
||||||
$container->delete();
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
$year = now()->format('Y');
|
|
||||||
|
|
||||||
$last = Invoice::whereYear('created_at', $year)
|
|
||||||
->orderBy('id', 'desc')
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if ($last) {
|
|
||||||
$parts = explode('-', $last->invoice_number);
|
|
||||||
$seq = 0;
|
|
||||||
|
|
||||||
if (count($parts) === 3) {
|
|
||||||
$seq = (int) $parts[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
$nextSeq = $seq + 1;
|
|
||||||
} else {
|
|
||||||
$nextSeq = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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'
|
||||||
// ]);
|
]);
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class ChatMessage extends Model
|
|||||||
'read_by_user',
|
'read_by_user',
|
||||||
'client_id',
|
'client_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ticket this message belongs to.
|
* The ticket this message belongs to.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
class Container extends Model
|
|
||||||
{
|
|
||||||
protected $fillable = [
|
|
||||||
'container_name',
|
|
||||||
'container_number',
|
|
||||||
'container_date',
|
|
||||||
'status',
|
|
||||||
'excel_file',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $casts = [
|
|
||||||
'container_date' => 'date',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function rows()
|
|
||||||
{
|
|
||||||
return $this->hasMany(ContainerRow::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function invoices()
|
|
||||||
{
|
|
||||||
return $this->hasMany(Invoice::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
class ContainerRow extends Model
|
|
||||||
{
|
|
||||||
protected $fillable = [
|
|
||||||
'container_id',
|
|
||||||
'row_index',
|
|
||||||
'data',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $casts = [
|
|
||||||
'data' => 'array',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function container()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Container::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,35 +10,40 @@ class Invoice extends Model
|
|||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'container_id',
|
'order_id',
|
||||||
'customer_id',
|
'customer_id',
|
||||||
'mark_no',
|
'mark_no',
|
||||||
|
|
||||||
'invoice_number',
|
'invoice_number',
|
||||||
'invoice_date',
|
'invoice_date',
|
||||||
'due_date',
|
'due_date',
|
||||||
|
|
||||||
'payment_method',
|
'payment_method',
|
||||||
'reference_no',
|
'reference_no',
|
||||||
'status',
|
'status',
|
||||||
'final_amount',
|
|
||||||
'gst_percent',
|
'final_amount', // without tax
|
||||||
'gst_amount',
|
|
||||||
|
'tax_type', // gst / igst
|
||||||
|
'gst_percent', // only used for gst UI input
|
||||||
|
'cgst_percent',
|
||||||
|
'sgst_percent',
|
||||||
|
'igst_percent',
|
||||||
|
|
||||||
|
'gst_amount', // total tax amount
|
||||||
'final_amount_with_gst',
|
'final_amount_with_gst',
|
||||||
|
|
||||||
'customer_name',
|
'customer_name',
|
||||||
'company_name',
|
'company_name',
|
||||||
'customer_email',
|
'customer_email',
|
||||||
'customer_mobile',
|
'customer_mobile',
|
||||||
'customer_address',
|
'customer_address',
|
||||||
'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',
|
|
||||||
];
|
|
||||||
|
|
||||||
/****************************
|
/****************************
|
||||||
* Relationships
|
* Relationships
|
||||||
@@ -49,31 +54,21 @@ class Invoice extends Model
|
|||||||
return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC');
|
return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC');
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function container()
|
public function order()
|
||||||
// {
|
{
|
||||||
// return $this->belongsTo(Container::class);
|
return $this->belongsTo(Order::class);
|
||||||
// }
|
}
|
||||||
|
|
||||||
public function customer()
|
public function customer()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'customer_id');
|
return $this->belongsTo(User::class, 'customer_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function installments()
|
|
||||||
{
|
|
||||||
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,6 +76,7 @@ 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);
|
||||||
@@ -88,46 +84,28 @@ class Invoice extends Model
|
|||||||
|
|
||||||
public function getShipment()
|
public function getShipment()
|
||||||
{
|
{
|
||||||
return null;
|
return $this->order?->shipments?->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Charge groups base total (WITHOUT GST)
|
public function installments()
|
||||||
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');
|
return $this->hasMany(InvoiceInstallment::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// App\Models\Invoice.php
|
||||||
|
|
||||||
|
public function totalPaid()
|
||||||
|
{
|
||||||
|
return $this->installments()->sum('amount');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remainingAmount()
|
||||||
|
{
|
||||||
|
return max(
|
||||||
|
($this->final_amount_with_gst ?? 0) - $this->totalPaid(),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +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()
|
|
||||||
{
|
|
||||||
Schema::create('containers', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->string('container_name');
|
|
||||||
$table->string('container_number')->unique();
|
|
||||||
$table->date('container_date');
|
|
||||||
$table->string('excel_file')->nullable();
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down()
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('containers');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
public function up()
|
|
||||||
{
|
|
||||||
Schema::create('loading_list_items', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->foreignId('container_id')
|
|
||||||
->constrained('containers')
|
|
||||||
->onDelete('cascade');
|
|
||||||
|
|
||||||
$table->string('mark')->nullable(); // MARK / ITEM NO
|
|
||||||
$table->string('description')->nullable();
|
|
||||||
$table->integer('ctn')->nullable();
|
|
||||||
$table->integer('qty')->nullable();
|
|
||||||
$table->integer('total_qty')->nullable();
|
|
||||||
$table->string('unit')->nullable();
|
|
||||||
$table->decimal('price', 15, 3)->nullable(); // SAHIL format साठी
|
|
||||||
$table->decimal('cbm', 15, 5)->nullable();
|
|
||||||
$table->decimal('total_cbm', 15, 5)->nullable();
|
|
||||||
$table->decimal('kg', 15, 3)->nullable();
|
|
||||||
$table->decimal('total_kg', 15, 3)->nullable();
|
|
||||||
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down()
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('loading_list_items');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,31 +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::create('container_rows', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->foreignId('container_id')
|
|
||||||
->constrained('containers')
|
|
||||||
->onDelete('cascade');
|
|
||||||
|
|
||||||
// Excel मधल्या row क्रमांकासाठी (optional)
|
|
||||||
$table->unsignedInteger('row_index')->nullable();
|
|
||||||
|
|
||||||
// या row चा full data: "heading text" => "cell value"
|
|
||||||
$table->json('data');
|
|
||||||
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('container_rows');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,24 +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('containers', function (Blueprint $table) {
|
|
||||||
$table->string('status', 21)
|
|
||||||
->default('pending')
|
|
||||||
->after('container_date');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('containers', function (Blueprint $table) {
|
|
||||||
$table->dropColumn('status');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,43 +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) {
|
|
||||||
// 1) order_id foreign key काढा
|
|
||||||
$table->dropForeign(['order_id']);
|
|
||||||
|
|
||||||
// 2) order_id column काढा
|
|
||||||
$table->dropColumn('order_id');
|
|
||||||
|
|
||||||
// 3) container_id add करा
|
|
||||||
$table->unsignedBigInteger('container_id')->nullable()->after('id');
|
|
||||||
|
|
||||||
// 4) container_id FK
|
|
||||||
$table->foreign('container_id')
|
|
||||||
->references('id')
|
|
||||||
->on('containers')
|
|
||||||
->onDelete('cascade');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('invoices', function (Blueprint $table) {
|
|
||||||
// rollback: container_id काढून order_id परत add
|
|
||||||
$table->dropForeign(['container_id']);
|
|
||||||
$table->dropColumn('container_id');
|
|
||||||
|
|
||||||
$table->unsignedBigInteger('order_id')->index();
|
|
||||||
$table->foreign('order_id')
|
|
||||||
->references('id')
|
|
||||||
->on('orders')
|
|
||||||
->onDelete('cascade');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -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']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -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']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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'
|
|
||||||
");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -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 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user