Compare commits
8 Commits
d5e9113820
...
qa
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
9a6ca49ad7 | ||
|
|
bf2689e62d | ||
|
|
c25b468c77 | ||
|
|
5d8a746876 | ||
|
|
bb2a361a97 | ||
|
|
6b5876e08f | ||
|
|
43b1a64911 | ||
|
|
ff4c006ca4 |
@@ -1,6 +1,6 @@
|
|||||||
APP_NAME=Laravel
|
APP_NAME=Laravel
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=
|
APP_KEY=base64:/ulhRgiCOFjZV6xUDkXLfiR9X8iFRZ4QIiX3UJbdwY4=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ use App\Models\InvoiceItem;
|
|||||||
use App\Models\InvoiceInstallment;
|
use App\Models\InvoiceInstallment;
|
||||||
use App\Models\InvoiceChargeGroup;
|
use App\Models\InvoiceChargeGroup;
|
||||||
use App\Models\InvoiceChargeGroupItem;
|
use App\Models\InvoiceChargeGroupItem;
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Mpdf\Mpdf;
|
use Mpdf\Mpdf;
|
||||||
|
|
||||||
class AdminInvoiceController extends Controller
|
class AdminInvoiceController extends Controller
|
||||||
@@ -21,7 +19,6 @@ class AdminInvoiceController extends Controller
|
|||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
// Container relation सह invoices load करतो
|
|
||||||
$query = Invoice::with(['items', 'customer', 'container']);
|
$query = Invoice::with(['items', 'customer', 'container']);
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
@@ -50,33 +47,25 @@ class AdminInvoiceController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// POPUP VIEW + CUSTOMER DATA SYNC
|
// POPUP VIEW
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function popup($id)
|
public function popup($id)
|
||||||
{
|
{
|
||||||
$invoice = Invoice::with([
|
$invoice = Invoice::with([
|
||||||
'items',
|
'items',
|
||||||
'chargeGroups.items',
|
'chargeGroups.items',
|
||||||
])->findOrFail($id);
|
])->findOrFail($id);
|
||||||
|
|
||||||
// demo update असेल तर ठेव/काढ
|
$shipment = null;
|
||||||
$invoice->update([
|
|
||||||
'customer_email' => 'test@demo.com',
|
|
||||||
'customer_address' => 'TEST ADDRESS',
|
|
||||||
'pincode' => '999999',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$shipment = null;
|
$groupedItemIds = $invoice->chargeGroups
|
||||||
|
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
// आधीच group मध्ये असलेले item ids
|
return view('admin.popup_invoice', compact('invoice', 'shipment', 'groupedItemIds'));
|
||||||
$groupedItemIds = $invoice->chargeGroups
|
}
|
||||||
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
|
|
||||||
->unique()
|
|
||||||
->values()
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
return view('admin.popup_invoice', compact('invoice', 'shipment', 'groupedItemIds'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// EDIT INVOICE PAGE
|
// EDIT INVOICE PAGE
|
||||||
@@ -88,8 +77,31 @@ class AdminInvoiceController extends Controller
|
|||||||
'customer',
|
'customer',
|
||||||
'container',
|
'container',
|
||||||
'chargeGroups.items',
|
'chargeGroups.items',
|
||||||
|
'installments',
|
||||||
])->findOrFail($id);
|
])->findOrFail($id);
|
||||||
|
|
||||||
|
// ✅ Customer details sync — जर test data आला असेल तर fix होईल
|
||||||
|
if ($invoice->customer) {
|
||||||
|
$needsUpdate = [];
|
||||||
|
|
||||||
|
if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') {
|
||||||
|
$needsUpdate['customer_email'] = $invoice->customer->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($invoice->customer_address) || $invoice->customer_address === 'TEST ADDRESS') {
|
||||||
|
$needsUpdate['customer_address'] = $invoice->customer->address;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($invoice->pincode) || $invoice->pincode === '999999') {
|
||||||
|
$needsUpdate['pincode'] = $invoice->customer->pincode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($needsUpdate)) {
|
||||||
|
$invoice->update($needsUpdate);
|
||||||
|
$invoice->refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$shipment = null;
|
$shipment = null;
|
||||||
|
|
||||||
$groupedItemIds = $invoice->chargeGroups
|
$groupedItemIds = $invoice->chargeGroups
|
||||||
@@ -104,7 +116,7 @@ class AdminInvoiceController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// UPDATE INVOICE (HEADER LEVEL)
|
// UPDATE INVOICE (HEADER ONLY)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
@@ -118,76 +130,31 @@ 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',
|
||||||
'final_amount' => 'required|numeric|min:0',
|
'status' => 'required|in:pending,paying,paid,overdue',
|
||||||
'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 Update Data', $data);
|
Log::info('✅ Validated Invoice Header Update Data', $data);
|
||||||
|
|
||||||
$finalAmount = (float) $data['final_amount'];
|
|
||||||
$taxPercent = (float) $data['tax_percent'];
|
|
||||||
|
|
||||||
if ($data['tax_type'] === 'gst') {
|
|
||||||
Log::info('🟢 GST Selected', compact('taxPercent'));
|
|
||||||
|
|
||||||
$data['cgst_percent'] = $taxPercent / 2;
|
|
||||||
$data['sgst_percent'] = $taxPercent / 2;
|
|
||||||
$data['igst_percent'] = 0;
|
|
||||||
} else {
|
|
||||||
Log::info('🔵 IGST Selected', compact('taxPercent'));
|
|
||||||
|
|
||||||
$data['cgst_percent'] = 0;
|
|
||||||
$data['sgst_percent'] = 0;
|
|
||||||
$data['igst_percent'] = $taxPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
$gstAmount = ($finalAmount * $taxPercent) / 100;
|
|
||||||
$data['gst_amount'] = $gstAmount;
|
|
||||||
$data['final_amount_with_gst'] = $finalAmount + $gstAmount;
|
|
||||||
$data['gst_percent'] = $taxPercent;
|
|
||||||
|
|
||||||
Log::info('📌 Final Calculated Invoice Values', [
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'final_amount' => $finalAmount,
|
|
||||||
'gst_amount' => $data['gst_amount'],
|
|
||||||
'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);
|
$invoice->update($data);
|
||||||
|
|
||||||
Log::info('✅ Invoice Updated Successfully', [
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$invoice->refresh();
|
$invoice->refresh();
|
||||||
Log::info('🔍 Invoice AFTER UPDATE (DB values)', [
|
|
||||||
'invoice_id' => $invoice->id,
|
Log::info('🔍 Invoice AFTER HEADER UPDATE', [
|
||||||
'final_amount' => $invoice->final_amount,
|
'invoice_id' => $invoice->id,
|
||||||
'gst_percent' => $invoice->gst_percent,
|
'charge_groups_total' => $invoice->charge_groups_total,
|
||||||
'gst_amount' => $invoice->gst_amount,
|
'gst_amount' => $invoice->gst_amount,
|
||||||
'final_amount_with_gst' => $invoice->final_amount_with_gst,
|
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
|
||||||
'tax_type' => $invoice->tax_type,
|
|
||||||
'cgst_percent' => $invoice->cgst_percent,
|
|
||||||
'sgst_percent' => $invoice->sgst_percent,
|
|
||||||
'igst_percent' => $invoice->igst_percent,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->generateInvoicePDF($invoice);
|
$this->generateInvoicePDF($invoice);
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
->route('admin.invoices.index')
|
->route('admin.invoices.edit', $invoice->id)
|
||||||
->with('success', 'Invoice updated & PDF generated successfully.');
|
->with('success', 'Invoice updated & PDF generated successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// UPDATE INVOICE ITEMS
|
// UPDATE INVOICE ITEMS (फक्त items save)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function updateItems(Request $request, Invoice $invoice)
|
public function updateItems(Request $request, Invoice $invoice)
|
||||||
{
|
{
|
||||||
@@ -202,9 +169,7 @@ class AdminInvoiceController extends Controller
|
|||||||
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
|
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$itemsInput = $data['items'];
|
foreach ($data['items'] as $itemId => $itemData) {
|
||||||
|
|
||||||
foreach ($itemsInput as $itemId => $itemData) {
|
|
||||||
$item = InvoiceItem::where('id', $itemId)
|
$item = InvoiceItem::where('id', $itemId)
|
||||||
->where('invoice_id', $invoice->id)
|
->where('invoice_id', $invoice->id)
|
||||||
->first();
|
->first();
|
||||||
@@ -222,47 +187,8 @@ class AdminInvoiceController extends Controller
|
|||||||
$item->save();
|
$item->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
$newBaseAmount = InvoiceItem::where('invoice_id', $invoice->id)
|
Log::info('✅ Invoice items updated (no totals recalculation)', [
|
||||||
->sum('ttl_amount');
|
'invoice_id' => $invoice->id,
|
||||||
|
|
||||||
$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();
|
|
||||||
|
|
||||||
// ⭐ Total Charges (groups समावेत) पुन्हा कॅलक्युलेट
|
|
||||||
$chargeGroupsTotal = $invoice->chargeGroups()->sum('total_charge');
|
|
||||||
$invoice->charge_groups_total = $chargeGroupsTotal;
|
|
||||||
$invoice->grand_total_with_charges = $invoice->final_amount_with_gst + $chargeGroupsTotal;
|
|
||||||
$invoice->save();
|
|
||||||
|
|
||||||
Log::info('✅ Invoice items updated & totals recalculated', [
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'final_amount' => $invoice->final_amount,
|
|
||||||
'gst_amount' => $invoice->gst_amount,
|
|
||||||
'final_amount_with_gst' => $invoice->final_amount_with_gst,
|
|
||||||
'tax_type' => $invoice->tax_type,
|
|
||||||
'cgst_percent' => $invoice->cgst_percent,
|
|
||||||
'sgst_percent' => $invoice->sgst_percent,
|
|
||||||
'igst_percent' => $invoice->igst_percent,
|
|
||||||
'charge_groups_total' => $invoice->charge_groups_total,
|
|
||||||
'grand_total_with_charges' => $invoice->grand_total_with_charges,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return back()->with('success', 'Invoice items updated successfully.');
|
return back()->with('success', 'Invoice items updated successfully.');
|
||||||
@@ -318,12 +244,12 @@ class AdminInvoiceController extends Controller
|
|||||||
'amount' => 'required|numeric|min:1',
|
'amount' => 'required|numeric|min:1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$invoice = Invoice::findOrFail($invoice_id);
|
$invoice = Invoice::findOrFail($invoice_id);
|
||||||
$paidTotal = $invoice->installments()->sum('amount');
|
|
||||||
|
|
||||||
// 👇 Total Charges (grand_total_with_charges) वरून remaining
|
$grandTotal = $invoice->grand_total_with_charges ?? 0;
|
||||||
$grandTotal = $invoice->grand_total_with_charges;
|
|
||||||
$remaining = $grandTotal - $paidTotal;
|
$paidTotal = $invoice->installments()->sum('amount');
|
||||||
|
$remaining = $grandTotal - $paidTotal;
|
||||||
|
|
||||||
if ($request->amount > $remaining) {
|
if ($request->amount > $remaining) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -340,24 +266,25 @@ class AdminInvoiceController extends Controller
|
|||||||
'amount' => $request->amount,
|
'amount' => $request->amount,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$newPaid = $paidTotal + $request->amount;
|
$newPaid = $paidTotal + $request->amount;
|
||||||
|
$remaining = max(0, $grandTotal - $newPaid);
|
||||||
|
|
||||||
if ($newPaid >= $invoice->final_amount_with_gst) {
|
if ($newPaid >= $grandTotal && $grandTotal > 0) {
|
||||||
$invoice->update(['status' => 'paid']);
|
$invoice->update(['status' => 'paid']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => 'Installment added successfully.',
|
'message' => 'Installment added successfully.',
|
||||||
'installment' => $installment,
|
'installment' => $installment,
|
||||||
'totalPaid' => $newPaid,
|
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
|
||||||
'gstAmount' => $invoice->gst_amount,
|
'gstAmount' => $invoice->gst_amount ?? 0,
|
||||||
'finalAmountWithGst' => $grandTotal, // इथे grand total पाठव
|
'grandTotal' => $grandTotal,
|
||||||
'baseAmount' => $invoice->final_amount,
|
'totalPaid' => $newPaid,
|
||||||
'remaining' => max(0, $grandTotal - $newPaid),
|
'remaining' => $remaining,
|
||||||
'isCompleted' => $newPaid >= $grandTotal,
|
'isCompleted' => $remaining <= 0,
|
||||||
|
'isZero' => $newPaid == 0,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@@ -369,87 +296,158 @@ class AdminInvoiceController extends Controller
|
|||||||
$invoice = $installment->invoice;
|
$invoice = $installment->invoice;
|
||||||
|
|
||||||
$installment->delete();
|
$installment->delete();
|
||||||
|
$invoice->refresh();
|
||||||
|
|
||||||
$paidTotal = $invoice->installments()->sum('amount');
|
$grandTotal = $invoice->grand_total_with_charges ?? 0;
|
||||||
$grandTotal = $invoice->grand_total_with_charges;
|
|
||||||
$remaining = $grandTotal - $paidTotal;
|
$paidTotal = $invoice->installments()->sum('amount');
|
||||||
|
$remaining = max(0, $grandTotal - $paidTotal);
|
||||||
|
|
||||||
|
if ($paidTotal <= 0 && $grandTotal > 0) {
|
||||||
|
$invoice->update(['status' => 'pending']);
|
||||||
|
} elseif ($paidTotal > 0 && $paidTotal < $grandTotal) {
|
||||||
|
$invoice->update(['status' => 'pending']);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => 'Installment deleted.',
|
'message' => 'Installment deleted.',
|
||||||
'totalPaid' => $paidTotal,
|
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
|
||||||
'gstAmount' => $invoice->gst_amount,
|
'gstAmount' => $invoice->gst_amount ?? 0,
|
||||||
'finalAmountWithGst' => $grandTotal, // इथेही
|
'grandTotal' => $grandTotal,
|
||||||
'baseAmount' => $invoice->final_amount,
|
'totalPaid' => $paidTotal,
|
||||||
'remaining' => $remaining,
|
'remaining' => $remaining,
|
||||||
'isZero' => $paidTotal == 0,
|
'isZero' => $paidTotal == 0,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// CHARGE GROUP SAVE (no AJAX branch)
|
// CHARGE GROUP SAVE
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function storeChargeGroup(Request $request, $invoiceId)
|
public function storeChargeGroup(Request $request, $invoiceId)
|
||||||
{
|
{
|
||||||
$invoice = Invoice::with('items')->findOrFail($invoiceId);
|
Log::info('🟡 storeChargeGroup HIT', [
|
||||||
|
'invoice_id' => $invoiceId,
|
||||||
$data = $request->validate([
|
'payload' => $request->all(),
|
||||||
'group_name' => 'required|string|max:255',
|
|
||||||
'basis_type' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
|
|
||||||
'basis_value' => 'required|numeric',
|
|
||||||
'rate' => 'required|numeric|min:0.0001',
|
|
||||||
'auto_total' => 'required|numeric|min:0.01',
|
|
||||||
'item_ids' => 'required|array',
|
|
||||||
'item_ids.*' => 'integer|exists:invoice_items,id',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$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);
|
||||||
|
|
||||||
|
// duplicate name check
|
||||||
$exists = InvoiceChargeGroup::where('invoice_id', $invoice->id)
|
$exists = InvoiceChargeGroup::where('invoice_id', $invoice->id)
|
||||||
->where('group_name', $data['group_name'])
|
->where('group_name', $data['groupname'])
|
||||||
->exists();
|
->exists();
|
||||||
|
|
||||||
if ($exists) {
|
if ($exists) {
|
||||||
return back()
|
return back()
|
||||||
->withErrors(['group_name' => 'This group name is already used for this invoice.'])
|
->withErrors(['groupname' => 'This group name is already used for this invoice.'])
|
||||||
->withInput();
|
->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Group create
|
||||||
$group = InvoiceChargeGroup::create([
|
$group = InvoiceChargeGroup::create([
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
'group_name' => $data['group_name'],
|
'group_name' => $data['groupname'],
|
||||||
'basis_type' => $data['basis_type'],
|
'basis_type' => $data['basistype'],
|
||||||
'basis_value' => $data['basis_value'],
|
'basis_value' => $data['basisvalue'],
|
||||||
'rate' => $data['rate'],
|
'rate' => $data['rate'],
|
||||||
'total_charge' => $data['auto_total'],
|
'total_charge' => $baseTotal,
|
||||||
|
'tax_type' => $taxType,
|
||||||
|
'gst_percent' => $gstPercent,
|
||||||
|
'total_with_gst' => $totalWithGst,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
foreach ($data['item_ids'] as $itemId) {
|
// 2) Items link
|
||||||
|
foreach ($data['itemids'] as $itemId) {
|
||||||
InvoiceChargeGroupItem::create([
|
InvoiceChargeGroupItem::create([
|
||||||
'group_id' => $group->id,
|
'group_id' => $group->id,
|
||||||
'invoice_item_id' => $itemId,
|
'invoice_item_id' => $itemId,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⭐ Charge groups नुसार Total Charges सेट करा
|
// 3) सर्व groups वरून invoice level totals
|
||||||
$chargeGroupsTotal = $invoice->chargeGroups()->sum('total_charge');
|
$invoice->load('chargeGroups');
|
||||||
$grandTotal = $invoice->final_amount_with_gst + $chargeGroupsTotal;
|
|
||||||
|
|
||||||
$invoice->update([
|
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge'); // base
|
||||||
'charge_groups_total' => $chargeGroupsTotal,
|
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst'); // base + gst
|
||||||
'grand_total_with_charges' => $grandTotal,
|
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase; // gst only
|
||||||
]);
|
|
||||||
|
|
||||||
return redirect()
|
$invoiceGstPercent = $group->gst_percent ?? 0;
|
||||||
->back()
|
$invoiceTaxType = $group->tax_type ?? 'gst';
|
||||||
->with('success', 'Charge group saved successfully.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function download(Invoice $invoice)
|
$cgstPercent = 0;
|
||||||
{
|
$sgstPercent = 0;
|
||||||
if (!$invoice->pdf_path || !Storage::exists($invoice->pdf_path)) {
|
$igstPercent = 0;
|
||||||
return back()->with('error', 'PDF not found.');
|
|
||||||
|
if ($invoiceTaxType === 'gst') {
|
||||||
|
$cgstPercent = $invoiceGstPercent / 2;
|
||||||
|
$sgstPercent = $invoiceGstPercent / 2;
|
||||||
|
} elseif ($invoiceTaxType === 'igst') {
|
||||||
|
$igstPercent = $invoiceGstPercent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Storage::download($invoice->pdf_path, $invoice->invoice_number . '.pdf');
|
// 🔴 इथे main fix:
|
||||||
|
// final_amount = base (total_charge sum)
|
||||||
|
// final_amount_with_gst = base + gst (total_with_gst sum)
|
||||||
|
// grand_total_with_charges = final_amount_with_gst (same)
|
||||||
|
$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,
|
||||||
|
'gst_percent' => $invoiceGstPercent,
|
||||||
|
'tax_type' => $invoiceTaxType,
|
||||||
|
'cgst_percent' => $cgstPercent,
|
||||||
|
'sgst_percent' => $sgstPercent,
|
||||||
|
'igst_percent' => $igstPercent,
|
||||||
|
'final_amount' => $invoice->final_amount,
|
||||||
|
'final_amount_with_gst' => $invoice->final_amount_with_gst,
|
||||||
|
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Charge group saved successfully.',
|
||||||
|
'group_id' => $group->id,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ use Illuminate\Http\Request;
|
|||||||
use Maatwebsite\Excel\Facades\Excel;
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Barryvdh\DomPDF\Facade\Pdf;
|
use Barryvdh\DomPDF\Facade\Pdf;
|
||||||
use Illuminate\Support\Facades\Storage; // <-- added for Excel download
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class ContainerController extends Controller
|
class ContainerController extends Controller
|
||||||
{
|
{
|
||||||
@@ -518,9 +518,12 @@ class ContainerController extends Controller
|
|||||||
$firstMark = $rowsForCustomer[0]['mark'];
|
$firstMark = $rowsForCustomer[0]['mark'];
|
||||||
$snap = $markToSnapshot[$firstMark] ?? null;
|
$snap = $markToSnapshot[$firstMark] ?? null;
|
||||||
|
|
||||||
|
// ✅ Customer User model वरून fetch करा (customer_id string आहे जसे CID-2025-000001)
|
||||||
|
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
|
||||||
|
|
||||||
$invoice = new Invoice();
|
$invoice = new Invoice();
|
||||||
$invoice->container_id = $container->id;
|
$invoice->container_id = $container->id;
|
||||||
// $invoice->customer_id = $customerId;
|
$invoice->customer_id = $customerUser->id ?? null; // ✅ integer id store करतोय
|
||||||
$invoice->mark_no = $firstMark;
|
$invoice->mark_no = $firstMark;
|
||||||
|
|
||||||
$invoice->invoice_number = $this->generateInvoiceNumber();
|
$invoice->invoice_number = $this->generateInvoiceNumber();
|
||||||
@@ -533,21 +536,25 @@ class ContainerController extends Controller
|
|||||||
->addDays(10)
|
->addDays(10)
|
||||||
->format('Y-m-d');
|
->format('Y-m-d');
|
||||||
|
|
||||||
|
// ✅ Snapshot data from MarkList (backward compatibility)
|
||||||
if ($snap) {
|
if ($snap) {
|
||||||
$invoice->customer_name = $snap['customer_name'] ?? null;
|
$invoice->customer_name = $snap['customer_name'] ?? null;
|
||||||
$invoice->company_name = $snap['company_name'] ?? null;
|
$invoice->company_name = $snap['company_name'] ?? null;
|
||||||
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
|
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ User model वरून email, address, pincode घ्या
|
||||||
|
if ($customerUser) {
|
||||||
|
$invoice->customer_email = $customerUser->email ?? null;
|
||||||
|
$invoice->customer_address = $customerUser->address ?? null;
|
||||||
|
$invoice->pincode = $customerUser->pincode ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
$invoice->final_amount = 0;
|
$invoice->final_amount = 0;
|
||||||
$invoice->gst_percent = 0;
|
$invoice->gst_percent = 0;
|
||||||
$invoice->gst_amount = 0;
|
$invoice->gst_amount = 0;
|
||||||
$invoice->final_amount_with_gst = 0;
|
$invoice->final_amount_with_gst = 0;
|
||||||
|
|
||||||
$invoice->customer_email = null;
|
|
||||||
$invoice->customer_address = null;
|
|
||||||
$invoice->pincode = null;
|
|
||||||
|
|
||||||
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
|
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
|
||||||
$invoice->notes = 'Auto-created from Container ' . $container->container_number
|
$invoice->notes = 'Auto-created from Container ' . $container->container_number
|
||||||
. ' for Mark(s): ' . implode(', ', $uniqueMarks);
|
. ' for Mark(s): ' . implode(', ', $uniqueMarks);
|
||||||
@@ -613,7 +620,18 @@ class ContainerController extends Controller
|
|||||||
public function show(Container $container)
|
public function show(Container $container)
|
||||||
{
|
{
|
||||||
$container->load('rows');
|
$container->load('rows');
|
||||||
return view('admin.container_show', compact('container'));
|
|
||||||
|
// paid / paying invoices च्या row indexes collect करा
|
||||||
|
$lockedRowIndexes = \App\Models\Invoice::whereIn('invoices.status', ['paid', 'paying'])
|
||||||
|
->where('invoices.container_id', $container->id)
|
||||||
|
->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id')
|
||||||
|
->pluck('invoice_items.container_row_index')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return view('admin.container_show', compact('container', 'lockedRowIndexes'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateRows(Request $request, Container $container)
|
public function updateRows(Request $request, Container $container)
|
||||||
@@ -871,73 +889,71 @@ class ContainerController extends Controller
|
|||||||
return Storage::download($path, $fileName);
|
return Storage::download($path, $fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function popupPopup(Container $container)
|
public function popupPopup(Container $container)
|
||||||
{
|
{
|
||||||
// existing show सारखाच data वापरू
|
// existing show सारखाच data वापरू
|
||||||
$container->load('rows');
|
$container->load('rows');
|
||||||
|
|
||||||
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
|
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
|
||||||
$rows = $container->rows ?? collect();
|
$rows = $container->rows ?? collect();
|
||||||
|
|
||||||
$totalCtn = 0;
|
$totalCtn = 0;
|
||||||
$totalQty = 0;
|
$totalQty = 0;
|
||||||
$totalCbm = 0;
|
$totalCbm = 0;
|
||||||
$totalKg = 0;
|
$totalKg = 0;
|
||||||
|
|
||||||
$ctnKeys = ['CTN', 'CTNS'];
|
$ctnKeys = ['CTN', 'CTNS'];
|
||||||
$qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES'];
|
$qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES'];
|
||||||
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
|
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
|
||||||
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
|
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
|
||||||
|
|
||||||
$getFirstNumeric = function (array $data, array $possibleKeys) {
|
$getFirstNumeric = function (array $data, array $possibleKeys) {
|
||||||
$normalizedMap = [];
|
$normalizedMap = [];
|
||||||
foreach ($data as $key => $value) {
|
foreach ($data as $key => $value) {
|
||||||
if ($key === null) continue;
|
if ($key === null) continue;
|
||||||
$normKey = strtoupper((string)$key);
|
$normKey = strtoupper((string)$key);
|
||||||
$normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey);
|
$normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey);
|
||||||
$normalizedMap[$normKey] = $value;
|
$normalizedMap[$normKey] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($possibleKeys as $search) {
|
foreach ($possibleKeys as $search) {
|
||||||
$normSearch = strtoupper($search);
|
$normSearch = strtoupper($search);
|
||||||
$normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch);
|
$normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch);
|
||||||
|
|
||||||
foreach ($normalizedMap as $nKey => $value) {
|
foreach ($normalizedMap as $nKey => $value) {
|
||||||
if (strpos($nKey, $normSearch) !== false) {
|
if (strpos($nKey, $normSearch) !== false) {
|
||||||
if (is_numeric($value)) {
|
if (is_numeric($value)) {
|
||||||
return (float)$value;
|
return (float)$value;
|
||||||
}
|
}
|
||||||
if (is_string($value) && is_numeric(trim($value))) {
|
if (is_string($value) && is_numeric(trim($value))) {
|
||||||
return (float)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);
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
$summary = [
|
||||||
$data = $row->data ?? [];
|
'total_ctn' => round($totalCtn, 2),
|
||||||
if (!is_array($data)) continue;
|
'total_qty' => round($totalQty, 2),
|
||||||
|
'total_cbm' => round($totalCbm, 3),
|
||||||
|
'total_kg' => round($totalKg, 2),
|
||||||
|
];
|
||||||
|
|
||||||
$totalCtn += $getFirstNumeric($data, $ctnKeys);
|
return view('admin.partials.container_popup_readonly', [
|
||||||
$totalQty += $getFirstNumeric($data, $qtyKeys);
|
'container' => $container,
|
||||||
$totalCbm += $getFirstNumeric($data, $cbmKeys);
|
'summary' => $summary,
|
||||||
$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,23 +21,33 @@ class UserOrderController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
// Get all orders
|
// Get customer invoices with containers
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
$orders = $user->orders()->with('invoice')->get();
|
$invoices = $user->invoices()->with('container')->get();
|
||||||
|
|
||||||
|
// Unique containers for this customer
|
||||||
|
$containers = $invoices->pluck('container')->filter()->unique('id');
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
// Counts
|
// Counts based on container status
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
$totalOrders = $orders->count();
|
$totalOrders = $containers->count();
|
||||||
$delivered = $orders->where('status', 'delivered')->count();
|
|
||||||
$inTransit = $orders->where('status', '!=', 'delivered')->count();
|
$delivered = $containers->where('status', 'delivered')->count();
|
||||||
$active = $totalOrders;
|
|
||||||
|
$inTransit = $containers->whereNotIn('status', [
|
||||||
|
'delivered',
|
||||||
|
'warehouse',
|
||||||
|
'domestic-distribution'
|
||||||
|
])->count();
|
||||||
|
|
||||||
|
$active = $totalOrders;
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
// Total Amount = Invoice.total_with_gst
|
// Total Amount = sum of invoice totals
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
$totalAmount = $orders->sum(function ($o) {
|
$totalAmount = $invoices->sum(function ($invoice) {
|
||||||
return $o->invoice->final_amount_with_gst ?? 0;
|
return $invoice->final_amount_with_gst ?? 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Format total amount in K, L, Cr
|
// Format total amount in K, L, Cr
|
||||||
@@ -45,13 +55,12 @@ 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, // formatted value
|
'total_value' => $formattedAmount,
|
||||||
'total_raw' => $totalAmount // original value
|
'total_raw' => $totalAmount
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -90,20 +99,28 @@ class UserOrderController extends Controller
|
|||||||
], 401);
|
], 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch orders for this user
|
// Get invoices with containers for this customer
|
||||||
$orders = $user->orders()
|
$invoices = $user->invoices()
|
||||||
->with(['invoice', 'shipments'])
|
->with('container')
|
||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->get()
|
->get();
|
||||||
->map(function ($o) {
|
|
||||||
return [
|
// Extract unique containers
|
||||||
'order_id' => $o->order_id,
|
$containers = $invoices->pluck('container')
|
||||||
'status' => $o->status,
|
->filter()
|
||||||
'amount' => $o->ttl_amount,
|
->unique('id')
|
||||||
'description'=> "Order from {$o->origin} to {$o->destination}",
|
->values();
|
||||||
'created_at' => $o->created_at,
|
|
||||||
];
|
$orders = $containers->map(function ($container) {
|
||||||
});
|
|
||||||
|
return [
|
||||||
|
'order_id' => $container->id,
|
||||||
|
'container_number' => $container->container_number,
|
||||||
|
'status' => $container->status,
|
||||||
|
'container_date' => $container->container_date,
|
||||||
|
'created_at' => $container->created_at,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
@@ -115,45 +132,73 @@ public function orderDetails($order_id)
|
|||||||
{
|
{
|
||||||
$user = JWTAuth::parseToken()->authenticate();
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
$order = $user->orders()
|
if (!$user) {
|
||||||
|
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 (!$order) {
|
if (!$invoice) {
|
||||||
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
|
return response()->json([
|
||||||
|
'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)
|
||||||
@@ -179,23 +224,35 @@ public function trackOrder($order_id)
|
|||||||
{
|
{
|
||||||
$user = JWTAuth::parseToken()->authenticate();
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
$order = $user->orders()
|
if (!$user) {
|
||||||
->with('shipments')
|
return response()->json([
|
||||||
->where('order_id', $order_id)
|
'success' => false,
|
||||||
->first();
|
'message' => 'Unauthorized'
|
||||||
|
], 401);
|
||||||
if (!$order) {
|
|
||||||
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$shipment = $order->shipments()->first();
|
// 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();
|
||||||
|
|
||||||
|
if (!$invoice || !$invoice->container) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Order not found'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$container = $invoice->container;
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'track' => [
|
'track' => [
|
||||||
'order_id' => $order->order_id,
|
'order_id' => $container->id,
|
||||||
'shipment_status' => $shipment->status ?? 'pending',
|
'container_number' => $container->container_number,
|
||||||
'shipment_date' => $shipment->shipment_date ?? null,
|
'status' => $container->status,
|
||||||
|
'container_date' => $container->container_date,
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -289,44 +346,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'
|
||||||
]);
|
// ]);
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ class Invoice extends Model
|
|||||||
'pincode',
|
'pincode',
|
||||||
'pdf_path',
|
'pdf_path',
|
||||||
'notes',
|
'notes',
|
||||||
|
// totals from charge groups
|
||||||
|
'charge_groups_total',
|
||||||
|
'grand_total_with_charges',
|
||||||
|
'tax_type',
|
||||||
|
'cgst_percent',
|
||||||
|
'sgst_percent',
|
||||||
|
'igst_percent',
|
||||||
];
|
];
|
||||||
|
|
||||||
/****************************
|
/****************************
|
||||||
@@ -42,10 +49,10 @@ 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 container()
|
||||||
{
|
// {
|
||||||
return $this->belongsTo(Container::class);
|
// return $this->belongsTo(Container::class);
|
||||||
}
|
// }
|
||||||
|
|
||||||
public function customer()
|
public function customer()
|
||||||
{
|
{
|
||||||
@@ -57,16 +64,16 @@ class Invoice extends Model
|
|||||||
return $this->hasMany(InvoiceInstallment::class);
|
return $this->hasMany(InvoiceInstallment::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ SINGLE, correct relation
|
|
||||||
public function chargeGroups()
|
public function chargeGroups()
|
||||||
{
|
{
|
||||||
return $this->hasMany(\App\Models\InvoiceChargeGroup::class, 'invoice_id');
|
return $this->hasMany(InvoiceChargeGroup::class, 'invoice_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************
|
/****************************
|
||||||
* Helper Functions
|
* Helper Functions
|
||||||
****************************/
|
****************************/
|
||||||
|
|
||||||
|
// (Items based calculateTotals वापरणार नाहीस तरी ठेवू शकतोस)
|
||||||
public function calculateTotals()
|
public function calculateTotals()
|
||||||
{
|
{
|
||||||
$gst = ($this->final_amount * $this->gst_percent) / 100;
|
$gst = ($this->final_amount * $this->gst_percent) / 100;
|
||||||
@@ -84,29 +91,43 @@ class Invoice extends Model
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Charge groups total accessor
|
// ✅ Charge groups base total (WITHOUT GST)
|
||||||
public function getChargeGroupsTotalAttribute()
|
public function getChargeGroupsTotalAttribute()
|
||||||
{
|
{
|
||||||
// relation already loaded असेल तर collection वरून sum होईल
|
// base = total_charge sum
|
||||||
return (float) $this->chargeGroups->sum('total_charge');
|
return (float) $this->chargeGroups->sum('total_charge');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Grand total accessor (items + GST + charge groups)
|
// ✅ Grand total: Charge groups base + GST (items ignore)
|
||||||
public function getGrandTotalWithChargesAttribute()
|
public function getGrandTotalWithChargesAttribute()
|
||||||
{
|
{
|
||||||
return (float) ($this->final_amount_with_gst ?? 0) + $this->charge_groups_total;
|
$base = (float) ($this->charge_groups_total ?? 0);
|
||||||
|
$gst = (float) ($this->gst_amount ?? 0);
|
||||||
|
|
||||||
|
return $base + $gst;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function totalPaid()
|
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->installments->sum('amount');
|
return $this->belongsTo(\App\Models\Container::class, 'container_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function remainingAmount()
|
|
||||||
{
|
|
||||||
return $this->grand_total_with_charges - $this->totalPaid();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ class InvoiceChargeGroup extends Model
|
|||||||
'basis_value',
|
'basis_value',
|
||||||
'rate',
|
'rate',
|
||||||
'total_charge',
|
'total_charge',
|
||||||
|
|
||||||
|
'tax_type',
|
||||||
|
'gst_percent',
|
||||||
|
'total_with_gst',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function invoice()
|
public function invoice()
|
||||||
|
|||||||
@@ -89,10 +89,7 @@ class User extends Authenticatable implements JWTSubject
|
|||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
public function invoices()
|
|
||||||
{
|
|
||||||
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'customer_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// App\Models\User.php
|
// App\Models\User.php
|
||||||
@@ -108,6 +105,10 @@ public function invoiceInstallments()
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function invoices()
|
||||||
|
{
|
||||||
|
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?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']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?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'
|
||||||
|
");
|
||||||
|
}
|
||||||
|
};
|
||||||
BIN
public/invoices/invoice-INV-2026-000007.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000007.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000012.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000012.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000013.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000013.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000014.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000014.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000015.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000015.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000183.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000183.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000184.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000184.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000185.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000185.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000202.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000202.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000205.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000205.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000206.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000206.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000209.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000209.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000213.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000213.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000214.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000214.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000217.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000217.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000218.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000218.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000220.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000220.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000221.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000221.pdf
Normal file
Binary file not shown.
@@ -672,11 +672,19 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
|
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
|
||||||
@endphp
|
// row index = headerRowIndex + 1 + offset — ContainerRow मध्ये row_index save आहे
|
||||||
|
$isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
|
||||||
|
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if($loop->first && $isLockedByInvoice)
|
||||||
|
{{-- पहिल्या cell मध्ये lock icon --}}
|
||||||
|
@endif
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="cm-cell-input {{ $isTotalColumn ? 'cm-cell-readonly' : '' }}"
|
class="cm-cell-input {{ $isReadOnly ? 'cm-cell-readonly' : '' }}"
|
||||||
name="rows[{{ $row->id }}][{{ $heading }}]"
|
name="rows[{{ $row->id }}][{{ $heading }}]"
|
||||||
value="{{ $value }}"
|
value="{{ $value }}"
|
||||||
data-row-id="{{ $row->id }}"
|
data-row-id="{{ $row->id }}"
|
||||||
@@ -690,10 +698,11 @@
|
|||||||
data-ttlkg="{{ $isTotalKg ? '1' : '0' }}"
|
data-ttlkg="{{ $isTotalKg ? '1' : '0' }}"
|
||||||
data-price="{{ $isPrice ? '1' : '0' }}"
|
data-price="{{ $isPrice ? '1' : '0' }}"
|
||||||
data-amount="{{ $isAmount ? '1' : '0' }}"
|
data-amount="{{ $isAmount ? '1' : '0' }}"
|
||||||
@if($isTotalColumn) readonly @endif
|
{{ $isReadOnly ? 'readonly' : '' }}
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -706,186 +715,195 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(!$container->rows->isEmpty())
|
@if(!$container->rows->isEmpty())
|
||||||
<button id="cmSaveBtnFloating" class="cm-save-btn-floating">
|
<button
|
||||||
Save Changes
|
id="cmSaveBtnFloating"
|
||||||
</button>
|
class="cm-save-btn-floating {{ $container->status !== 'pending' ? 'cm-disabled' : '' }}"
|
||||||
<div id="cmToast" class="cm-toast">
|
{{ $container->status !== 'pending' ? 'disabled' : '' }}
|
||||||
Changes saved successfully.
|
>
|
||||||
</div>
|
Save Changes
|
||||||
|
</button>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function cmFilterRows() {
|
function cmFilterRows() {
|
||||||
const input = document.getElementById('cmRowSearch');
|
const input = document.getElementById('cmRowSearch');
|
||||||
if (!input) return;
|
if (!input) return;
|
||||||
const filter = input.value.toLowerCase();
|
const filter = input.value.toLowerCase();
|
||||||
const table = document.getElementById('cmExcelTable');
|
const table = document.getElementById('cmExcelTable');
|
||||||
if (!table) return;
|
if (!table) return;
|
||||||
const rows = table.getElementsByTagName('tr');
|
const rows = table.getElementsByTagName('tr');
|
||||||
for (let i = 1; i < rows.length; i++) {
|
for (let i = 1; i < rows.length; i++) {
|
||||||
const cells = rows[i].getElementsByTagName('td');
|
const cells = rows[i].getElementsByTagName('td');
|
||||||
let match = false;
|
let match = false;
|
||||||
for (let j = 0; j < cells.length; j++) {
|
for (let j = 0; j < cells.length; j++) {
|
||||||
const txt = cells[j].textContent || cells[j].innerText;
|
const txt = cells[j].textContent || cells[j].innerText;
|
||||||
if (txt.toLowerCase().indexOf(filter) > -1) {
|
if (txt.toLowerCase().indexOf(filter) > -1) {
|
||||||
match = true;
|
match = true;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
rows[i].style.display = match ? '' : 'none';
|
}
|
||||||
|
rows[i].style.display = match ? '' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const form = document.getElementById('cm-edit-rows-form');
|
||||||
|
const btn = document.getElementById('cmSaveBtnFloating');
|
||||||
|
const toast = document.getElementById('cmToast');
|
||||||
|
const table = document.getElementById('cmExcelTable');
|
||||||
|
|
||||||
|
function showToast(message, isError = false) {
|
||||||
|
if (!toast) return;
|
||||||
|
toast.textContent = message;
|
||||||
|
toast.style.background = isError ? '#b91c1c' : '#0f172a';
|
||||||
|
toast.classList.add('cm-show');
|
||||||
|
setTimeout(() => toast.classList.remove('cm-show'), 2500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNumber(str) {
|
||||||
|
if (!str) return 0;
|
||||||
|
const cleaned = String(str).replace(/,/g, '').trim();
|
||||||
|
const val = parseFloat(cleaned);
|
||||||
|
return isNaN(val) ? 0 : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNumber(val, decimals) {
|
||||||
|
if (isNaN(val)) val = 0;
|
||||||
|
return val.toFixed(decimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
function recalcRow(row) {
|
||||||
|
const inputs = row.querySelectorAll('.cm-cell-input');
|
||||||
|
|
||||||
|
let ctn = 0, qty = 0, ttlQty = 0;
|
||||||
|
let cbm = 0, ttlCbm = 0;
|
||||||
|
let kg = 0, ttlKg = 0;
|
||||||
|
let price = 0, amount = 0;
|
||||||
|
|
||||||
|
let ctnInput = null,
|
||||||
|
qtyInput = null,
|
||||||
|
ttlQtyInput = null,
|
||||||
|
cbmInput = null,
|
||||||
|
ttlCbmInput = null,
|
||||||
|
kgInput = null,
|
||||||
|
ttlKgInput = null,
|
||||||
|
priceInput = null,
|
||||||
|
amountInput = null;
|
||||||
|
|
||||||
|
inputs.forEach(inp => {
|
||||||
|
const val = parseNumber(inp.value);
|
||||||
|
|
||||||
|
if (inp.dataset.ctn === '1') {
|
||||||
|
ctn = val;
|
||||||
|
ctnInput = inp;
|
||||||
|
} else if (inp.dataset.qty === '1') {
|
||||||
|
qty = val;
|
||||||
|
qtyInput = inp;
|
||||||
|
} else if (inp.dataset.ttlqty === '1') {
|
||||||
|
ttlQty = val;
|
||||||
|
ttlQtyInput = inp;
|
||||||
|
} else if (inp.dataset.cbm === '1') {
|
||||||
|
cbm = val;
|
||||||
|
cbmInput = inp;
|
||||||
|
} else if (inp.dataset.ttlcbm === '1') {
|
||||||
|
ttlCbm = val;
|
||||||
|
ttlCbmInput = inp;
|
||||||
|
} else if (inp.dataset.kg === '1') {
|
||||||
|
kg = val;
|
||||||
|
kgInput = inp;
|
||||||
|
} else if (inp.dataset.ttlkg === '1') {
|
||||||
|
ttlKg = val;
|
||||||
|
ttlKgInput = inp;
|
||||||
|
} else if (inp.dataset.price === '1') {
|
||||||
|
price = val;
|
||||||
|
priceInput = inp;
|
||||||
|
} else if (inp.dataset.amount === '1') {
|
||||||
|
amount = val;
|
||||||
|
amountInput = inp;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ttlQtyInput && ctnInput && qtyInput) {
|
||||||
|
const newTtlQty = ctn * qty;
|
||||||
|
ttlQtyInput.value = formatNumber(newTtlQty, 0);
|
||||||
|
ttlQty = newTtlQty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ttlCbmInput && cbmInput && ctnInput) {
|
||||||
|
const newTtlCbm = cbm * ctn;
|
||||||
|
ttlCbmInput.value = formatNumber(newTtlCbm, 3);
|
||||||
|
ttlCbm = newTtlCbm;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ttlKgInput && kgInput && ctnInput) {
|
||||||
|
const newTtlKg = kg * ctn;
|
||||||
|
ttlKgInput.value = formatNumber(newTtlKg, 2);
|
||||||
|
ttlKg = newTtlKg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountInput && priceInput && ttlQtyInput) {
|
||||||
|
const newAmount = price * ttlQty;
|
||||||
|
amountInput.value = formatNumber(newAmount, 2);
|
||||||
|
amount = newAmount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
if (table) {
|
||||||
const form = document.getElementById('cm-edit-rows-form');
|
table.addEventListener('input', function (e) {
|
||||||
const btn = document.getElementById('cmSaveBtnFloating');
|
const target = e.target;
|
||||||
const toast = document.getElementById('cmToast');
|
if (!target.classList.contains('cm-cell-input')) return;
|
||||||
const table = document.getElementById('cmExcelTable');
|
|
||||||
|
|
||||||
function showToast(message, isError = false) {
|
// readonly / non-pending cells साठी block
|
||||||
if (!toast) return;
|
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
|
||||||
toast.textContent = message;
|
target.blur();
|
||||||
toast.style.background = isError ? '#b91c1c' : '#0f172a';
|
return;
|
||||||
toast.classList.add('cm-show');
|
|
||||||
setTimeout(() => toast.classList.remove('cm-show'), 2500);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseNumber(str) {
|
|
||||||
if (!str) return 0;
|
|
||||||
const cleaned = String(str).replace(/,/g, '').trim();
|
|
||||||
const val = parseFloat(cleaned);
|
|
||||||
return isNaN(val) ? 0 : val;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatNumber(val, decimals) {
|
|
||||||
if (isNaN(val)) val = 0;
|
|
||||||
return val.toFixed(decimals);
|
|
||||||
}
|
|
||||||
|
|
||||||
function recalcRow(row) {
|
|
||||||
const inputs = row.querySelectorAll('.cm-cell-input');
|
|
||||||
|
|
||||||
let ctn = 0, qty = 0, ttlQty = 0;
|
|
||||||
let cbm = 0, ttlCbm = 0;
|
|
||||||
let kg = 0, ttlKg = 0;
|
|
||||||
let price = 0, amount = 0;
|
|
||||||
|
|
||||||
let ctnInput = null,
|
|
||||||
qtyInput = null,
|
|
||||||
ttlQtyInput = null,
|
|
||||||
cbmInput = null,
|
|
||||||
ttlCbmInput = null,
|
|
||||||
kgInput = null,
|
|
||||||
ttlKgInput = null,
|
|
||||||
priceInput = null,
|
|
||||||
amountInput = null;
|
|
||||||
|
|
||||||
inputs.forEach(inp => {
|
|
||||||
const val = parseNumber(inp.value);
|
|
||||||
|
|
||||||
if (inp.dataset.ctn === '1') {
|
|
||||||
ctn = val;
|
|
||||||
ctnInput = inp;
|
|
||||||
} else if (inp.dataset.qty === '1') {
|
|
||||||
qty = val;
|
|
||||||
qtyInput = inp;
|
|
||||||
} else if (inp.dataset.ttlqty === '1') {
|
|
||||||
ttlQty = val;
|
|
||||||
ttlQtyInput = inp;
|
|
||||||
} else if (inp.dataset.cbm === '1') {
|
|
||||||
cbm = val;
|
|
||||||
cbmInput = inp;
|
|
||||||
} else if (inp.dataset.ttlcbm === '1') {
|
|
||||||
ttlCbm = val;
|
|
||||||
ttlCbmInput = inp;
|
|
||||||
} else if (inp.dataset.kg === '1') {
|
|
||||||
kg = val;
|
|
||||||
kgInput = inp;
|
|
||||||
} else if (inp.dataset.ttlkg === '1') {
|
|
||||||
ttlKg = val;
|
|
||||||
ttlKgInput = inp;
|
|
||||||
} else if (inp.dataset.price === '1') {
|
|
||||||
price = val;
|
|
||||||
priceInput = inp;
|
|
||||||
} else if (inp.dataset.amount === '1') {
|
|
||||||
amount = val;
|
|
||||||
amountInput = inp;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ttlQtyInput && ctnInput && qtyInput) {
|
|
||||||
const newTtlQty = ctn * qty;
|
|
||||||
ttlQtyInput.value = formatNumber(newTtlQty, 0);
|
|
||||||
ttlQty = newTtlQty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ttlCbmInput && cbmInput && ctnInput) {
|
const row = target.closest('tr');
|
||||||
const newTtlCbm = cbm * ctn;
|
if (row) {
|
||||||
ttlCbmInput.value = formatNumber(newTtlCbm, 3);
|
recalcRow(row);
|
||||||
ttlCbm = newTtlCbm;
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form && btn) {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
// जर बटण आधीच disabled असेल (non-pending status किंवा processing)
|
||||||
|
if (btn.classList.contains('cm-disabled') || btn.hasAttribute('disabled')) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ttlKgInput && kgInput && ctnInput) {
|
btn.classList.add('cm-disabled');
|
||||||
const newTtlKg = kg * ctn;
|
const formData = new FormData(form);
|
||||||
ttlKgInput.value = formatNumber(newTtlKg, 2);
|
|
||||||
ttlKg = newTtlKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amountInput && priceInput && ttlQtyInput) {
|
fetch(form.action, {
|
||||||
const newAmount = price * ttlQty;
|
method: 'POST',
|
||||||
amountInput.value = formatNumber(newAmount, 2);
|
headers: {
|
||||||
amount = newAmount;
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
}
|
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value
|
||||||
}
|
},
|
||||||
|
body: formData
|
||||||
if (table) {
|
})
|
||||||
table.addEventListener('input', function (e) {
|
.then(async res => {
|
||||||
const target = e.target;
|
if (!res.ok) {
|
||||||
if (!target.classList.contains('cm-cell-input')) return;
|
const text = await res.text();
|
||||||
|
throw new Error(text || 'Failed to save');
|
||||||
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
|
}
|
||||||
target.blur();
|
return res.json().catch(() => ({}));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const row = target.closest('tr');
|
|
||||||
if (row) {
|
|
||||||
recalcRow(row);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (form && btn) {
|
|
||||||
btn.addEventListener('click', function () {
|
|
||||||
btn.classList.add('cm-disabled');
|
|
||||||
const formData = new FormData(form);
|
|
||||||
|
|
||||||
fetch(form.action, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
|
||||||
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value
|
|
||||||
},
|
|
||||||
body: formData
|
|
||||||
})
|
})
|
||||||
.then(async res => {
|
.then(() => {
|
||||||
if (!res.ok) {
|
showToast('Changes saved successfully.');
|
||||||
const text = await res.text();
|
})
|
||||||
throw new Error(text || 'Failed to save');
|
.catch(() => {
|
||||||
}
|
showToast('Error while saving changes.', true);
|
||||||
return res.json().catch(() => ({}));
|
})
|
||||||
})
|
.finally(() => {
|
||||||
.then(() => {
|
btn.classList.remove('cm-disabled');
|
||||||
showToast('Changes saved successfully.');
|
});
|
||||||
})
|
});
|
||||||
.catch(() => {
|
}
|
||||||
showToast('Error while saving changes.', true);
|
});
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
btn.classList.remove('cm-disabled');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -170,6 +170,13 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-select option[value="paying"] {
|
||||||
|
background-color: #e0e7ff;
|
||||||
|
color: #4338ca;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.filter-select option[value="all"] {
|
.filter-select option[value="all"] {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
@@ -534,6 +541,10 @@
|
|||||||
background: url('/images/status-bg-overdue.png') !important;
|
background: url('/images/status-bg-overdue.png') !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-paying {
|
||||||
|
background: url('/images/status-bg-paying.png') !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Fallback colors if images don't load - ALL WITH SAME SIZE */
|
/* Fallback colors if images don't load - ALL WITH SAME SIZE */
|
||||||
.badge.badge-paid {
|
.badge.badge-paid {
|
||||||
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
|
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
|
||||||
@@ -554,6 +565,13 @@
|
|||||||
border-color: #8b5cf6 !important;
|
border-color: #8b5cf6 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge.badge-paying {
|
||||||
|
background: linear-gradient(135deg, #e0e7ff, #c7d2fe) !important;
|
||||||
|
color: #4338ca !important;
|
||||||
|
border-color: #6366f1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Entry Button - Centered */
|
/* Entry Button - Centered */
|
||||||
.btn-entry {
|
.btn-entry {
|
||||||
background: linear-gradient(135deg, #3492f8 55%, #1256cc 110%);
|
background: linear-gradient(135deg, #3492f8 55%, #1256cc 110%);
|
||||||
@@ -1277,16 +1295,19 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span class="badge badge-{{ $invoice->status }}">
|
<span class="badge badge-{{ $invoice->status }}">
|
||||||
@if($invoice->status == 'paid')
|
@if($invoice->status == 'paid')
|
||||||
<i class="bi bi-check-circle-fill status-icon"></i>
|
<i class="bi bi-check-circle-fill status-icon"></i>
|
||||||
@elseif($invoice->status == 'pending')
|
@elseif($invoice->status == 'pending')
|
||||||
<i class="bi bi-clock-fill status-icon"></i>
|
<i class="bi bi-clock-fill status-icon"></i>
|
||||||
@elseif($invoice->status == 'overdue')
|
@elseif($invoice->status == 'paying')
|
||||||
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
|
<i class="bi bi-arrow-repeat status-icon"></i> {{-- processing icon --}}
|
||||||
@endif
|
@elseif($invoice->status == 'overdue')
|
||||||
{{ ucfirst($invoice->status) }}
|
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
|
||||||
</span>
|
@endif
|
||||||
|
{{ ucfirst($invoice->status) }}
|
||||||
|
</span>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="date-cell">
|
<td class="date-cell">
|
||||||
@@ -1338,11 +1359,14 @@
|
|||||||
<i class="bi bi-check-circle-fill status-icon"></i>
|
<i class="bi bi-check-circle-fill status-icon"></i>
|
||||||
@elseif($invoice->status == 'pending')
|
@elseif($invoice->status == 'pending')
|
||||||
<i class="bi bi-clock-fill status-icon"></i>
|
<i class="bi bi-clock-fill status-icon"></i>
|
||||||
|
@elseif($invoice->status == 'paying')
|
||||||
|
<i class="bi bi-arrow-repeat status-icon"></i>
|
||||||
@elseif($invoice->status == 'overdue')
|
@elseif($invoice->status == 'overdue')
|
||||||
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
|
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
|
||||||
@endif
|
@endif
|
||||||
{{ ucfirst($invoice->status) }}
|
{{ ucfirst($invoice->status) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mobile-invoice-details">
|
<div class="mobile-invoice-details">
|
||||||
@@ -1677,6 +1701,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
|
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
|
||||||
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
|
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
|
||||||
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
|
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
|
||||||
|
${invoice.status === 'paying' ? '<i class="bi bi-arrow-repeat status-icon"></i>' : ''}
|
||||||
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
|
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -1710,6 +1735,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
|
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
|
||||||
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
|
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
|
||||||
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
|
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
|
||||||
|
${invoice.status === 'paying' ? '<i class="bi bi-arrow-repeat status-icon"></i>' : ''}
|
||||||
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
|
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
@extends('admin.layouts.app')
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
|
|
||||||
@section('page-title', 'Edit Invoice')
|
@section('page-title', 'Edit Invoice')
|
||||||
|
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
@@ -314,7 +312,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container-fluid py-3">
|
<div class="container-fluid py-3">
|
||||||
{{-- Invoice Preview / Overview --}}
|
{{-- Invoice Overview --}}
|
||||||
<div class="glass-card">
|
<div class="glass-card">
|
||||||
<div class="card-header-compact">
|
<div class="card-header-compact">
|
||||||
<h4>
|
<h4>
|
||||||
@@ -323,12 +321,11 @@
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body-compact">
|
<div class="card-body-compact">
|
||||||
{{-- Read-only popup: items price/total cannot be edited here --}}
|
|
||||||
@include('admin.popup_invoice', ['invoice' => $invoice, 'shipment' => $shipment])
|
@include('admin.popup_invoice', ['invoice' => $invoice, 'shipment' => $shipment])
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Edit Invoice Header Details (POST) --}}
|
{{-- Edit Invoice Header --}}
|
||||||
<div class="glass-card">
|
<div class="glass-card">
|
||||||
<div class="card-header-compact">
|
<div class="card-header-compact">
|
||||||
<h4>
|
<h4>
|
||||||
@@ -365,61 +362,16 @@
|
|||||||
required>
|
required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Final Amount (Base) --}}
|
{{-- Final Amount (With GST) – Charge Groups Total --}}
|
||||||
<div class="form-group-compact">
|
<div class="form-group-compact">
|
||||||
<label class="form-label-compact">
|
<label class="form-label-compact">
|
||||||
<i class="fas fa-money-bill-wave"></i> Final Amount (Before GST)
|
<i class="fas fa-money-bill-wave"></i> Final Amount (With GST)
|
||||||
</label>
|
</label>
|
||||||
<input type="number"
|
<input type="number"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
name="final_amount"
|
|
||||||
class="form-control-compact"
|
class="form-control-compact"
|
||||||
value="{{ old('final_amount', $invoice->final_amount) }}"
|
value="{{ $invoice->grand_total_with_charges }}"
|
||||||
required>
|
readonly>
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Tax Type --}}
|
|
||||||
<div class="form-group-compact">
|
|
||||||
<label class="form-label-compact">
|
|
||||||
<i class="fas fa-receipt"></i> Tax Type
|
|
||||||
</label>
|
|
||||||
<div class="d-flex gap-3 mt-1">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input"
|
|
||||||
type="radio"
|
|
||||||
name="tax_type"
|
|
||||||
value="gst"
|
|
||||||
{{ old('tax_type', $invoice->tax_type) === 'gst' ? 'checked' : '' }}>
|
|
||||||
<label class="form-check-label fw-semibold">GST (CGST + SGST)</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input"
|
|
||||||
type="radio"
|
|
||||||
name="tax_type"
|
|
||||||
value="igst"
|
|
||||||
{{ old('tax_type', $invoice->tax_type) === 'igst' ? 'checked' : '' }}>
|
|
||||||
<label class="form-check-label fw-semibold">IGST</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Tax Percentage --}}
|
|
||||||
<div class="form-group-compact">
|
|
||||||
<label class="form-label-compact">
|
|
||||||
<i class="fas fa-percentage"></i> Tax Percentage
|
|
||||||
</label>
|
|
||||||
<input type="number"
|
|
||||||
step="0.01"
|
|
||||||
min="0"
|
|
||||||
max="28"
|
|
||||||
name="tax_percent"
|
|
||||||
class="form-control-compact"
|
|
||||||
value="{{ old('tax_percent',
|
|
||||||
$invoice->tax_type === 'gst'
|
|
||||||
? ($invoice->cgst_percent + $invoice->sgst_percent)
|
|
||||||
: $invoice->igst_percent
|
|
||||||
) }}"
|
|
||||||
required>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Status --}}
|
{{-- Status --}}
|
||||||
@@ -428,16 +380,12 @@
|
|||||||
<i class="fas fa-tasks"></i> Status
|
<i class="fas fa-tasks"></i> Status
|
||||||
</label>
|
</label>
|
||||||
<select name="status" class="form-select-compact" required>
|
<select name="status" class="form-select-compact" required>
|
||||||
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>
|
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>Pending</option>
|
||||||
Pending
|
<option value="paying" {{ old('status', $invoice->status) === 'paying' ? 'selected' : '' }}>Paying</option>
|
||||||
</option>
|
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>Paid</option>
|
||||||
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>
|
<option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>Overdue</option>
|
||||||
Paid
|
|
||||||
</option>
|
|
||||||
<option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>
|
|
||||||
Overdue
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Notes --}}
|
{{-- Notes --}}
|
||||||
@@ -462,43 +410,46 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@php
|
@php
|
||||||
// आता helpers वापरू: totalPaid() आणि remainingAmount()
|
$totalPaid = $invoice->totalPaid();
|
||||||
$totalPaid = $invoice->totalPaid();
|
$remaining = $invoice->remainingAmount();
|
||||||
$remaining = $invoice->remainingAmount();
|
|
||||||
|
// Mixed tax type label from charge groups
|
||||||
|
$taxTypes = $invoice->chargeGroups
|
||||||
|
? $invoice->chargeGroups->pluck('tax_type')->filter()->unique()->values()
|
||||||
|
: collect([]);
|
||||||
|
|
||||||
|
$taxTypeLabel = 'None';
|
||||||
|
|
||||||
|
if ($taxTypes->count() === 1) {
|
||||||
|
if ($taxTypes[0] === 'gst') {
|
||||||
|
$taxTypeLabel = 'GST (CGST + SGST)';
|
||||||
|
} elseif ($taxTypes[0] === 'igst') {
|
||||||
|
$taxTypeLabel = 'IGST';
|
||||||
|
} else {
|
||||||
|
$taxTypeLabel = strtoupper($taxTypes[0]);
|
||||||
|
}
|
||||||
|
} elseif ($taxTypes->count() > 1) {
|
||||||
|
$parts = [];
|
||||||
|
if ($taxTypes->contains('gst')) {
|
||||||
|
$parts[] = 'GST (CGST + SGST)';
|
||||||
|
}
|
||||||
|
if ($taxTypes->contains('igst')) {
|
||||||
|
$parts[] = 'IGST';
|
||||||
|
}
|
||||||
|
$taxTypeLabel = implode(' + ', $parts);
|
||||||
|
}
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
{{-- Amount Breakdown (items + GST + groups) --}}
|
{{-- Amount Breakdown --}}
|
||||||
<div class="amount-breakdown-compact">
|
<div class="amount-breakdown-compact">
|
||||||
<h6 class="fw-bold mb-3 text-dark">
|
<h6 class="fw-bold mb-3 text-dark">
|
||||||
<i class="fas fa-calculator me-2"></i>Amount Breakdown
|
<i class="fas fa-calculator me-2"></i>Amount Breakdown
|
||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
<div class="breakdown-row">
|
|
||||||
<span class="breakdown-label">Total Amount Before Tax</span>
|
|
||||||
<span class="breakdown-value" id="baseAmount">
|
|
||||||
₹{{ number_format($invoice->final_amount, 2) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="breakdown-row">
|
<div class="breakdown-row">
|
||||||
<span class="breakdown-label">Tax Type</span>
|
<span class="breakdown-label">Tax Type</span>
|
||||||
<span class="breakdown-value text-primary">
|
<span class="breakdown-value text-primary">
|
||||||
@if($invoice->tax_type === 'gst')
|
{{ $taxTypeLabel }}
|
||||||
GST (CGST + SGST)
|
|
||||||
@else
|
|
||||||
IGST
|
|
||||||
@endif
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="breakdown-row">
|
|
||||||
<span class="breakdown-label">Tax Percentage</span>
|
|
||||||
<span class="breakdown-value text-primary">
|
|
||||||
@if($invoice->tax_type === 'gst')
|
|
||||||
{{ $invoice->cgst_percent + $invoice->sgst_percent }}%
|
|
||||||
@else
|
|
||||||
{{ $invoice->igst_percent }}%
|
|
||||||
@endif
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -509,15 +460,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="breakdown-row">
|
|
||||||
<span class="breakdown-label">Charge Groups Total</span>
|
|
||||||
<span class="breakdown-value text-info" id="chargeGroupsTotal">
|
|
||||||
₹{{ number_format($invoice->charge_groups_total, 2) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="breakdown-row" style="border-top: 2px solid #e2e8f0; padding-top: 0.75rem;">
|
<div class="breakdown-row" style="border-top: 2px solid #e2e8f0; padding-top: 0.75rem;">
|
||||||
<span class="breakdown-label fw-bold">Grand Total (Items + GST + Groups)</span>
|
<span class="breakdown-label fw-bold">Grand Total (Charges + GST)</span>
|
||||||
<span class="breakdown-value fw-bold text-dark" id="totalInvoiceWithGst">
|
<span class="breakdown-value fw-bold text-dark" id="totalInvoiceWithGst">
|
||||||
₹{{ number_format($invoice->grand_total_with_charges, 2) }}
|
₹{{ number_format($invoice->grand_total_with_charges, 2) }}
|
||||||
</span>
|
</span>
|
||||||
@@ -538,13 +482,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Installment Summary (top cards) --}}
|
{{-- Summary cards --}}
|
||||||
<div class="summary-grid-compact">
|
<div class="summary-grid-compact">
|
||||||
<div class="summary-card-compact total">
|
<div class="summary-card-compact total">
|
||||||
<div class="summary-value-compact text-success" id="totalInvoiceWithGstCard">
|
<div class="summary-value-compact text-success" id="totalInvoiceWithGstCard">
|
||||||
₹{{ number_format($invoice->grand_total_with_charges, 2) }}
|
₹{{ number_format($invoice->grand_total_with_charges, 2) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-label-compact">Grand Total (Items + GST + Groups)</div>
|
<div class="summary-label-compact">Grand Total (Charges + GST)</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-card-compact paid">
|
<div class="summary-card-compact paid">
|
||||||
<div class="summary-value-compact text-primary">
|
<div class="summary-value-compact text-primary">
|
||||||
@@ -789,15 +733,19 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
document.getElementById("remainingAmount").textContent = "₹" + formatINR(data.remaining);
|
document.getElementById("remainingAmount").textContent = "₹" + formatINR(data.remaining);
|
||||||
}
|
}
|
||||||
if (document.getElementById("baseAmount")) {
|
if (document.getElementById("baseAmount")) {
|
||||||
document.getElementById("baseAmount").textContent = "₹" + formatINR(data.baseAmount);
|
document.getElementById("baseAmount").textContent = "₹" + formatINR(data.chargeGroupsTotal);
|
||||||
}
|
}
|
||||||
if (document.getElementById("gstAmount")) {
|
if (document.getElementById("gstAmount")) {
|
||||||
document.getElementById("gstAmount").textContent = "₹" + formatINR(data.gstAmount);
|
document.getElementById("gstAmount").textContent = "₹" + formatINR(data.gstAmount);
|
||||||
}
|
}
|
||||||
// grand total आता finalAmountWithGst नाही, पण API अजून तेच key देत आहे,
|
|
||||||
// त्यामुळे इथे फक्त card आणि breakdown value update करतो:
|
|
||||||
if (document.getElementById("totalInvoiceWithGst")) {
|
if (document.getElementById("totalInvoiceWithGst")) {
|
||||||
document.getElementById("totalInvoiceWithGst").textContent = "₹" + formatINR(data.finalAmountWithGst);
|
document.getElementById("totalInvoiceWithGst").textContent =
|
||||||
|
"₹" + formatINR(data.grandTotal);
|
||||||
|
}
|
||||||
|
const totalCard = document.getElementById("totalInvoiceWithGstCard");
|
||||||
|
if (totalCard) {
|
||||||
|
totalCard.textContent = "₹" + formatINR(data.grandTotal);
|
||||||
}
|
}
|
||||||
|
|
||||||
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
||||||
@@ -858,13 +806,18 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
document.getElementById("remainingAmount").textContent = "₹" + formatINR(data.remaining);
|
document.getElementById("remainingAmount").textContent = "₹" + formatINR(data.remaining);
|
||||||
}
|
}
|
||||||
if (document.getElementById("baseAmount")) {
|
if (document.getElementById("baseAmount")) {
|
||||||
document.getElementById("baseAmount").textContent = "₹" + formatINR(data.baseAmount);
|
document.getElementById("baseAmount").textContent = "₹" + formatINR(data.chargeGroupsTotal);
|
||||||
}
|
}
|
||||||
if (document.getElementById("gstAmount")) {
|
if (document.getElementById("gstAmount")) {
|
||||||
document.getElementById("gstAmount").textContent = "₹" + formatINR(data.gstAmount);
|
document.getElementById("gstAmount").textContent = "₹" + formatINR(data.gstAmount);
|
||||||
}
|
}
|
||||||
if (document.getElementById("totalInvoiceWithGst")) {
|
if (document.getElementById("totalInvoiceWithGst")) {
|
||||||
document.getElementById("totalInvoiceWithGst").textContent = "₹" + formatINR(data.finalAmountWithGst);
|
document.getElementById("totalInvoiceWithGst").textContent =
|
||||||
|
"₹" + formatINR(data.grandTotal);
|
||||||
|
}
|
||||||
|
const totalCard = document.getElementById("totalInvoiceWithGstCard");
|
||||||
|
if (totalCard) {
|
||||||
|
totalCard.textContent = "₹" + formatINR(data.grandTotal);
|
||||||
}
|
}
|
||||||
|
|
||||||
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
||||||
@@ -886,9 +839,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
alert("Something went wrong. Please try again.");
|
alert("Something went wrong. Please try again.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
// Auto due date = invoice date + 10 days
|
||||||
const invoiceDateInput = document.querySelector('input[name="invoice_date"]');
|
const invoiceDateInput = document.querySelector('input[name="invoice_date"]');
|
||||||
const dueDateInput = document.querySelector('input[name="due_date"]');
|
const dueDateInput = document.querySelector('input[name="due_date"]');
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user