Compare commits
11 Commits
d5e9113820
...
dev
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
19d7f423b3 | ||
|
|
0257b68f16 | ||
|
|
785f2564be | ||
|
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ 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
|
||||||
{
|
{
|
||||||
@@ -31,16 +35,15 @@ class AdminAuthController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$credentials = [
|
$credentials = [
|
||||||
$field => $loginInput,
|
$field => $loginInput,
|
||||||
'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')
|
||||||
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, ' . $user->name . '!');
|
->with('success', 'Welcome back, ' . $user->name . '!');
|
||||||
}
|
}
|
||||||
|
|
||||||
return back()->withErrors(['login' => 'Invalid login credentials.']);
|
return back()->withErrors(['login' => 'Invalid login credentials.']);
|
||||||
@@ -51,6 +54,25 @@ 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')->with('success', 'Logged out successfully.');
|
return redirect()->route('admin.login')
|
||||||
|
->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,7 +22,8 @@ class AdminCustomerController extends Controller
|
|||||||
$query = User::with([
|
$query = User::with([
|
||||||
'marks',
|
'marks',
|
||||||
'orders',
|
'orders',
|
||||||
'invoices.installments' // 🔥 IMPORTANT
|
'invoices.installments',
|
||||||
|
'invoices.chargeGroups', // 🔥 for order total calculation
|
||||||
])->orderBy('id', 'desc');
|
])->orderBy('id', 'desc');
|
||||||
|
|
||||||
if (!empty($search)) {
|
if (!empty($search)) {
|
||||||
|
|||||||
@@ -2,33 +2,31 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller; // ⬅️ हे नक्की असू दे
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\InvoiceItem;
|
use App\Models\InvoiceItem;
|
||||||
use App\Models\InvoiceInstallment;
|
|
||||||
use App\Models\InvoiceChargeGroup;
|
use App\Models\InvoiceChargeGroup;
|
||||||
use App\Models\InvoiceChargeGroupItem;
|
use App\Models\InvoiceChargeGroupItem;
|
||||||
use App\Models\User;
|
use App\Models\InvoiceInstallment;
|
||||||
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
|
||||||
{
|
{
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// INVOICE LIST PAGE
|
// INDEX (LIST ALL INVOICES WITH FILTERS)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
// Container relation सह invoices load करतो
|
$query = Invoice::query();
|
||||||
$query = Invoice::with(['items', 'customer', 'container']);
|
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$search = $request->search;
|
$search = $request->search;
|
||||||
$query->where(function ($q) use ($search) {
|
$query->where(function ($q) use ($search) {
|
||||||
$q->where('invoice_number', 'like', "%{$search}%")
|
$q->where('invoice_number', 'like', "%{$search}%")
|
||||||
->orWhere('customer_name', 'like', "%{$search}%");
|
->orWhere('customer_name', 'like', "%{$search}%");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +48,7 @@ class AdminInvoiceController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// POPUP VIEW + CUSTOMER DATA SYNC
|
// POPUP VIEW
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function popup($id)
|
public function popup($id)
|
||||||
{
|
{
|
||||||
@@ -59,16 +57,8 @@ class AdminInvoiceController extends Controller
|
|||||||
'chargeGroups.items',
|
'chargeGroups.items',
|
||||||
])->findOrFail($id);
|
])->findOrFail($id);
|
||||||
|
|
||||||
// demo update असेल तर ठेव/काढ
|
|
||||||
$invoice->update([
|
|
||||||
'customer_email' => 'test@demo.com',
|
|
||||||
'customer_address' => 'TEST ADDRESS',
|
|
||||||
'pincode' => '999999',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$shipment = null;
|
$shipment = null;
|
||||||
|
|
||||||
// आधीच group मध्ये असलेले item ids
|
|
||||||
$groupedItemIds = $invoice->chargeGroups
|
$groupedItemIds = $invoice->chargeGroups
|
||||||
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
|
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
|
||||||
->unique()
|
->unique()
|
||||||
@@ -88,8 +78,29 @@ class AdminInvoiceController extends Controller
|
|||||||
'customer',
|
'customer',
|
||||||
'container',
|
'container',
|
||||||
'chargeGroups.items',
|
'chargeGroups.items',
|
||||||
|
'installments',
|
||||||
])->findOrFail($id);
|
])->findOrFail($id);
|
||||||
|
|
||||||
|
// ✅ Customer details sync
|
||||||
|
if ($invoice->customer) {
|
||||||
|
$needsUpdate = [];
|
||||||
|
|
||||||
|
if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') {
|
||||||
|
$needsUpdate['customer_email'] = $invoice->customer->email;
|
||||||
|
}
|
||||||
|
if (empty($invoice->customer_address) || $invoice->customer_address === 'TEST ADDRESS') {
|
||||||
|
$needsUpdate['customer_address'] = $invoice->customer->address;
|
||||||
|
}
|
||||||
|
if (empty($invoice->pincode) || $invoice->pincode === '999999') {
|
||||||
|
$needsUpdate['pincode'] = $invoice->customer->pincode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($needsUpdate)) {
|
||||||
|
$invoice->update($needsUpdate);
|
||||||
|
$invoice->refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$shipment = null;
|
$shipment = null;
|
||||||
|
|
||||||
$groupedItemIds = $invoice->chargeGroups
|
$groupedItemIds = $invoice->chargeGroups
|
||||||
@@ -104,107 +115,60 @@ class AdminInvoiceController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// UPDATE INVOICE (HEADER LEVEL)
|
// UPDATE INVOICE (HEADER ONLY)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
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);
|
||||||
|
|
||||||
$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',
|
'notes' => 'nullable|string',
|
||||||
'tax_percent' => 'required|numeric|min:0|max:28',
|
|
||||||
'status' => 'required|in:pending,paid,overdue',
|
|
||||||
'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)
|
||||||
{
|
{
|
||||||
Log::info('🟡 Invoice Items Update Request', [
|
Log::info('🟡 Invoice Items Update Request', [
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
'payload' => $request->all(),
|
'payload' => $request->all(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'items' => ['required', 'array'],
|
'items' => ['required', 'array'],
|
||||||
'items.*.price' => ['required', 'numeric', 'min:0'],
|
'items.*.price' => ['required', 'numeric', 'min:0'],
|
||||||
'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();
|
||||||
@@ -212,72 +176,34 @@ class AdminInvoiceController extends Controller
|
|||||||
if (!$item) {
|
if (!$item) {
|
||||||
Log::warning('Invoice item not found or mismatched invoice', [
|
Log::warning('Invoice item not found or mismatched invoice', [
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
'item_id' => $itemId,
|
'item_id' => $itemId,
|
||||||
]);
|
]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$item->price = $itemData['price'];
|
$item->price = $itemData['price'];
|
||||||
$item->ttl_amount = $itemData['ttl_amount'];
|
$item->ttl_amount = $itemData['ttl_amount'];
|
||||||
$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.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// PDF GENERATION
|
// PDF GENERATION (EXISTING - केवळ chargeGroups load ला confirm कर)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function generateInvoicePDF($invoice)
|
public function generateInvoicePDF($invoice)
|
||||||
{
|
{
|
||||||
$invoice->load(['items', 'customer', 'container']);
|
// ✅ यामध्ये chargeGroups आणि installments load कर
|
||||||
|
$invoice->load(['items', 'customer', 'container', 'chargeGroups.items', 'installments']);
|
||||||
$shipment = null;
|
$shipment = null;
|
||||||
|
|
||||||
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
|
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
|
||||||
$folder = public_path('invoices/');
|
$folder = public_path('invoices/');
|
||||||
|
|
||||||
if (!file_exists($folder)) {
|
if (!file_exists($folder)) {
|
||||||
mkdir($folder, 0777, true);
|
mkdir($folder, 0777, true);
|
||||||
@@ -290,13 +216,13 @@ class AdminInvoiceController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$mpdf = new Mpdf([
|
$mpdf = new Mpdf([
|
||||||
'mode' => 'utf-8',
|
'mode' => 'utf-8',
|
||||||
'format' => 'A4',
|
'format' => 'A4',
|
||||||
'default_font' => 'sans-serif',
|
'default_font' => 'sans-serif',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$html = view('admin.pdf.invoice', [
|
$html = view('admin.pdf.invoice', [
|
||||||
'invoice' => $invoice,
|
'invoice' => $invoice,
|
||||||
'shipment' => $shipment,
|
'shipment' => $shipment,
|
||||||
])->render();
|
])->render();
|
||||||
|
|
||||||
@@ -313,51 +239,69 @@ class AdminInvoiceController extends Controller
|
|||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'installment_date' => 'required|date',
|
'installment_date' => 'required|date',
|
||||||
'payment_method' => 'required|string',
|
'payment_method' => 'required|string',
|
||||||
'reference_no' => 'nullable|string',
|
'reference_no' => 'nullable|string',
|
||||||
'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([
|
||||||
'status' => 'error',
|
'status' => 'error',
|
||||||
'message' => 'Installment amount exceeds remaining balance.',
|
'message' => 'Installment amount exceeds remaining balance.',
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
$installment = InvoiceInstallment::create([
|
$installment = InvoiceInstallment::create([
|
||||||
'invoice_id' => $invoice_id,
|
'invoice_id' => $invoice_id,
|
||||||
'installment_date' => $request->installment_date,
|
'installment_date' => $request->installment_date,
|
||||||
'payment_method' => $request->payment_method,
|
'payment_method' => $request->payment_method,
|
||||||
'reference_no' => $request->reference_no,
|
'reference_no' => $request->reference_no,
|
||||||
'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) {
|
$isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay());
|
||||||
$invoice->update(['status' => 'paid']);
|
|
||||||
|
if ($grandTotal > 0 && $newPaid >= $grandTotal) {
|
||||||
|
$newStatus = 'paid';
|
||||||
|
} elseif ($newPaid > 0 && $isOverdue) {
|
||||||
|
$newStatus = 'overdue';
|
||||||
|
} elseif ($newPaid > 0 && !$isOverdue) {
|
||||||
|
$newStatus = 'paying';
|
||||||
|
} elseif ($newPaid <= 0 && $isOverdue) {
|
||||||
|
$newStatus = 'overdue';
|
||||||
|
} else {
|
||||||
|
$newStatus = 'pending';
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
$invoice->update([
|
||||||
'status' => 'success',
|
'payment_method' => $request->payment_method,
|
||||||
'message' => 'Installment added successfully.',
|
'reference_no' => $request->reference_no,
|
||||||
'installment' => $installment,
|
'status' => $newStatus,
|
||||||
'totalPaid' => $newPaid,
|
|
||||||
'gstAmount' => $invoice->gst_amount,
|
|
||||||
'finalAmountWithGst' => $grandTotal, // इथे grand total पाठव
|
|
||||||
'baseAmount' => $invoice->final_amount,
|
|
||||||
'remaining' => max(0, $grandTotal - $newPaid),
|
|
||||||
'isCompleted' => $newPaid >= $grandTotal,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Installment added successfully.',
|
||||||
|
'installment' => $installment,
|
||||||
|
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
|
||||||
|
'gstAmount' => $invoice->gst_amount ?? 0,
|
||||||
|
'grandTotal' => $grandTotal,
|
||||||
|
'totalPaid' => $newPaid,
|
||||||
|
'remaining' => $remaining,
|
||||||
|
'newStatus' => $newStatus,
|
||||||
|
'isCompleted' => $remaining <= 0,
|
||||||
|
'isZero' => $newPaid == 0,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@@ -366,90 +310,195 @@ class AdminInvoiceController extends Controller
|
|||||||
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();
|
||||||
|
|
||||||
$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);
|
||||||
|
|
||||||
|
$isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay());
|
||||||
|
|
||||||
|
if ($grandTotal > 0 && $paidTotal >= $grandTotal) {
|
||||||
|
$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.',
|
||||||
'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,
|
'newStatus' => $newStatus,
|
||||||
|
'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);
|
||||||
|
|
||||||
$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;
|
||||||
|
}
|
||||||
|
|
||||||
$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) {
|
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 सेट करा
|
$invoice->load('chargeGroups');
|
||||||
$chargeGroupsTotal = $invoice->chargeGroups()->sum('total_charge');
|
|
||||||
$grandTotal = $invoice->final_amount_with_gst + $chargeGroupsTotal;
|
|
||||||
|
|
||||||
$invoice->update([
|
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge');
|
||||||
'charge_groups_total' => $chargeGroupsTotal,
|
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst');
|
||||||
'grand_total_with_charges' => $grandTotal,
|
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase;
|
||||||
]);
|
|
||||||
|
|
||||||
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');
|
$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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,23 @@ class AdminOrderController extends Controller
|
|||||||
* ---------------------------*/
|
* ---------------------------*/
|
||||||
public function dashboard()
|
public function dashboard()
|
||||||
{
|
{
|
||||||
$totalOrders = Order::count();
|
// ── Order counts (from Invoice Management / Orders table) ──
|
||||||
$pendingOrders = Order::where('status', 'pending')->count();
|
$totalOrders = Order::count();
|
||||||
$totalShipments = Shipment::count();
|
|
||||||
$totalItems = OrderItem::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');
|
$totalRevenue = Invoice::sum('final_amount_with_gst');
|
||||||
|
|
||||||
|
// ── User / Staff counts ──
|
||||||
$activeCustomers = User::where('status', 'active')->count();
|
$activeCustomers = User::where('status', 'active')->count();
|
||||||
$inactiveCustomers = User::where('status', 'inactive')->count();
|
$inactiveCustomers = User::where('status', 'inactive')->count();
|
||||||
$totalStaff = Admin::where('type', 'staff')->count();
|
$totalStaff = Admin::where('type', 'staff')->count();
|
||||||
@@ -43,8 +55,11 @@ class AdminOrderController extends Controller
|
|||||||
return view('admin.dashboard', compact(
|
return view('admin.dashboard', compact(
|
||||||
'totalOrders',
|
'totalOrders',
|
||||||
'pendingOrders',
|
'pendingOrders',
|
||||||
'totalShipments',
|
'totalContainers',
|
||||||
'totalItems',
|
'totalInvoices',
|
||||||
|
'paidInvoices',
|
||||||
|
'pendingInvoices',
|
||||||
|
'overdueInvoices',
|
||||||
'totalRevenue',
|
'totalRevenue',
|
||||||
'activeCustomers',
|
'activeCustomers',
|
||||||
'inactiveCustomers',
|
'inactiveCustomers',
|
||||||
@@ -90,11 +105,23 @@ class AdminOrderController extends Controller
|
|||||||
->when($request->filled('status'), function ($q) use ($request) {
|
->when($request->filled('status'), function ($q) use ($request) {
|
||||||
$q->where('invoices.status', $request->status);
|
$q->where('invoices.status', $request->status);
|
||||||
})
|
})
|
||||||
->orderByDesc('invoices.invoice_date') // इथे बदल
|
->orderByDesc('invoices.invoice_date')
|
||||||
->orderByDesc('invoices.id') // same-date साठी tie-breaker
|
->orderByDesc('invoices.id')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return view('admin.orders', compact('invoices'));
|
// ── 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'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------
|
/* ---------------------------
|
||||||
|
|||||||
@@ -5,104 +5,197 @@ 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\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
|
||||||
* Display the reports page with joined data
|
public function containerReport(Request $request)
|
||||||
*/
|
|
||||||
// public function index(Request $request)
|
|
||||||
// {
|
|
||||||
/*********************************************************
|
|
||||||
* OLD FLOW (Order + Shipment + Invoice)
|
|
||||||
* फक्त reference साठी ठेवलेला, वापरत नाही.
|
|
||||||
*********************************************************/
|
|
||||||
|
|
||||||
/*
|
|
||||||
$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(...)
|
|
||||||
->orderBy('shipments.shipment_date', 'desc')
|
|
||||||
->get();
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*********************************************************
|
|
||||||
* NEW FLOW (Container + Invoice + MarkList)
|
|
||||||
*********************************************************/
|
|
||||||
|
|
||||||
// $reports = DB::table('invoices')
|
|
||||||
// ->join('containers', 'containers.id', '=', 'invoices.containerid')
|
|
||||||
// ->leftJoin('mark_list', 'mark_list.markno', '=', 'invoices.markno')
|
|
||||||
// ->select(
|
|
||||||
// 'invoices.id as invoicepk',
|
|
||||||
// 'invoices.invoicenumber',
|
|
||||||
// 'invoices.invoicedate',
|
|
||||||
// 'invoices.finalamount',
|
|
||||||
// 'invoices.finalamountwithgst',
|
|
||||||
// 'invoices.gstpercent',
|
|
||||||
// 'invoices.gstamount',
|
|
||||||
// 'invoices.status as invoicestatus',
|
|
||||||
// 'invoices.markno',
|
|
||||||
|
|
||||||
// 'containers.id as containerpk',
|
|
||||||
// 'containers.containernumber',
|
|
||||||
// 'containers.containerdate',
|
|
||||||
// 'containers.containername',
|
|
||||||
|
|
||||||
// 'mark_list.companyname',
|
|
||||||
// 'mark_list.customername'
|
|
||||||
// )
|
|
||||||
// ->orderBy('containers.containerdate', 'desc')
|
|
||||||
// ->get();
|
|
||||||
|
|
||||||
// return view('admin.reports', compact('reports'));
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function index(Request $request)
|
|
||||||
{
|
{
|
||||||
$reports = DB::table('invoices')
|
$reports = $this->buildContainerReportQuery($request)->get();
|
||||||
->join('containers', 'containers.id', '=', 'invoices.container_id')
|
|
||||||
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
|
|
||||||
->select(
|
|
||||||
// INVOICE
|
|
||||||
'invoices.id as invoicepk',
|
|
||||||
'invoices.invoice_number',
|
|
||||||
'invoices.invoice_date',
|
|
||||||
'invoices.final_amount',
|
|
||||||
'invoices.final_amount_with_gst',
|
|
||||||
'invoices.gst_percent',
|
|
||||||
'invoices.gst_amount',
|
|
||||||
'invoices.status as invoicestatus',
|
|
||||||
'invoices.mark_no',
|
|
||||||
|
|
||||||
// CONTAINER
|
|
||||||
'containers.id as containerpk',
|
|
||||||
'containers.container_number',
|
|
||||||
'containers.container_date',
|
|
||||||
'containers.container_name',
|
|
||||||
|
|
||||||
// RAW FIELDS (for reference/debug if needed)
|
|
||||||
'invoices.company_name as inv_company_name',
|
|
||||||
'invoices.customer_name as inv_customer_name',
|
|
||||||
'mark_list.company_name as ml_company_name',
|
|
||||||
'mark_list.customer_name as ml_customer_name',
|
|
||||||
|
|
||||||
// FINAL FIELDS (automatically pick invoice first, else mark_list)
|
|
||||||
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')
|
|
||||||
)
|
|
||||||
->orderBy('invoices.invoice_date', 'desc')
|
|
||||||
->orderBy('invoices.id', '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 . '"',
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
{
|
{
|
||||||
@@ -193,6 +193,7 @@ class ContainerController extends Controller
|
|||||||
'kg_col' => null,
|
'kg_col' => null,
|
||||||
'totalkg_col' => null,
|
'totalkg_col' => null,
|
||||||
'itemno_col' => null,
|
'itemno_col' => null,
|
||||||
|
'shopno_col' => null,
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($header as $colIndex => $headingText) {
|
foreach ($header as $colIndex => $headingText) {
|
||||||
@@ -233,6 +234,11 @@ class ContainerController extends Controller
|
|||||||
strpos($normalized, 'ITEM') !== false
|
strpos($normalized, 'ITEM') !== false
|
||||||
) {
|
) {
|
||||||
$essentialColumns['itemno_col'] = $colIndex;
|
$essentialColumns['itemno_col'] = $colIndex;
|
||||||
|
} elseif (
|
||||||
|
strpos($normalized, 'SHOPNO') !== false ||
|
||||||
|
strpos($normalized, 'SHOP') !== false
|
||||||
|
) {
|
||||||
|
$essentialColumns['shopno_col'] = $colIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,8 +401,8 @@ class ContainerController extends Controller
|
|||||||
|
|
||||||
if (!empty($unmatchedMarks)) {
|
if (!empty($unmatchedMarks)) {
|
||||||
foreach ($cleanedRows as $item) {
|
foreach ($cleanedRows as $item) {
|
||||||
$row = $item['row'];
|
$row = $item['row'];
|
||||||
$offset = $item['offset'];
|
$offset = $item['offset'];
|
||||||
$rowMark = trim((string)($row[$essentialColumns['itemno_col']] ?? ''));
|
$rowMark = trim((string)($row[$essentialColumns['itemno_col']] ?? ''));
|
||||||
|
|
||||||
if ($rowMark === '' || !in_array($rowMark, $unmatchedMarks)) {
|
if ($rowMark === '' || !in_array($rowMark, $unmatchedMarks)) {
|
||||||
@@ -518,17 +524,17 @@ class ContainerController extends Controller
|
|||||||
$firstMark = $rowsForCustomer[0]['mark'];
|
$firstMark = $rowsForCustomer[0]['mark'];
|
||||||
$snap = $markToSnapshot[$firstMark] ?? null;
|
$snap = $markToSnapshot[$firstMark] ?? null;
|
||||||
|
|
||||||
|
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
|
||||||
|
|
||||||
$invoice = new Invoice();
|
$invoice = new Invoice();
|
||||||
$invoice->container_id = $container->id;
|
$invoice->container_id = $container->id;
|
||||||
// $invoice->customer_id = $customerId;
|
$invoice->customer_id = $customerUser->id ?? null;
|
||||||
$invoice->mark_no = $firstMark;
|
$invoice->mark_no = $firstMark;
|
||||||
|
|
||||||
$invoice->invoice_number = $this->generateInvoiceNumber();
|
$invoice->invoice_number = $this->generateInvoiceNumber();
|
||||||
|
|
||||||
// invoice_date = container_date
|
|
||||||
$invoice->invoice_date = $container->container_date;
|
$invoice->invoice_date = $container->container_date;
|
||||||
|
|
||||||
// due_date = container_date + 10 days
|
|
||||||
$invoice->due_date = Carbon::parse($invoice->invoice_date)
|
$invoice->due_date = Carbon::parse($invoice->invoice_date)
|
||||||
->addDays(10)
|
->addDays(10)
|
||||||
->format('Y-m-d');
|
->format('Y-m-d');
|
||||||
@@ -539,15 +545,17 @@ class ContainerController extends Controller
|
|||||||
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
|
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($customerUser) {
|
||||||
|
$invoice->customer_email = $customerUser->email ?? null;
|
||||||
|
$invoice->customer_address = $customerUser->address ?? null;
|
||||||
|
$invoice->pincode = $customerUser->pincode ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
$invoice->final_amount = 0;
|
$invoice->final_amount = 0;
|
||||||
$invoice->gst_percent = 0;
|
$invoice->gst_percent = 0;
|
||||||
$invoice->gst_amount = 0;
|
$invoice->gst_amount = 0;
|
||||||
$invoice->final_amount_with_gst = 0;
|
$invoice->final_amount_with_gst = 0;
|
||||||
|
|
||||||
$invoice->customer_email = null;
|
|
||||||
$invoice->customer_address = null;
|
|
||||||
$invoice->pincode = null;
|
|
||||||
|
|
||||||
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
|
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
|
||||||
$invoice->notes = 'Auto-created from Container ' . $container->container_number
|
$invoice->notes = 'Auto-created from Container ' . $container->container_number
|
||||||
. ' for Mark(s): ' . implode(', ', $uniqueMarks);
|
. ' for Mark(s): ' . implode(', ', $uniqueMarks);
|
||||||
@@ -557,11 +565,10 @@ class ContainerController extends Controller
|
|||||||
$invoice->save();
|
$invoice->save();
|
||||||
$invoiceCount++;
|
$invoiceCount++;
|
||||||
|
|
||||||
$totalAmount = 0;
|
|
||||||
|
|
||||||
foreach ($rowsForCustomer as $item) {
|
foreach ($rowsForCustomer as $item) {
|
||||||
$row = $item['row'];
|
$row = $item['row'];
|
||||||
$offset = $item['offset'];
|
$offset = $item['offset'];
|
||||||
|
$mark = $item['mark']; // ✅ mark_no from Excel
|
||||||
|
|
||||||
$description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
|
$description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
|
||||||
$ctn = $essentialColumns['ctn_col'] !== null ? (int) ($row[$essentialColumns['ctn_col']] ?? 0) : 0;
|
$ctn = $essentialColumns['ctn_col'] !== null ? (int) ($row[$essentialColumns['ctn_col']] ?? 0) : 0;
|
||||||
@@ -575,6 +582,8 @@ class ContainerController extends Controller
|
|||||||
$kg = $essentialColumns['kg_col'] !== null ? (float) ($row[$essentialColumns['kg_col']] ?? 0) : 0;
|
$kg = $essentialColumns['kg_col'] !== null ? (float) ($row[$essentialColumns['kg_col']] ?? 0) : 0;
|
||||||
$ttlKg = $essentialColumns['totalkg_col'] !== null ? (float) ($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg;
|
$ttlKg = $essentialColumns['totalkg_col'] !== null ? (float) ($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg;
|
||||||
|
|
||||||
|
$shopNo = $essentialColumns['shopno_col'] !== null ? ($row[$essentialColumns['shopno_col']] ?? null) : null;
|
||||||
|
|
||||||
$rowIndex = $headerRowIndex + 1 + $offset;
|
$rowIndex = $headerRowIndex + 1 + $offset;
|
||||||
|
|
||||||
InvoiceItem::create([
|
InvoiceItem::create([
|
||||||
@@ -592,18 +601,10 @@ class ContainerController extends Controller
|
|||||||
'ttl_cbm' => $ttlCbm,
|
'ttl_cbm' => $ttlCbm,
|
||||||
'kg' => $kg,
|
'kg' => $kg,
|
||||||
'ttl_kg' => $ttlKg,
|
'ttl_kg' => $ttlKg,
|
||||||
'shop_no' => null,
|
'shop_no' => $shopNo,
|
||||||
|
'mark_no' => $mark, // ✅ save mark_no from Excel
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$totalAmount += $ttlAmount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$invoice->final_amount = $totalAmount;
|
|
||||||
$invoice->gst_percent = 0;
|
|
||||||
$invoice->gst_amount = 0;
|
|
||||||
$invoice->final_amount_with_gst = $totalAmount;
|
|
||||||
|
|
||||||
$invoice->save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$msg = "Container '{$container->container_number}' created with {$savedCount} rows and {$invoiceCount} customer invoice(s).";
|
$msg = "Container '{$container->container_number}' created with {$savedCount} rows and {$invoiceCount} customer invoice(s).";
|
||||||
@@ -613,7 +614,17 @@ class ContainerController extends Controller
|
|||||||
public function show(Container $container)
|
public function show(Container $container)
|
||||||
{
|
{
|
||||||
$container->load('rows');
|
$container->load('rows');
|
||||||
return view('admin.container_show', compact('container'));
|
|
||||||
|
$lockedRowIndexes = \App\Models\Invoice::whereIn('invoices.status', ['paid', 'paying'])
|
||||||
|
->where('invoices.container_id', $container->id)
|
||||||
|
->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id')
|
||||||
|
->pluck('invoice_items.container_row_index')
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return view('admin.container_show', compact('container', 'lockedRowIndexes'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateRows(Request $request, Container $container)
|
public function updateRows(Request $request, Container $container)
|
||||||
@@ -629,14 +640,12 @@ class ContainerController extends Controller
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1) update container_rows.data
|
|
||||||
$data = $row->data ?? [];
|
$data = $row->data ?? [];
|
||||||
foreach ($cols as $colHeader => $value) {
|
foreach ($cols as $colHeader => $value) {
|
||||||
$data[$colHeader] = $value;
|
$data[$colHeader] = $value;
|
||||||
}
|
}
|
||||||
$row->update(['data' => $data]);
|
$row->update(['data' => $data]);
|
||||||
|
|
||||||
// 2) normalize keys
|
|
||||||
$normalizedMap = [];
|
$normalizedMap = [];
|
||||||
foreach ($data as $key => $value) {
|
foreach ($data as $key => $value) {
|
||||||
if ($key === null || $key === '') {
|
if ($key === null || $key === '') {
|
||||||
@@ -648,7 +657,6 @@ class ContainerController extends Controller
|
|||||||
$normalizedMap[$normKey] = $value;
|
$normalizedMap[$normKey] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper: get first numeric value from given keys
|
|
||||||
$getFirstNumeric = function (array $map, array $possibleKeys) {
|
$getFirstNumeric = function (array $map, array $possibleKeys) {
|
||||||
foreach ($possibleKeys as $search) {
|
foreach ($possibleKeys as $search) {
|
||||||
$normSearch = strtoupper($search);
|
$normSearch = strtoupper($search);
|
||||||
@@ -668,23 +676,19 @@ class ContainerController extends Controller
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3) read values – QTY vs TTLQTY separately
|
|
||||||
$ctnKeys = ['CTN', 'CTNS'];
|
$ctnKeys = ['CTN', 'CTNS'];
|
||||||
$qtyKeys = ['QTY', 'PCS', 'PIECES']; // per-carton qty
|
$qtyKeys = ['QTY', 'PCS', 'PIECES'];
|
||||||
$ttlQtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY']; // total qty
|
$ttlQtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY'];
|
||||||
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
|
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
|
||||||
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
|
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
|
||||||
$amountKeys = ['AMOUNT', 'TTLAMOUNT', 'TOTALAMOUNT'];
|
$amountKeys = ['AMOUNT', 'TTLAMOUNT', 'TOTALAMOUNT'];
|
||||||
|
|
||||||
$ctn = $getFirstNumeric($normalizedMap, $ctnKeys);
|
$ctn = $getFirstNumeric($normalizedMap, $ctnKeys);
|
||||||
|
|
||||||
// per carton qty
|
|
||||||
$qty = $getFirstNumeric($normalizedMap, $qtyKeys);
|
$qty = $getFirstNumeric($normalizedMap, $qtyKeys);
|
||||||
|
|
||||||
// total qty direct from TOTALQTY/TTLQTY/ITLQTY
|
|
||||||
$ttlQ = $getFirstNumeric($normalizedMap, $ttlQtyKeys);
|
$ttlQ = $getFirstNumeric($normalizedMap, $ttlQtyKeys);
|
||||||
|
|
||||||
// if total column is 0 then compute ctn * qty
|
|
||||||
if ($ttlQ == 0 && $ctn && $qty) {
|
if ($ttlQ == 0 && $ctn && $qty) {
|
||||||
$ttlQ = $ctn * $qty;
|
$ttlQ = $ctn * $qty;
|
||||||
}
|
}
|
||||||
@@ -707,7 +711,6 @@ class ContainerController extends Controller
|
|||||||
$amount = $price * $ttlQ;
|
$amount = $price * $ttlQ;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) get description
|
|
||||||
$desc = null;
|
$desc = null;
|
||||||
foreach (['DESCRIPTION', 'DESC'] as $dKey) {
|
foreach (['DESCRIPTION', 'DESC'] as $dKey) {
|
||||||
$normD = str_replace([' ', '/', '-', '.'], '', strtoupper($dKey));
|
$normD = str_replace([' ', '/', '-', '.'], '', strtoupper($dKey));
|
||||||
@@ -719,9 +722,31 @@ class ContainerController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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;
|
$rowIndex = $row->row_index;
|
||||||
|
|
||||||
// 5) find linked invoice_items
|
|
||||||
$items = InvoiceItem::where('container_id', $container->id)
|
$items = InvoiceItem::where('container_id', $container->id)
|
||||||
->where('container_row_index', $rowIndex)
|
->where('container_row_index', $rowIndex)
|
||||||
->get();
|
->get();
|
||||||
@@ -733,18 +758,19 @@ class ContainerController extends Controller
|
|||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6) update invoice_items + recalc invoice totals
|
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
$item->description = $desc;
|
$item->description = $desc;
|
||||||
$item->ctn = $ctn;
|
$item->ctn = $ctn;
|
||||||
$item->qty = $qty; // per carton
|
$item->qty = $qty;
|
||||||
$item->ttl_qty = $ttlQ; // total
|
$item->ttl_qty = $ttlQ;
|
||||||
$item->price = $price;
|
$item->price = $price;
|
||||||
$item->ttl_amount = $amount;
|
$item->ttl_amount = $amount;
|
||||||
$item->cbm = $cbm;
|
$item->cbm = $cbm;
|
||||||
$item->ttl_cbm = $ttlC;
|
$item->ttl_cbm = $ttlC;
|
||||||
$item->kg = $kg;
|
$item->kg = $kg;
|
||||||
$item->ttl_kg = $ttlK;
|
$item->ttl_kg = $ttlK;
|
||||||
|
$item->shop_no = $shopNo;
|
||||||
|
$item->mark_no = $markNo; // ✅ update mark_no
|
||||||
$item->save();
|
$item->save();
|
||||||
|
|
||||||
$invoice = $item->invoice;
|
$invoice = $item->invoice;
|
||||||
@@ -859,7 +885,6 @@ class ContainerController extends Controller
|
|||||||
abort(404, 'Excel file not found on record.');
|
abort(404, 'Excel file not found on record.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stored path like "containers/abc.xlsx"
|
|
||||||
$path = $container->excel_file;
|
$path = $container->excel_file;
|
||||||
|
|
||||||
if (!Storage::exists($path)) {
|
if (!Storage::exists($path)) {
|
||||||
@@ -871,73 +896,69 @@ 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 वापरू
|
$container->load('rows');
|
||||||
$container->load('rows');
|
|
||||||
|
|
||||||
// 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()
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class InvoiceItem extends Model
|
|||||||
'ttl_kg',
|
'ttl_kg',
|
||||||
|
|
||||||
'shop_no',
|
'shop_no',
|
||||||
|
'mark_no',
|
||||||
];
|
];
|
||||||
|
|
||||||
/****************************
|
/****************************
|
||||||
|
|||||||
@@ -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'
|
||||||
|
");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
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.
BIN
public/invoices/invoice-INV-2026-000222.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000222.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000225.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000225.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000253.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000253.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000256.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000256.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000257.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000257.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000258.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000258.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000259.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000259.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000260.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000260.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000261.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000261.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000262.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000262.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000263.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000263.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000264.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000264.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000265.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000265.pdf
Normal file
Binary file not shown.
@@ -40,6 +40,72 @@
|
|||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-download-pdf {
|
||||||
|
background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: none !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 12.5px !important;
|
||||||
|
padding: 8px 16px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
box-shadow: 0 4px 14px rgba(76,111,255,0.4) !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
}
|
||||||
|
.cm-download-pdf:hover {
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 6px 20px rgba(76,111,255,0.5) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
opacity: 0.95 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-download-excel {
|
||||||
|
background: linear-gradient(100deg, #10b981 0%, #059669 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: none !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 12.5px !important;
|
||||||
|
padding: 8px 16px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
box-shadow: 0 4px 14px rgba(16,185,129,0.4) !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
}
|
||||||
|
.cm-download-excel:hover {
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 6px 20px rgba(16,185,129,0.5) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
opacity: 0.95 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-back-btn {
|
||||||
|
background: linear-gradient(100deg, #6b7280 0%, #4b5563 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: none !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 12.5px !important;
|
||||||
|
padding: 8px 16px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
box-shadow: 0 4px 14px rgba(107,114,128,0.4) !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
}
|
||||||
|
.cm-back-btn:hover {
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 6px 20px rgba(107,114,128,0.5) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
opacity: 0.95 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.cm-main-card {
|
.cm-main-card {
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -65,7 +131,7 @@
|
|||||||
|
|
||||||
.cm-info-cards-row {
|
.cm-info-cards-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr)); /* 3 cards one row */
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
padding: 14px 18px 8px 18px;
|
padding: 14px 18px 8px 18px;
|
||||||
}
|
}
|
||||||
@@ -132,7 +198,6 @@
|
|||||||
color: #fff7ed;
|
color: #fff7ed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TOTAL BOXES */
|
|
||||||
.cm-total-cards-row {
|
.cm-total-cards-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
@@ -221,7 +286,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-table-scroll-outer {
|
.cm-table-scroll-outer {
|
||||||
margin: 10px 14px 0 14px;
|
margin: 10px 14px 30px 14px;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
border: 1.5px solid #c9a359;
|
border: 1.5px solid #c9a359;
|
||||||
box-shadow: 0 4px 14px rgba(76,111,255,0.18);
|
box-shadow: 0 4px 14px rgba(76,111,255,0.18);
|
||||||
@@ -433,15 +498,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<a href="{{ route('containers.index') }}" class="btn btn-light btn-sm">Back to list</a>
|
<a href="{{ route('containers.index') }}" class="cm-back-btn">
|
||||||
|
<i class="bi bi-arrow-left"></i>
|
||||||
|
Back to list
|
||||||
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('containers.download.pdf', $container->id) }}"
|
<a href="{{ route('containers.download.pdf', $container->id) }}" class="cm-download-pdf">
|
||||||
class="btn btn-sm btn-outline-primary">
|
<i class="bi bi-file-earmark-pdf"></i>
|
||||||
Download PDF
|
Download PDF
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('containers.download.excel', $container->id) }}"
|
<a href="{{ route('containers.download.excel', $container->id) }}" class="cm-download-excel">
|
||||||
class="btn btn-sm btn-outline-success">
|
<i class="bi bi-file-earmark-excel"></i>
|
||||||
Download Excel
|
Download Excel
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -453,7 +521,6 @@
|
|||||||
<h5>Container Information</h5>
|
<h5>Container Information</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- 3 INFO CARDS IN SINGLE ROW --}}
|
|
||||||
<div class="cm-info-cards-row">
|
<div class="cm-info-cards-row">
|
||||||
<div class="cm-info-card cm-card-container">
|
<div class="cm-info-card cm-card-container">
|
||||||
<div class="cm-info-card-icon">
|
<div class="cm-info-card-icon">
|
||||||
@@ -608,7 +675,7 @@
|
|||||||
|
|
||||||
<div class="cm-filter-bar">
|
<div class="cm-filter-bar">
|
||||||
<span class="cm-row-count">
|
<span class="cm-row-count">
|
||||||
Total rows: {{ $container->rows->count() }} Edit cells then click "Save Changes".
|
Total rows: {{ $container->rows->count() }} Edit cells then click "Save Changes".
|
||||||
</span>
|
</span>
|
||||||
<input type="text" id="cmRowSearch" class="cm-filter-input"
|
<input type="text" id="cmRowSearch" class="cm-filter-input"
|
||||||
placeholder="Quick search..." onkeyup="cmFilterRows()">
|
placeholder="Quick search..." onkeyup="cmFilterRows()">
|
||||||
@@ -666,17 +733,20 @@
|
|||||||
$isPrice = (str_contains($norm, 'PRICE') || str_contains($norm, 'RATE'));
|
$isPrice = (str_contains($norm, 'PRICE') || str_contains($norm, 'RATE'));
|
||||||
|
|
||||||
$isAmount = (
|
$isAmount = (
|
||||||
str_contains($norm, 'AMOUNT') ||
|
str_contains($norm, 'AMOUNT') ||
|
||||||
str_contains($norm, 'TTLAMOUNT') ||
|
str_contains($norm, 'TTLAMOUNT') ||
|
||||||
str_contains($norm, 'TOTALAMOUNT')
|
str_contains($norm, 'TOTALAMOUNT')
|
||||||
);
|
);
|
||||||
|
|
||||||
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
|
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
|
||||||
|
$isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
|
||||||
|
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<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,7 +760,7 @@
|
|||||||
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
|
||||||
@@ -706,186 +776,191 @@
|
|||||||
</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
|
||||||
|
|
||||||
|
<div id="cmToast" class="cm-toast"></div>
|
||||||
|
|
||||||
<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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// फक्त number काढण्यासाठी helper, पण cell मध्ये format नाही करणार
|
||||||
|
function parseNumber(str) {
|
||||||
|
if (!str) return 0;
|
||||||
|
const cleaned = String(str).replace(/,/g, '').trim();
|
||||||
|
const val = parseFloat(cleaned);
|
||||||
|
return isNaN(val) ? 0 : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// इथे आपण फक्त VALUE बदलतो, कोणतंही toFixed नाही वापरत
|
||||||
|
if (ttlQtyInput && ctnInput && qtyInput) {
|
||||||
|
const newTtlQty = ctn * qty;
|
||||||
|
ttlQtyInput.value = newTtlQty === 0 ? '' : String(newTtlQty);
|
||||||
|
ttlQty = newTtlQty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ttlCbmInput && cbmInput && ctnInput) {
|
||||||
|
const newTtlCbm = cbm * ctn;
|
||||||
|
ttlCbmInput.value = newTtlCbm === 0 ? '' : String(newTtlCbm);
|
||||||
|
ttlCbm = newTtlCbm;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ttlKgInput && kgInput && ctnInput) {
|
||||||
|
const newTtlKg = kg * ctn;
|
||||||
|
ttlKgInput.value = newTtlKg === 0 ? '' : String(newTtlKg);
|
||||||
|
ttlKg = newTtlKg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountInput && priceInput && ttlQtyInput) {
|
||||||
|
const newAmount = price * ttlQty;
|
||||||
|
amountInput.value = newAmount === 0 ? '' : String(newAmount);
|
||||||
|
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) {
|
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
|
||||||
if (!toast) return;
|
target.blur();
|
||||||
toast.textContent = message;
|
return;
|
||||||
toast.style.background = isError ? '#b91c1c' : '#0f172a';
|
}
|
||||||
toast.classList.add('cm-show');
|
|
||||||
setTimeout(() => toast.classList.remove('cm-show'), 2500);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseNumber(str) {
|
const row = target.closest('tr');
|
||||||
if (!str) return 0;
|
if (row) {
|
||||||
const cleaned = String(str).replace(/,/g, '').trim();
|
recalcRow(row);
|
||||||
const val = parseFloat(cleaned);
|
}
|
||||||
return isNaN(val) ? 0 : val;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatNumber(val, decimals) {
|
if (form && btn) {
|
||||||
if (isNaN(val)) val = 0;
|
btn.addEventListener('click', function () {
|
||||||
return val.toFixed(decimals);
|
if (btn.classList.contains('cm-disabled') || btn.hasAttribute('disabled')) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
function recalcRow(row) {
|
btn.classList.add('cm-disabled');
|
||||||
const inputs = row.querySelectorAll('.cm-cell-input');
|
const formData = new FormData(form);
|
||||||
|
|
||||||
let ctn = 0, qty = 0, ttlQty = 0;
|
fetch(form.action, {
|
||||||
let cbm = 0, ttlCbm = 0;
|
method: 'POST',
|
||||||
let kg = 0, ttlKg = 0;
|
headers: {
|
||||||
let price = 0, amount = 0;
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value
|
||||||
let ctnInput = null,
|
},
|
||||||
qtyInput = null,
|
body: formData
|
||||||
ttlQtyInput = null,
|
})
|
||||||
cbmInput = null,
|
.then(async res => {
|
||||||
ttlCbmInput = null,
|
if (!res.ok) {
|
||||||
kgInput = null,
|
const text = await res.text();
|
||||||
ttlKgInput = null,
|
throw new Error(text || 'Failed to save');
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
return res.json().catch(() => ({}));
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
showToast('Changes saved successfully.');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
showToast('Error while saving changes.', true);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
btn.classList.remove('cm-disabled');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (table) {
|
|
||||||
table.addEventListener('input', function (e) {
|
|
||||||
const target = e.target;
|
|
||||||
if (!target.classList.contains('cm-cell-input')) return;
|
|
||||||
|
|
||||||
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
|
|
||||||
target.blur();
|
|
||||||
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 => {
|
|
||||||
if (!res.ok) {
|
|
||||||
const text = await res.text();
|
|
||||||
throw new Error(text || 'Failed to save');
|
|
||||||
}
|
|
||||||
return res.json().catch(() => ({}));
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
showToast('Changes saved successfully.');
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showToast('Error while saving changes.', true);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
btn.classList.remove('cm-disabled');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -661,7 +661,8 @@
|
|||||||
<th class="table-header">Customer ID</th>
|
<th class="table-header">Customer ID</th>
|
||||||
<th class="table-header">Orders</th>
|
<th class="table-header">Orders</th>
|
||||||
<th class="table-header">Order Total</th>
|
<th class="table-header">Order Total</th>
|
||||||
<th class="table-header">Total Payable</th>
|
<th class="table-header">GST Amount</th>
|
||||||
|
<th class="table-header">Total Paid</th>
|
||||||
<th class="table-header">Remaining</th>
|
<th class="table-header">Remaining</th>
|
||||||
<th class="table-header">Create Date</th>
|
<th class="table-header">Create Date</th>
|
||||||
<th class="table-header">Status</th>
|
<th class="table-header">Status</th>
|
||||||
@@ -672,20 +673,27 @@
|
|||||||
<tbody id="customersTableBody">
|
<tbody id="customersTableBody">
|
||||||
@forelse($customers as $c)
|
@forelse($customers as $c)
|
||||||
@php
|
@php
|
||||||
// Orders = invoice count
|
// 1) Orders = total invoice count
|
||||||
$ordersCount = $c->invoices->count();
|
$ordersCount = $c->invoices->count();
|
||||||
|
|
||||||
// Order Total = items total from all invoices (final_amount)
|
// 2) Order Total = सर्व invoices च्या charge groups चा base total (without GST)
|
||||||
$orderTotal = $c->invoices->sum('final_amount');
|
$orderTotal = $c->invoices->sum(function($invoice) {
|
||||||
|
return $invoice->chargeGroups->sum('total_charge');
|
||||||
|
});
|
||||||
|
|
||||||
// Total payable = grand total with GST + groups
|
// 3) GST Amount = सर्व invoices च्या gst_amount चा sum
|
||||||
$totalPayable = $c->invoices->sum('grand_total_with_charges');
|
$gstTotal = $c->invoices->sum('gst_amount');
|
||||||
|
|
||||||
// Total paid via installments
|
// 3) Total Payable = customer ने किती paid केले (installments sum)
|
||||||
$totalPaid = $c->invoiceInstallments->sum('amount');
|
$totalPaid = $c->invoices->flatMap->installments->sum('amount');
|
||||||
|
|
||||||
// Remaining amount
|
// 4) Remaining = grand_total_with_charges - paid
|
||||||
$remainingAmount = max($totalPayable - $totalPaid, 0);
|
$grandTotal = $c->invoices->sum(function($invoice) {
|
||||||
|
$base = $invoice->chargeGroups->sum('total_charge');
|
||||||
|
$gst = (float)($invoice->gst_amount ?? 0);
|
||||||
|
return $base + $gst;
|
||||||
|
});
|
||||||
|
$remainingAmount = max($grandTotal - $totalPaid, 0);
|
||||||
@endphp
|
@endphp
|
||||||
<tr>
|
<tr>
|
||||||
<td class="customer-info-column">
|
<td class="customer-info-column">
|
||||||
@@ -724,7 +732,13 @@
|
|||||||
|
|
||||||
<td class="total-column">
|
<td class="total-column">
|
||||||
<span class="total-amount">
|
<span class="total-amount">
|
||||||
₹{{ number_format($totalPayable, 2) }}
|
₹{{ number_format($gstTotal, 2) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="total-column">
|
||||||
|
<span class="total-amount">
|
||||||
|
₹{{ number_format($totalPaid, 2) }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@@ -771,7 +785,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="9" class="text-center py-4">
|
<td colspan="10" class="text-center py-4">
|
||||||
<i class="bi bi-people display-4 text-muted d-block mb-2"></i>
|
<i class="bi bi-people display-4 text-muted d-block mb-2"></i>
|
||||||
<span class="text-muted">No customers found.</span>
|
<span class="text-muted">No customers found.</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,30 +1,7 @@
|
|||||||
@extends('admin.layouts.app')
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
@section('page-title', 'Dashboard')
|
@section('page-title', 'Dashboard')
|
||||||
@php
|
|
||||||
use App\Models\Order;
|
|
||||||
use App\Models\OrderItem;
|
|
||||||
use App\Models\Shipment;
|
|
||||||
use App\Models\Invoice;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Admin;
|
|
||||||
|
|
||||||
$totalOrders = Order::count();
|
|
||||||
$pendingOrders = Order::where('status', 'pending')->count();
|
|
||||||
$totalShipments = Shipment::count();
|
|
||||||
$totalItems = OrderItem::count();
|
|
||||||
|
|
||||||
$totalRevenue = Invoice::sum('final_amount_with_gst');
|
|
||||||
|
|
||||||
// USERS (CUSTOMERS)
|
|
||||||
$activeCustomers = User::where('status', 'active')->count();
|
|
||||||
$inactiveCustomers = User::where('status', 'inactive')->count();
|
|
||||||
|
|
||||||
// STAFF (FROM ADMINS TABLE)
|
|
||||||
$totalStaff = Admin::where('type', 'staff')->count();
|
|
||||||
|
|
||||||
$orders = Order::latest()->get();
|
|
||||||
@endphp
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<style>
|
<style>
|
||||||
/* ===== GLOBAL STYLES (From Shipment) ===== */
|
/* ===== GLOBAL STYLES (From Shipment) ===== */
|
||||||
@@ -1235,66 +1212,91 @@ body {
|
|||||||
|
|
||||||
<!-- STATS CARDS -->
|
<!-- STATS CARDS -->
|
||||||
<div class="stats-row-wrap">
|
<div class="stats-row-wrap">
|
||||||
|
|
||||||
|
{{-- Row 1: Total Containers, Active Customers, Total Invoices, Paid Invoices --}}
|
||||||
<div class="stats-row">
|
<div class="stats-row">
|
||||||
<div class="stats-card stats-card-blue">
|
<div class="stats-card stats-card-blue">
|
||||||
<span class="stats-icon">📦</span>
|
<span class="stats-icon">🚢</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Total Shipments</div>
|
<div class="stats-label">Total Containers</div>
|
||||||
<div class="stats-value">{{ $totalShipments }}</div>
|
<div class="stats-value">{{ $totalContainers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-card stats-card-blue">
|
<div class="stats-card stats-card-blue">
|
||||||
<span class="stats-icon">👥</span>
|
<span class="stats-icon">👥</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Active Customers</div>
|
<div class="stats-label">Active Customers</div>
|
||||||
<div class="stats-value">{{ $activeCustomers }}</div>
|
<div class="stats-value">{{ $activeCustomers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-card stats-card-green">
|
<div class="stats-card stats-card-blue">
|
||||||
<span class="stats-icon">💰</span>
|
<span class="stats-icon">🧾</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Total Revenue</div>
|
<div class="stats-label">Total Orders</div>
|
||||||
<div class="stats-value">₹{{ number_format($totalRevenue, 2) }}</div>
|
<div class="stats-value">{{ $totalInvoices }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-card stats-card-red">
|
<div class="stats-card stats-card-green">
|
||||||
<span class="stats-icon">⏳</span>
|
<span class="stats-icon">✅</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Pending Order</div>
|
<div class="stats-label">Paid Invoices</div>
|
||||||
<div class="stats-value">{{ $pendingOrders }}</div>
|
<div class="stats-value">{{ $paidInvoices }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Row 2: Pending Invoices, Total Staff, Inactive Customers, Total Revenue --}}
|
||||||
<div class="stats-row">
|
<div class="stats-row">
|
||||||
<div class="stats-card stats-card-blue">
|
<div class="stats-card stats-card-orng">
|
||||||
<span class="stats-icon">📦</span>
|
<span class="stats-icon">🕐</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Total Orders</div>
|
<div class="stats-label">Pending Orders</div>
|
||||||
<div class="stats-value">{{ $totalOrders }}</div>
|
<div class="stats-value">{{ $pendingInvoices }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-card stats-card-blue">
|
<div class="stats-card stats-card-blue">
|
||||||
<span class="stats-icon">🧑💼</span>
|
<span class="stats-icon">🧑‍💼</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Total Staff</div>
|
<div class="stats-label">Total Staff</div>
|
||||||
<div class="stats-value">{{ $totalStaff }}</div>
|
<div class="stats-value">{{ $totalStaff }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-card stats-card-blue">
|
|
||||||
<span class="stats-icon">📦</span>
|
|
||||||
<div>
|
|
||||||
<div class="stats-label">Total Items</div>
|
|
||||||
<div class="stats-value">{{ $totalItems }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stats-card stats-card-orng">
|
<div class="stats-card stats-card-orng">
|
||||||
<span class="stats-icon">⛔</span>
|
<span class="stats-icon">⛔</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Inactive Customers</div>
|
<div class="stats-label">Inactive Customers</div>
|
||||||
<div class="stats-value">{{ $inactiveCustomers }}</div>
|
<div class="stats-value">{{ $inactiveCustomers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stats-card stats-card-green">
|
||||||
|
<span class="stats-icon">💰</span>
|
||||||
|
<div>
|
||||||
|
<div class="stats-label">Total Revenue</div>
|
||||||
|
<div class="stats-value">₹{{ number_format($totalRevenue, 2) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- COMMENTED OUT --}}
|
||||||
|
{{--
|
||||||
|
<div class="stats-card stats-card-red">
|
||||||
|
<div class="stats-label">Pending Orders</div>
|
||||||
|
<div class="stats-value">{{ $pendingOrders }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats-card stats-card-red">
|
||||||
|
<div class="stats-label">Overdue Invoices</div>
|
||||||
|
<div class="stats-value">{{ $overdueInvoices }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats-card stats-card-blue">
|
||||||
|
<div class="stats-label">Total Orders</div>
|
||||||
|
<div class="stats-value">{{ $totalOrders }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats-card stats-card-blue">
|
||||||
|
<div class="stats-label">Delivered Orders</div>
|
||||||
|
<div class="stats-value">{{ $totalOrders - $pendingOrders }}</div>
|
||||||
|
</div>
|
||||||
|
--}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ORDER MANAGEMENT -->
|
<!-- ORDER MANAGEMENT -->
|
||||||
|
|||||||
@@ -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%);
|
||||||
@@ -1218,7 +1236,7 @@
|
|||||||
<th class="column-header">Customer</th>
|
<th class="column-header">Customer</th>
|
||||||
<th class="column-header">Container</th> {{-- NEW --}}
|
<th class="column-header">Container</th> {{-- NEW --}}
|
||||||
<th class="column-header">Final Amount</th>
|
<th class="column-header">Final Amount</th>
|
||||||
<th class="column-header">GST %</th>
|
<th class="column-header">GST Amount</th>
|
||||||
<th class="column-header">Total w/GST</th>
|
<th class="column-header">Total w/GST</th>
|
||||||
<th class="column-header">Status</th>
|
<th class="column-header">Status</th>
|
||||||
<th class="column-header">Invoice Date</th>
|
<th class="column-header">Invoice Date</th>
|
||||||
@@ -1268,8 +1286,8 @@
|
|||||||
₹{{ number_format($invoice->final_amount, 2) }}
|
₹{{ number_format($invoice->final_amount, 2) }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="gst-cell">
|
<td class="amount-cell">
|
||||||
{{ $invoice->gst_percent }}%
|
₹{{ number_format($invoice->gst_amount, 2) }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="amount-cell">
|
<td class="amount-cell">
|
||||||
@@ -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">
|
||||||
@@ -1355,8 +1379,8 @@
|
|||||||
<span class="mobile-detail-value">₹{{ number_format($invoice->final_amount, 2) }}</span>
|
<span class="mobile-detail-value">₹{{ number_format($invoice->final_amount, 2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-detail-item">
|
<div class="mobile-detail-item">
|
||||||
<span class="mobile-detail-label">GST</span>
|
<span class="mobile-detail-label">GST Amount</span>
|
||||||
<span class="mobile-detail-value">{{ $invoice->gst_percent }}%</span>
|
<span class="mobile-detail-value">₹{{ number_format($invoice->gst_amount, 2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-detail-item">
|
<div class="mobile-detail-item">
|
||||||
<span class="mobile-detail-label">Total</span>
|
<span class="mobile-detail-label">Total</span>
|
||||||
@@ -1670,13 +1694,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
${invoice.container ? (invoice.container.container_number ?? '—') : '—'}
|
${invoice.container ? (invoice.container.container_number ?? '—') : '—'}
|
||||||
</td>
|
</td>
|
||||||
<td class="amount-cell">₹${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
|
<td class="amount-cell">₹${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
|
||||||
<td class="gst-cell">${invoice.gst_percent}%</td>
|
<td class="amount-cell">₹${parseFloat(invoice.gst_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
|
||||||
<td class="amount-cell">₹${parseFloat(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
|
<td class="amount-cell">₹${parseFloat(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge badge-${invoice.status}">
|
<span class="badge badge-${invoice.status}">
|
||||||
${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>
|
||||||
@@ -1730,8 +1756,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<span class="mobile-detail-value">₹${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
|
<span class="mobile-detail-value">₹${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-detail-item">
|
<div class="mobile-detail-item">
|
||||||
<span class="mobile-detail-label">GST</span>
|
<span class="mobile-detail-label">GST Amount</span>
|
||||||
<span class="mobile-detail-value">${invoice.gst_percent}%</span>
|
<span class="mobile-detail-value">₹${parseFloat(invoice.gst_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-detail-item">
|
<div class="mobile-detail-item">
|
||||||
<span class="mobile-detail-label">Total</span>
|
<span class="mobile-detail-label">Total</span>
|
||||||
|
|||||||
@@ -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,237 +321,66 @@
|
|||||||
</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) --}}
|
@if(false)
|
||||||
<div class="glass-card">
|
{{-- Edit Invoice Details card – HIDDEN --}}
|
||||||
<div class="card-header-compact">
|
{{-- तुझा full header edit form इथे hidden ठेवलेला आहे --}}
|
||||||
<h4>
|
@endif
|
||||||
<i class="fas fa-edit me-2"></i>
|
|
||||||
Edit Invoice Details
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body-compact">
|
|
||||||
<form action="{{ route('admin.invoices.update', $invoice->id) }}" method="POST">
|
|
||||||
@csrf
|
|
||||||
|
|
||||||
<div class="form-grid-compact">
|
|
||||||
{{-- Invoice Date --}}
|
|
||||||
<div class="form-group-compact">
|
|
||||||
<label class="form-label-compact">
|
|
||||||
<i class="fas fa-calendar-day"></i> Invoice Date
|
|
||||||
</label>
|
|
||||||
<input type="date"
|
|
||||||
name="invoice_date"
|
|
||||||
class="form-control-compact"
|
|
||||||
value="{{ old('invoice_date', $invoice->invoice_date) }}"
|
|
||||||
required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Due Date --}}
|
|
||||||
<div class="form-group-compact">
|
|
||||||
<label class="form-label-compact">
|
|
||||||
<i class="fas fa-clock"></i> Due Date
|
|
||||||
</label>
|
|
||||||
<input type="date"
|
|
||||||
name="due_date"
|
|
||||||
class="form-control-compact"
|
|
||||||
value="{{ old('due_date', $invoice->due_date) }}"
|
|
||||||
required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Final Amount (Base) --}}
|
|
||||||
<div class="form-group-compact">
|
|
||||||
<label class="form-label-compact">
|
|
||||||
<i class="fas fa-money-bill-wave"></i> Final Amount (Before GST)
|
|
||||||
</label>
|
|
||||||
<input type="number"
|
|
||||||
step="0.01"
|
|
||||||
name="final_amount"
|
|
||||||
class="form-control-compact"
|
|
||||||
value="{{ old('final_amount', $invoice->final_amount) }}"
|
|
||||||
required>
|
|
||||||
</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>
|
|
||||||
|
|
||||||
{{-- Status --}}
|
|
||||||
<div class="form-group-compact">
|
|
||||||
<label class="form-label-compact">
|
|
||||||
<i class="fas fa-tasks"></i> Status
|
|
||||||
</label>
|
|
||||||
<select name="status" class="form-select-compact" required>
|
|
||||||
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>
|
|
||||||
Pending
|
|
||||||
</option>
|
|
||||||
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>
|
|
||||||
Paid
|
|
||||||
</option>
|
|
||||||
<option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>
|
|
||||||
Overdue
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Notes --}}
|
|
||||||
<div class="form-group-compact" style="grid-column: 1 / -1;">
|
|
||||||
<label class="form-label-compact">
|
|
||||||
<i class="fas fa-sticky-note"></i> Notes
|
|
||||||
</label>
|
|
||||||
<textarea name="notes"
|
|
||||||
rows="3"
|
|
||||||
class="form-control-compact"
|
|
||||||
placeholder="Add any additional notes...">{{ old('notes', $invoice->notes) }}</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-end mt-3">
|
|
||||||
<button type="submit" class="btn-success-compact btn-compact">
|
|
||||||
<i class="fas fa-save me-2"></i>Update Invoice
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@php
|
@php
|
||||||
// आता helpers वापरू: totalPaid() आणि remainingAmount()
|
$totalPaid = $invoice->totalPaid();
|
||||||
$totalPaid = $invoice->totalPaid();
|
$remaining = $invoice->remainingAmount();
|
||||||
$remaining = $invoice->remainingAmount();
|
|
||||||
|
$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) --}}
|
@if(false)
|
||||||
<div class="amount-breakdown-compact">
|
{{-- Amount Breakdown HIDDEN --}}
|
||||||
<h6 class="fw-bold mb-3 text-dark">
|
{{-- जुनं breakdown section इथे hidden आहे --}}
|
||||||
<i class="fas fa-calculator me-2"></i>Amount Breakdown
|
@endif
|
||||||
</h6>
|
|
||||||
|
|
||||||
<div class="breakdown-row">
|
{{-- Summary cards --}}
|
||||||
<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">
|
|
||||||
<span class="breakdown-label">Tax Type</span>
|
|
||||||
<span class="breakdown-value text-primary">
|
|
||||||
@if($invoice->tax_type === 'gst')
|
|
||||||
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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="breakdown-row">
|
|
||||||
<span class="breakdown-label">GST Amount</span>
|
|
||||||
<span class="breakdown-value text-warning" id="gstAmount">
|
|
||||||
₹{{ number_format($invoice->gst_amount, 2) }}
|
|
||||||
</span>
|
|
||||||
</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;">
|
|
||||||
<span class="breakdown-label fw-bold">Grand Total (Items + GST + Groups)</span>
|
|
||||||
<span class="breakdown-value fw-bold text-dark" id="totalInvoiceWithGst">
|
|
||||||
₹{{ number_format($invoice->grand_total_with_charges, 2) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="breakdown-row">
|
|
||||||
<span class="breakdown-label text-success">Total Paid</span>
|
|
||||||
<span class="breakdown-value fw-bold text-success" id="paidAmount">
|
|
||||||
₹{{ number_format($totalPaid, 2) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="breakdown-row" style="border-bottom: none;">
|
|
||||||
<span class="breakdown-label text-danger">Remaining</span>
|
|
||||||
<span class="breakdown-value fw-bold text-danger" id="remainingAmount">
|
|
||||||
₹{{ number_format(max(0, $remaining), 2) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Installment Summary (top 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" id="paidAmount">
|
||||||
₹{{ number_format($totalPaid, 2) }}
|
₹{{ number_format($totalPaid, 2) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-label-compact">Total Paid</div>
|
<div class="summary-label-compact">Total Paid</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-card-compact remaining">
|
<div class="summary-card-compact remaining">
|
||||||
<div class="summary-value-compact text-warning">
|
<div class="summary-value-compact text-warning" id="remainingAmount">
|
||||||
₹{{ number_format(max(0, $remaining), 2) }}
|
₹{{ number_format(max(0, $remaining), 2) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-label-compact">Remaining</div>
|
<div class="summary-label-compact">Remaining</div>
|
||||||
@@ -739,87 +566,128 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
headers: {
|
headers: {
|
||||||
"X-CSRF-TOKEN": submitForm.querySelector('input[name="_token"]').value,
|
"X-CSRF-TOKEN": submitForm.querySelector('input[name="_token"]').value,
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
},
|
},
|
||||||
body: new FormData(submitForm)
|
body: new FormData(submitForm)
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(async res => {
|
||||||
.then(data => {
|
let data;
|
||||||
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
|
try {
|
||||||
submitBtn.disabled = false;
|
data = await res.json();
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("Invalid server response.");
|
||||||
|
}
|
||||||
|
|
||||||
if (data.status === "error") {
|
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
|
||||||
alert(data.message);
|
submitBtn.disabled = false;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const table = document.getElementById("installmentTable");
|
if (!res.ok) {
|
||||||
const index = table.rows.length + 1;
|
const msg =
|
||||||
|
(data && data.message) ||
|
||||||
|
(data && data.errors && Object.values(data.errors)[0][0]) ||
|
||||||
|
"Something went wrong.";
|
||||||
|
alert(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById("noInstallmentsMsg")?.classList.add("d-none");
|
if (data.status === "error") {
|
||||||
|
alert(data.message || "Something went wrong.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
table.insertAdjacentHTML("beforeend", `
|
const table = document.getElementById("installmentTable");
|
||||||
<tr data-id="${data.installment.id}">
|
const index = table.querySelectorAll("tr").length + 1;
|
||||||
<td class="fw-bold text-muted">${index}</td>
|
|
||||||
<td>${data.installment.installment_date}</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge-compact bg-primary bg-opacity-10 text-primary">
|
|
||||||
${data.installment.payment_method.toUpperCase()}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
${data.installment.reference_no
|
|
||||||
? `<span class="text-muted">${data.installment.reference_no}</span>`
|
|
||||||
: '<span class="text-muted">-</span>'}
|
|
||||||
</td>
|
|
||||||
<td class="fw-bold text-success">
|
|
||||||
₹${formatINR(data.installment.amount)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button class="btn-danger-compact btn-compact btn-sm deleteInstallment">
|
|
||||||
<i class="fas fa-trash me-1"></i>Delete
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`);
|
|
||||||
|
|
||||||
if (document.getElementById("paidAmount")) {
|
document.getElementById("noInstallmentsMsg")?.classList.add("d-none");
|
||||||
document.getElementById("paidAmount").textContent = "₹" + formatINR(data.totalPaid);
|
|
||||||
}
|
|
||||||
if (document.getElementById("remainingAmount")) {
|
|
||||||
document.getElementById("remainingAmount").textContent = "₹" + formatINR(data.remaining);
|
|
||||||
}
|
|
||||||
if (document.getElementById("baseAmount")) {
|
|
||||||
document.getElementById("baseAmount").textContent = "₹" + formatINR(data.baseAmount);
|
|
||||||
}
|
|
||||||
if (document.getElementById("gstAmount")) {
|
|
||||||
document.getElementById("gstAmount").textContent = "₹" + formatINR(data.gstAmount);
|
|
||||||
}
|
|
||||||
// grand total आता finalAmountWithGst नाही, पण API अजून तेच key देत आहे,
|
|
||||||
// त्यामुळे इथे फक्त card आणि breakdown value update करतो:
|
|
||||||
if (document.getElementById("totalInvoiceWithGst")) {
|
|
||||||
document.getElementById("totalInvoiceWithGst").textContent = "₹" + formatINR(data.finalAmountWithGst);
|
|
||||||
}
|
|
||||||
|
|
||||||
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
const pmMethod = data.installment.payment_method
|
||||||
if (paidCard) paidCard.textContent = "₹" + formatINR(data.totalPaid);
|
? data.installment.payment_method.toUpperCase()
|
||||||
|
: "-";
|
||||||
|
const refNo = data.installment.reference_no
|
||||||
|
? `<span class="text-muted">${data.installment.reference_no}</span>`
|
||||||
|
: '<span class="text-muted">-</span>';
|
||||||
|
|
||||||
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
table.insertAdjacentHTML("beforeend", `
|
||||||
if (remainingCard) remainingCard.textContent = "₹" + formatINR(data.remaining);
|
<tr data-id="${data.installment.id}">
|
||||||
|
<td class="fw-bold text-muted">${index}</td>
|
||||||
|
<td>${data.installment.installment_date}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge-compact bg-primary bg-opacity-10 text-primary">
|
||||||
|
${pmMethod}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>${refNo}</td>
|
||||||
|
<td class="fw-bold text-success">
|
||||||
|
₹${formatINR(data.installment.amount)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn-danger-compact btn-compact btn-sm deleteInstallment">
|
||||||
|
<i class="fas fa-trash me-1"></i>Delete
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
|
||||||
submitForm.reset();
|
if (document.getElementById("paidAmount")) {
|
||||||
|
document.getElementById("paidAmount").textContent = "₹" + formatINR(data.totalPaid);
|
||||||
|
}
|
||||||
|
if (document.getElementById("remainingAmount")) {
|
||||||
|
document.getElementById("remainingAmount").textContent = "₹" + formatINR(data.remaining);
|
||||||
|
}
|
||||||
|
if (document.getElementById("baseAmount")) {
|
||||||
|
document.getElementById("baseAmount").textContent = "₹" + formatINR(data.chargeGroupsTotal);
|
||||||
|
}
|
||||||
|
if (document.getElementById("gstAmount")) {
|
||||||
|
document.getElementById("gstAmount").textContent = "₹" + formatINR(data.gstAmount);
|
||||||
|
}
|
||||||
|
if (document.getElementById("totalInvoiceWithGst")) {
|
||||||
|
document.getElementById("totalInvoiceWithGst").textContent =
|
||||||
|
"₹" + formatINR(data.grandTotal);
|
||||||
|
}
|
||||||
|
const totalCard = document.getElementById("totalInvoiceWithGstCard");
|
||||||
|
if (totalCard) {
|
||||||
|
totalCard.textContent = "₹" + formatINR(data.grandTotal);
|
||||||
|
}
|
||||||
|
|
||||||
if (data.isCompleted && toggleBtn) {
|
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
||||||
toggleBtn.remove();
|
if (paidCard) paidCard.textContent = "₹" + formatINR(data.totalPaid);
|
||||||
formBox.classList.add("d-none");
|
|
||||||
}
|
|
||||||
|
|
||||||
alert(data.message);
|
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
||||||
})
|
if (remainingCard) remainingCard.textContent = "₹" + formatINR(data.remaining);
|
||||||
.catch(() => {
|
|
||||||
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
|
const fsTotal = document.getElementById("finalSummaryTotalPaid");
|
||||||
submitBtn.disabled = false;
|
if (fsTotal) fsTotal.textContent = "₹" + formatINR(data.totalPaid);
|
||||||
alert("Something went wrong. Please try again.");
|
|
||||||
});
|
const fsRemaining = document.getElementById("finalSummaryRemaining");
|
||||||
|
if (fsRemaining) {
|
||||||
|
fsRemaining.textContent = "₹" + formatINR(data.remaining);
|
||||||
|
fsRemaining.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
|
||||||
|
}
|
||||||
|
const fsRemainingLabel = document.getElementById("finalSummaryRemainingLabel");
|
||||||
|
if (fsRemainingLabel) {
|
||||||
|
fsRemainingLabel.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
|
||||||
|
fsRemainingLabel.innerHTML = `<i class="fas fa-${data.remaining > 0 ? 'exclamation-circle' : 'check-circle'} me-1" style="font-size:0.8rem;"></i>Remaining`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.newStatus && typeof updateStatusBadge === "function") {
|
||||||
|
updateStatusBadge(data.newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
submitForm.reset();
|
||||||
|
|
||||||
|
if (data.isCompleted && toggleBtn) {
|
||||||
|
toggleBtn.remove();
|
||||||
|
formBox.classList.add("d-none");
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(data.message || "Installment added successfully.");
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Installment submit error:", err);
|
||||||
|
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
alert(err.message || "Something went wrong. Please try again.");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -843,66 +711,138 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
headers: {
|
headers: {
|
||||||
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
|
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(async res => {
|
||||||
.then(data => {
|
let data;
|
||||||
if (data.status === "success") {
|
try { data = await res.json(); } catch(e) { throw new Error("Invalid server response."); }
|
||||||
row.style.opacity = 0;
|
|
||||||
setTimeout(() => row.remove(), 300);
|
|
||||||
|
|
||||||
if (document.getElementById("paidAmount")) {
|
if (!res.ok || data.status !== "success") {
|
||||||
document.getElementById("paidAmount").textContent = "₹" + formatINR(data.totalPaid);
|
const msg = data && data.message ? data.message : "Something went wrong. Please try again.";
|
||||||
}
|
throw new Error(msg);
|
||||||
if (document.getElementById("remainingAmount")) {
|
}
|
||||||
document.getElementById("remainingAmount").textContent = "₹" + formatINR(data.remaining);
|
|
||||||
}
|
|
||||||
if (document.getElementById("baseAmount")) {
|
|
||||||
document.getElementById("baseAmount").textContent = "₹" + formatINR(data.baseAmount);
|
|
||||||
}
|
|
||||||
if (document.getElementById("gstAmount")) {
|
|
||||||
document.getElementById("gstAmount").textContent = "₹" + formatINR(data.gstAmount);
|
|
||||||
}
|
|
||||||
if (document.getElementById("totalInvoiceWithGst")) {
|
|
||||||
document.getElementById("totalInvoiceWithGst").textContent = "₹" + formatINR(data.finalAmountWithGst);
|
|
||||||
}
|
|
||||||
|
|
||||||
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
row.style.opacity = 0;
|
||||||
if (paidCard) paidCard.textContent = "₹" + formatINR(data.totalPaid);
|
setTimeout(() => row.remove(), 300);
|
||||||
|
|
||||||
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
if (document.getElementById("paidAmount")) {
|
||||||
if (remainingCard) remainingCard.textContent = "₹" + formatINR(data.remaining);
|
document.getElementById("paidAmount").textContent = "₹" + formatINR(data.totalPaid);
|
||||||
|
}
|
||||||
|
if (document.getElementById("remainingAmount")) {
|
||||||
|
document.getElementById("remainingAmount").textContent = "₹" + formatINR(data.remaining);
|
||||||
|
}
|
||||||
|
if (document.getElementById("baseAmount")) {
|
||||||
|
document.getElementById("baseAmount").textContent = "₹" + formatINR(data.chargeGroupsTotal);
|
||||||
|
}
|
||||||
|
if (document.getElementById("gstAmount")) {
|
||||||
|
document.getElementById("gstAmount").textContent = "₹" + formatINR(data.gstAmount);
|
||||||
|
}
|
||||||
|
if (document.getElementById("totalInvoiceWithGst")) {
|
||||||
|
document.getElementById("totalInvoiceWithGst").textContent =
|
||||||
|
"₹" + formatINR(data.grandTotal);
|
||||||
|
}
|
||||||
|
const totalCard = document.getElementById("totalInvoiceWithGstCard");
|
||||||
|
if (totalCard) {
|
||||||
|
totalCard.textContent = "₹" + formatINR(data.grandTotal);
|
||||||
|
}
|
||||||
|
|
||||||
if (data.isZero) {
|
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
||||||
document.getElementById("noInstallmentsMsg")?.classList.remove("d-none");
|
if (paidCard) paidCard.textContent = "₹" + formatINR(data.totalPaid);
|
||||||
|
|
||||||
|
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
||||||
|
if (remainingCard) remainingCard.textContent = "₹" + formatINR(data.remaining);
|
||||||
|
|
||||||
|
const fsTotal = document.getElementById("finalSummaryTotalPaid");
|
||||||
|
if (fsTotal) fsTotal.textContent = "₹" + formatINR(data.totalPaid);
|
||||||
|
|
||||||
|
const fsRemaining = document.getElementById("finalSummaryRemaining");
|
||||||
|
if (fsRemaining) {
|
||||||
|
fsRemaining.textContent = "₹" + formatINR(data.remaining);
|
||||||
|
fsRemaining.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
|
||||||
|
}
|
||||||
|
const fsRemainingLabel = document.getElementById("finalSummaryRemainingLabel");
|
||||||
|
if (fsRemainingLabel) {
|
||||||
|
fsRemainingLabel.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
|
||||||
|
fsRemainingLabel.innerHTML = `<i class="fas fa-${data.remaining > 0 ? 'exclamation-circle' : 'check-circle'} me-1" style="font-size:0.8rem;"></i>Remaining`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.newStatus && typeof updateStatusBadge === "function") {
|
||||||
|
updateStatusBadge(data.newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.isZero) {
|
||||||
|
document.getElementById("noInstallmentsMsg")?.classList.remove("d-none");
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(data.message || "Installment deleted.");
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert(err.message || "Something went wrong. Please try again.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Header AJAX (सध्या card hidden आहे, तरीही ठेवलेलं)
|
||||||
|
const headerForm = document.getElementById('invoiceHeaderForm');
|
||||||
|
const headerBtn = document.getElementById('btnHeaderSave');
|
||||||
|
const headerMsg = document.getElementById('headerUpdateMsg');
|
||||||
|
|
||||||
|
if (headerForm && headerBtn) {
|
||||||
|
headerForm.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
headerMsg.textContent = '';
|
||||||
|
headerBtn.disabled = true;
|
||||||
|
headerBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
|
||||||
|
|
||||||
|
const formData = new FormData(headerForm);
|
||||||
|
|
||||||
|
fetch(headerForm.action, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(async res => {
|
||||||
|
let data;
|
||||||
|
try { data = await res.json(); } catch(e) {}
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
if (data && data.errors) {
|
||||||
|
const firstError = Object.values(data.errors)[0][0] ?? 'Validation error.';
|
||||||
|
throw new Error(firstError);
|
||||||
}
|
}
|
||||||
|
throw new Error(data && data.message ? data.message : 'Failed to update invoice.');
|
||||||
|
}
|
||||||
|
|
||||||
alert(data.message);
|
headerMsg.textContent = 'Invoice header updated.';
|
||||||
} else {
|
headerMsg.classList.remove('text-danger');
|
||||||
alert(data.message || "Something went wrong. Please try again.");
|
headerMsg.classList.add('text-light');
|
||||||
|
|
||||||
|
const status = document.getElementById('statusSelect')?.value;
|
||||||
|
const badge = document.querySelector('.status-badge');
|
||||||
|
if (badge && status) {
|
||||||
|
badge.classList.remove('status-paid','status-pending','status-overdue','status-default');
|
||||||
|
if (status === 'paid') badge.classList.add('status-paid');
|
||||||
|
else if (status === 'pending') badge.classList.add('status-pending');
|
||||||
|
else if (status === 'overdue') badge.classList.add('status-overdue');
|
||||||
|
else badge.classList.add('status-default');
|
||||||
|
|
||||||
|
badge.innerHTML = status.charAt(0).toUpperCase() + status.slice(1);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(err => {
|
||||||
alert("Something went wrong. Please try again.");
|
headerMsg.textContent = err.message || 'Error updating invoice.';
|
||||||
|
headerMsg.classList.remove('text-light');
|
||||||
|
headerMsg.classList.add('text-warning');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
headerBtn.disabled = false;
|
||||||
|
headerBtn.innerHTML = '<i class="fas fa-save me-2"></i>Update Invoice';
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const invoiceDateInput = document.querySelector('input[name="invoice_date"]');
|
|
||||||
const dueDateInput = document.querySelector('input[name="due_date"]');
|
|
||||||
|
|
||||||
if (invoiceDateInput && dueDateInput) {
|
|
||||||
invoiceDateInput.addEventListener('change', function() {
|
|
||||||
const selectedDate = new Date(this.value);
|
|
||||||
|
|
||||||
if (!isNaN(selectedDate.getTime())) {
|
|
||||||
selectedDate.setDate(selectedDate.getDate() + 10);
|
|
||||||
const year = selectedDate.getFullYear();
|
|
||||||
const month = String(selectedDate.getMonth() + 1).padStart(2, '0');
|
|
||||||
const day = String(selectedDate.getDate()).padStart(2, '0');
|
|
||||||
dueDateInput.value = `${year}-${month}-${day}`;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -546,12 +546,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats-container">
|
<div class="stats-container">
|
||||||
@php
|
|
||||||
$totalInvoices = $invoices->count();
|
|
||||||
$paidInvoices = $invoices->where('invoice_status', 'paid')->count();
|
|
||||||
$pendingInvoices = $invoices->where('invoice_status', 'pending')->count();
|
|
||||||
$overdueInvoices = $invoices->where('invoice_status', 'overdue')->count();
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div class="stat-card total">
|
<div class="stat-card total">
|
||||||
<div class="stat-icon-wrap">
|
<div class="stat-icon-wrap">
|
||||||
|
|||||||
@@ -1,272 +1,623 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{{ $invoice->invoice_number }}</title>
|
<title>Invoice #{{ $invoice->invoice_number }}</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', 'Roboto', Arial, sans-serif;
|
font-family: 'DejaVu Sans', sans-serif;
|
||||||
background: #F7FBFC;
|
font-size: 10px;
|
||||||
color: #1A222B;
|
color: #1a202c;
|
||||||
|
line-height: 1.4;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 18px;
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
max-width: 850px;
|
|
||||||
margin: 24px auto 0 auto;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 13px;
|
|
||||||
box-shadow: 0 2px 14px rgba(40,105,160,0.08);
|
|
||||||
padding: 35px 32px 18px 32px;
|
|
||||||
}
|
}
|
||||||
.header {
|
|
||||||
display: flex;
|
/* ── TOP HEADER ── */
|
||||||
justify-content: space-between;
|
.top-header {
|
||||||
align-items: flex-start;
|
|
||||||
border-bottom: 2px solid #E6EBF0;
|
|
||||||
padding-bottom: 13px;
|
|
||||||
}
|
|
||||||
.logo-company {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
.logo {
|
|
||||||
height: 50px;
|
|
||||||
margin-right: 13px;
|
|
||||||
}
|
|
||||||
.company-details {
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
.company-title {
|
|
||||||
font-size: 21px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
.company-sub {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
font-weight: 500;
|
border-bottom: 2px solid #1a202c;
|
||||||
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
.invoice-details {
|
.header-table {
|
||||||
text-align: right;
|
|
||||||
min-width: 220px;
|
|
||||||
}
|
|
||||||
.invoice-title {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 23px;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
.paid-label {
|
|
||||||
margin-top: 8px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
.paid-tag {
|
|
||||||
background: #23BF47;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 4px 16px 4px 22px;
|
|
||||||
font-size: 17px;
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.paid-tag:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 7px;
|
|
||||||
top: 7px;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.paid-date {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #23BF47;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bill-section {
|
|
||||||
background: #F3F7FB;
|
|
||||||
border-radius: 11px;
|
|
||||||
padding: 20px 18px 13px 18px;
|
|
||||||
margin: 28px 0 16px 0;
|
|
||||||
box-shadow: 0 0px 0px #0000;
|
|
||||||
}
|
|
||||||
.bill-title {
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #23355D;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
letter-spacing: 0.3px;
|
|
||||||
}
|
|
||||||
.bill-details {
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin-top: 9px;
|
|
||||||
margin-bottom: 13px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
th {
|
.header-table td {
|
||||||
background: #F6F7F9;
|
vertical-align: middle;
|
||||||
padding: 10px 0;
|
padding: 0;
|
||||||
font-size: 15px;
|
}
|
||||||
color: #6781A6;
|
/* Logo cell — fixed width, logo fits inside */
|
||||||
font-weight: bold;
|
.logo-cell {
|
||||||
border: none;
|
width: 60px;
|
||||||
|
}
|
||||||
|
.logo-cell img {
|
||||||
|
width: 55px;
|
||||||
|
height: 55px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* Company name cell */
|
||||||
|
.title-cell {
|
||||||
|
padding-left: 10px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
td {
|
.company-name {
|
||||||
padding: 7px 0;
|
font-size: 18px;
|
||||||
color: #222;
|
|
||||||
font-size: 15px;
|
|
||||||
border: none;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
tbody tr:not(:last-child) td {
|
|
||||||
border-bottom: 1px solid #E6EBF0;
|
|
||||||
}
|
|
||||||
tbody tr:last-child td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
.totals-row td {
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #23355D;
|
color: #1a202c;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
}
|
}
|
||||||
.gst-row td {
|
.company-subtitle {
|
||||||
font-weight: 500;
|
font-size: 11px;
|
||||||
color: #23BF47;
|
|
||||||
}
|
|
||||||
.total-row td {
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 17px;
|
color: #2d3748;
|
||||||
color: #222;
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
.payment-info {
|
.company-tagline {
|
||||||
margin-top: 24px;
|
font-size: 8px;
|
||||||
margin-bottom: 9px;
|
color: #718096;
|
||||||
font-size: 15px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
.ref-number {
|
|
||||||
font-size: 14px;
|
/* ── INFO TABLE (Customer | Invoice side by side) ── */
|
||||||
color: #6781A6;
|
.info-outer {
|
||||||
margin-bottom: 8px;
|
width: 100%;
|
||||||
margin-top: 2px;
|
border-collapse: collapse;
|
||||||
|
border: 1px solid #cbd5e0;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
.footer {
|
.info-outer td {
|
||||||
border-top: 1.2px solid #E6EBF0;
|
vertical-align: top;
|
||||||
margin-top: 25px;
|
padding: 10px 12px;
|
||||||
padding-top: 12px;
|
}
|
||||||
font-size: 16px;
|
.info-left { width: 50%; }
|
||||||
color: #888;
|
.info-right { width: 50%; border-left: 1px solid #cbd5e0; }
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
color: #718096;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
padding-bottom: 3px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.info-line {
|
||||||
|
font-size: 9px;
|
||||||
|
color: #2d3748;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── STATUS BADGE ── */
|
||||||
|
.badge {
|
||||||
|
display: inline;
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.badge-paid { background: #c6f6d5; color: #22543d; }
|
||||||
|
.badge-pending { background: #fef3c7; color: #92400e; }
|
||||||
|
.badge-paying { background: #dbeafe; color: #1e40af; }
|
||||||
|
.badge-overdue { background: #fee2e2; color: #991b1b; }
|
||||||
|
|
||||||
|
/* ── BILL TO ── */
|
||||||
|
.bill-to-box {
|
||||||
|
border: 1px solid #cbd5e0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background: #f7fafc;
|
||||||
|
}
|
||||||
|
.bill-to-label {
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
color: #718096;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.bill-name { font-size: 12px; font-weight: bold; color: #1a202c; margin-bottom: 2px; }
|
||||||
|
.bill-sub { font-size: 10px; font-weight: bold; color: #2d3748; margin-bottom: 2px; }
|
||||||
|
.bill-detail { font-size: 9px; color: #4a5568; line-height: 1.8; }
|
||||||
|
|
||||||
|
/* ── SUMMARY GRID — 4 boxes per row ── */
|
||||||
|
.summary-wrap { margin-bottom: 12px; }
|
||||||
|
.summary-label {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.summary-outer {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.sbox {
|
||||||
|
width: 25%;
|
||||||
|
border: 1px solid #cbd5e0;
|
||||||
|
padding: 7px 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
.footer strong {
|
.sbox-row1 { background: #ffffff; }
|
||||||
color: #222;
|
.sbox-row2 { background: #f7fafc; }
|
||||||
font-weight: 500;
|
.sbox-lbl {
|
||||||
|
font-size: 7px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
color: #718096;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
.sbox-val {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1a202c;
|
||||||
|
}
|
||||||
|
.sbox-sub {
|
||||||
|
font-size: 7px;
|
||||||
|
color: #a0aec0;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── CHARGE GROUPS ── */
|
||||||
|
.cg-header {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #2d3748;
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
.cg-group-wrap {
|
||||||
|
border: 1px solid #cbd5e0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.cg-sum-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
.cg-sum-table th {
|
||||||
|
background: #edf2f7;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #4a5568;
|
||||||
|
border: 1px solid #cbd5e0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.cg-sum-table td {
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #cbd5e0;
|
||||||
|
color: #2d3748;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
.cg-item-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
.cg-item-table th {
|
||||||
|
background: #f0fff4;
|
||||||
|
padding: 4px 5px;
|
||||||
|
font-size: 7px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #276749;
|
||||||
|
border: 1px solid #c6f6d5;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.cg-item-table td {
|
||||||
|
padding: 4px 5px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
color: #2d3748;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.row-even { background: #f7fafc; }
|
||||||
|
|
||||||
|
/* ── GRAND TOTAL ── */
|
||||||
|
.grand-outer {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.grand-spacer { width: 60%; }
|
||||||
|
.grand-inner { width: 40%; vertical-align: top; }
|
||||||
|
.grand-table { width: 100%; border-collapse: collapse; font-size: 9px; }
|
||||||
|
.grand-table td { padding: 4px 8px; border: 1px solid #e2e8f0; }
|
||||||
|
.g-lbl { text-align: right; font-weight: bold; color: #4a5568; background: #f7fafc; }
|
||||||
|
.g-val { text-align: right; font-weight: bold; color: #1a202c; }
|
||||||
|
.g-total-lbl { text-align: right; font-size: 11px; font-weight: bold; color: #1a202c; background: #ebf8ff; border-top: 2px solid #2b6cb0; }
|
||||||
|
.g-total-val { text-align: right; font-size: 11px; font-weight: bold; color: #1a202c; background: #ebf8ff; border-top: 2px solid #2b6cb0; }
|
||||||
|
|
||||||
|
/* ── INSTALLMENTS ── */
|
||||||
|
.install-section { margin-top: 12px; }
|
||||||
|
.install-header {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
background: #2d3748;
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
.install-table { width: 100%; border-collapse: collapse; font-size: 9px; }
|
||||||
|
.install-table th {
|
||||||
|
background: #edf2f7;
|
||||||
|
padding: 5px 6px;
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #4a5568;
|
||||||
|
border: 1px solid #cbd5e0;
|
||||||
|
}
|
||||||
|
.install-table td { padding: 4px 6px; border: 1px solid #e2e8f0; color: #2d3748; }
|
||||||
|
|
||||||
|
/* ── HELPERS ── */
|
||||||
|
.tr { text-align: right; }
|
||||||
|
.tc { text-align: center; }
|
||||||
|
.bold { font-weight: bold; }
|
||||||
|
|
||||||
|
/* ── FOOTER ── */
|
||||||
|
.footer {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
font-size: 8px;
|
||||||
|
color: #718096;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
|
||||||
<!-- Header Section -->
|
{{-- ══ VARIABLES ══ --}}
|
||||||
<div class="header">
|
@php
|
||||||
<div class="logo-company">
|
$companyName = $invoice->company_name ?: ($invoice->customer->company_name ?? null);
|
||||||
<img src="{{ public_path('images/kent_logo2.png') }}" class="logo">
|
$custName = $invoice->customer_name;
|
||||||
<div class="company-details">
|
$custAddress = $invoice->customer_address ?: ($invoice->customer->address ?? null);
|
||||||
<div class="company-title">{{ $invoice->company_name ?? 'Kent International Pvt. Ltd.' }}</div>
|
$custPincode = $invoice->pincode ?: ($invoice->customer->pincode ?? null);
|
||||||
<div class="company-sub"></div>
|
$custEmail = $invoice->customer_email ?: ($invoice->customer->email ?? null);
|
||||||
{{ $invoice->company_address ?? '123 Business Park, Sector 5' }}<br>
|
$custMobile = $invoice->customer_mobile ?: ($invoice->customer->mobile_no ?? null);
|
||||||
{{ $invoice->company_city ?? 'Gurugram, Haryana 122001' }}<br>
|
@endphp
|
||||||
{{ $invoice->company_country ?? 'India' }}<br>
|
|
||||||
GST: {{ $invoice->company_gst ?? 'GST123456789' }}<br>
|
{{-- ══ 1) HEADER — Logo + Company Name ══ --}}
|
||||||
Email: {{ $invoice->company_email ?? 'billing@kent.com' }}<br>
|
<div class="top-header">
|
||||||
Phone: {{ $invoice->company_phone ?? '+91 124 123 4567' }}
|
<table class="header-table">
|
||||||
|
<tr>
|
||||||
|
{{-- Logo: 55x55px — DomPDF साठी public_path() वापरतो --}}
|
||||||
|
<td class="logo-cell">
|
||||||
|
<img src="{{ public_path('images/kent_logo2.png') }}" alt="Logo" width="55" height="55">
|
||||||
|
</td>
|
||||||
|
{{-- Company Name + Subtitle + Tagline --}}
|
||||||
|
<td class="title-cell">
|
||||||
|
<div class="company-name">KENT</div>
|
||||||
|
<div class="company-subtitle">International Pvt. Ltd.</div>
|
||||||
|
<div class="company-tagline">
|
||||||
|
Address Line 1, City, State – Pincode |
|
||||||
|
Email: info@company.com |
|
||||||
|
Phone: +91 1234567890
|
||||||
</div>
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ══ 2) CUSTOMER | INVOICE ══ --}}
|
||||||
|
<table class="info-outer">
|
||||||
|
<tr>
|
||||||
|
<td class="info-left">
|
||||||
|
<div class="section-title">Customer Details</div>
|
||||||
|
@if($companyName)
|
||||||
|
<div class="info-line bold" style="font-size:11px;">{{ $companyName }}</div>
|
||||||
|
<div class="info-line">{{ $custName }}</div>
|
||||||
|
@else
|
||||||
|
<div class="info-line bold" style="font-size:11px;">{{ $custName }}</div>
|
||||||
|
@endif
|
||||||
|
@if($custAddress)<div class="info-line">{{ $custAddress }}</div>@endif
|
||||||
|
@if($custPincode)<div class="info-line">{{ $custPincode }}</div>@endif
|
||||||
|
@if($custEmail)<div class="info-line">Email: {{ $custEmail }}</div>@endif
|
||||||
|
@if($custMobile)<div class="info-line">Phone: {{ $custMobile }}</div>@endif
|
||||||
|
</td>
|
||||||
|
<td class="info-right">
|
||||||
|
<div class="section-title">Invoice Details</div>
|
||||||
|
<div class="info-line"><strong>Invoice #:</strong> {{ $invoice->invoice_number }}</div>
|
||||||
|
<div class="info-line"><strong>Date:</strong> {{ \Carbon\Carbon::parse($invoice->invoice_date)->format('d M, Y') }}</div>
|
||||||
|
<div class="info-line"><strong>Due Date:</strong> {{ \Carbon\Carbon::parse($invoice->due_date)->format('d M, Y') }}</div>
|
||||||
|
<div class="info-line">
|
||||||
|
<strong>Status:</strong>
|
||||||
|
<span class="badge badge-{{ strtolower($invoice->status) }}">{{ strtoupper($invoice->status) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="invoice-details">
|
<div class="info-line"><strong>GST Type:</strong> {{ strtoupper($invoice->tax_type ?? 'GST') }}</div>
|
||||||
<div class="invoice-title">INVOICE</div>
|
<div class="info-line"><strong>GST %:</strong> {{ number_format($invoice->gst_percent ?? 0, 2) }}%</div>
|
||||||
Invoice #: {{ $invoice->invoice_number }}<br>
|
</td>
|
||||||
Issue Date: {{ date('d M Y', strtotime($invoice->invoice_date)) }}<br>
|
</tr>
|
||||||
Due Date: {{ date('d M Y', strtotime($invoice->due_date)) }}
|
</table>
|
||||||
@if(strtolower($invoice->status) == 'paid')
|
|
||||||
<div class="paid-label">
|
{{-- ══ 3) BILL TO ══ --}}
|
||||||
<span class="paid-tag">Paid</span>
|
<div class="bill-to-box">
|
||||||
</div>
|
<div class="bill-to-label">Bill To</div>
|
||||||
<div class="paid-date">
|
@if($companyName)
|
||||||
Paid on: {{ date('d M Y', strtotime($invoice->paid_date ?? now())) }}
|
<div class="bill-name">{{ $companyName }}</div>
|
||||||
</div>
|
<div class="bill-sub">{{ $custName }}</div>
|
||||||
@else
|
@else
|
||||||
<div class="paid-date" style="color:#d00;">Status: {{ ucfirst($invoice->status) }}</div>
|
<div class="bill-name">{{ $custName }}</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
<div class="bill-detail">
|
||||||
</div>
|
@if($custAddress){{ $custAddress }}<br>@endif
|
||||||
<!-- Bill To Section -->
|
@if($custPincode){{ $custPincode }}<br>@endif
|
||||||
<div class="bill-section">
|
@if($custEmail)Email: {{ $custEmail }}<br>@endif
|
||||||
<div class="bill-title">Bill To</div>
|
@if($custMobile)Phone: {{ $custMobile }}@endif
|
||||||
<div class="bill-details">
|
|
||||||
<strong>{{ $invoice->customer_name }}</strong><br>
|
|
||||||
{{ $invoice->customer_address }}<br>
|
|
||||||
GST: {{ $invoice->customer_gst ?? '-' }}<br>
|
|
||||||
Email: {{ $invoice->customer_email }}<br>
|
|
||||||
Phone: {{ $invoice->customer_mobile }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Items Table -->
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Description</th>
|
|
||||||
<th>Qty</th>
|
|
||||||
<th>Rate (₹)</th>
|
|
||||||
<th>Amount (₹)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($invoice->items as $item)
|
|
||||||
<tr>
|
|
||||||
<td>{{ $item->description }}</td>
|
|
||||||
<td>{{ $item->qty }}</td>
|
|
||||||
<td>{{ number_format($item->price, 0) }}</td>
|
|
||||||
<td>{{ number_format($item->ttl_amount, 0) }}</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
<tr class="totals-row">
|
|
||||||
<td colspan="3" style="text-align:right;">Subtotal:</td>
|
|
||||||
<td>{{ number_format($invoice->subtotal, 0) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="gst-row">
|
|
||||||
<td colspan="3" style="text-align:right;">GST ({{ $invoice->gst_percent }}%):</td>
|
|
||||||
<td>{{ number_format($invoice->gst_amount, 0) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="total-row">
|
|
||||||
<td colspan="3" style="text-align:right;">Total:</td>
|
|
||||||
<td>{{ number_format($invoice->final_amount_with_gst, 0) }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<!-- Payment Info & Reference -->
|
|
||||||
<div class="payment-info">
|
|
||||||
<strong>Payment Method:</strong> {{ $invoice->payment_method ?? 'Bank Transfer' }}
|
|
||||||
</div>
|
|
||||||
<div class="ref-number">
|
|
||||||
Reference Number: {{ $invoice->reference_no ?? "REF123456789" }}
|
|
||||||
</div>
|
|
||||||
<!-- Footer -->
|
|
||||||
<div class="footer">
|
|
||||||
Thank you for your business!<br>
|
|
||||||
For any queries, please contact us at <strong>{{ $invoice->company_email ?? 'billing@kent.com' }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ══ 4) 8 SUMMARY BOXES ══ --}}
|
||||||
|
@php
|
||||||
|
$allGroupItems = $invoice->chargeGroups->flatMap(fn($g) => $g->items);
|
||||||
|
$invoiceItemIds = $allGroupItems->pluck('invoice_item_id')->unique()->values();
|
||||||
|
$invoiceItems = $invoice->items->whereIn('id', $invoiceItemIds);
|
||||||
|
$totalMarkCount = $invoiceItems->pluck('mark_no')->filter()->unique()->count();
|
||||||
|
$totalCtn = $invoiceItems->sum('ctn');
|
||||||
|
$totalQty = $invoiceItems->sum('ttl_qty');
|
||||||
|
$totalCbm = $invoiceItems->sum('ttl_cbm');
|
||||||
|
$totalKg = $invoiceItems->sum('ttl_kg');
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="summary-wrap">
|
||||||
|
<div class="summary-label">Container & Shipment Summary</div>
|
||||||
|
<table class="summary-outer">
|
||||||
|
<tr>
|
||||||
|
<td class="sbox sbox-row1">
|
||||||
|
<div class="sbox-lbl">Container Name</div>
|
||||||
|
<div class="sbox-val">{{ $invoice->container->container_name ?? '—' }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="sbox sbox-row1">
|
||||||
|
<div class="sbox-lbl">Container Date</div>
|
||||||
|
<div class="sbox-val">
|
||||||
|
@if($invoice->container && $invoice->container->container_date)
|
||||||
|
{{ \Carbon\Carbon::parse($invoice->container->container_date)->format('d M, Y') }}
|
||||||
|
@else—@endif
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="sbox sbox-row1">
|
||||||
|
<div class="sbox-lbl">Container No.</div>
|
||||||
|
<div class="sbox-val">{{ $invoice->container->container_number ?? '—' }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="sbox sbox-row1">
|
||||||
|
<div class="sbox-lbl">Total Mark No.</div>
|
||||||
|
<div class="sbox-val">{{ $totalMarkCount }}</div>
|
||||||
|
<div class="sbox-sub">Unique marks</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="sbox sbox-row2">
|
||||||
|
<div class="sbox-lbl">Total CTN</div>
|
||||||
|
<div class="sbox-val">{{ number_format($totalCtn, 0) }}</div>
|
||||||
|
<div class="sbox-sub">Cartons</div>
|
||||||
|
</td>
|
||||||
|
<td class="sbox sbox-row2">
|
||||||
|
<div class="sbox-lbl">Total QTY</div>
|
||||||
|
<div class="sbox-val">{{ number_format($totalQty, 0) }}</div>
|
||||||
|
<div class="sbox-sub">Pieces</div>
|
||||||
|
</td>
|
||||||
|
<td class="sbox sbox-row2">
|
||||||
|
<div class="sbox-lbl">Total CBM</div>
|
||||||
|
<div class="sbox-val">{{ number_format($totalCbm, 3) }}</div>
|
||||||
|
<div class="sbox-sub">Cubic Meter</div>
|
||||||
|
</td>
|
||||||
|
<td class="sbox sbox-row2">
|
||||||
|
<div class="sbox-lbl">Total KG</div>
|
||||||
|
<div class="sbox-val">{{ number_format($totalKg, 2) }}</div>
|
||||||
|
<div class="sbox-sub">Kilograms</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ══ 5) CHARGE GROUPS ══ --}}
|
||||||
|
@if($invoice->chargeGroups && $invoice->chargeGroups->count() > 0)
|
||||||
|
<div style="margin-bottom:14px;">
|
||||||
|
<div class="cg-header">Charge Groups</div>
|
||||||
|
|
||||||
|
@foreach($invoice->chargeGroups as $group)
|
||||||
|
@php
|
||||||
|
$groupItemIds = $group->items->pluck('invoice_item_id')->toArray();
|
||||||
|
$groupInvItems = $invoice->items->whereIn('id', $groupItemIds);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="cg-group-wrap">
|
||||||
|
<table class="cg-sum-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:22%;">Group Name</th>
|
||||||
|
<th style="width:11%;">Basis</th>
|
||||||
|
<th style="width:11%;">Basis Value</th>
|
||||||
|
<th style="width:10%;">Rate</th>
|
||||||
|
<th style="width:13%;" class="tr">Total Charge</th>
|
||||||
|
<th style="width:8%;">GST %</th>
|
||||||
|
<th style="width:9%;">Tax Type</th>
|
||||||
|
<th style="width:16%;" class="tr">Total With GST</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{{ $group->group_name ?? 'Group '.$group->id }}</strong></td>
|
||||||
|
<td>{{ strtoupper($group->basis_type) }}</td>
|
||||||
|
<td>{{ number_format($group->basis_value, 3) }}</td>
|
||||||
|
<td>{{ number_format($group->rate, 2) }}</td>
|
||||||
|
<td class="tr">₹{{ number_format($group->total_charge, 2) }}</td>
|
||||||
|
<td>{{ number_format($group->gst_percent ?? 0, 2) }}%</td>
|
||||||
|
<td>{{ strtoupper($group->tax_type ?? 'GST') }}</td>
|
||||||
|
<td class="tr"><strong>₹{{ number_format($group->total_with_gst, 2) }}</strong></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
@if($groupInvItems->count() > 0)
|
||||||
|
<table class="cg-item-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:3%;">#</th>
|
||||||
|
<th style="width:15%;">Description</th>
|
||||||
|
<th style="width:7%;">Mark No</th>
|
||||||
|
<th style="width:5%;" class="tc">QTY</th>
|
||||||
|
<th style="width:6%;" class="tr">TTL QTY</th>
|
||||||
|
<th style="width:6%;" class="tr">CBM</th>
|
||||||
|
<th style="width:6%;" class="tr">TTL CBM</th>
|
||||||
|
<th style="width:5%;" class="tr">KG</th>
|
||||||
|
<th style="width:6%;" class="tr">TTL KG</th>
|
||||||
|
<th style="width:7%;" class="tr">TTL Amt</th>
|
||||||
|
<th style="width:5%;" class="tr">Rate</th>
|
||||||
|
<th style="width:7%;" class="tr">Total Charge</th>
|
||||||
|
<th style="width:5%;" class="tr">GST %</th>
|
||||||
|
<th style="width:6%;" class="tr">Item GST</th>
|
||||||
|
<th style="width:6%;" class="tr">Item Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($groupInvItems as $idx => $item)
|
||||||
|
@php
|
||||||
|
$groupBasisTotal = $groupInvItems->sum(function($i) use ($group) {
|
||||||
|
return match($group->basis_type) {
|
||||||
|
'ttl_qty' => $i->ttl_qty ?? 0,
|
||||||
|
'ttl_cbm' => $i->ttl_cbm ?? 0,
|
||||||
|
'ttl_kg' => $i->ttl_kg ?? 0,
|
||||||
|
'amount' => $i->ttl_amount ?? 0,
|
||||||
|
default => $i->ttl_amount ?? 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
$itemBasisValue = match($group->basis_type) {
|
||||||
|
'ttl_qty' => $item->ttl_qty ?? 0,
|
||||||
|
'ttl_cbm' => $item->ttl_cbm ?? 0,
|
||||||
|
'ttl_kg' => $item->ttl_kg ?? 0,
|
||||||
|
'amount' => $item->ttl_amount ?? 0,
|
||||||
|
default => $item->ttl_amount ?? 0,
|
||||||
|
};
|
||||||
|
$itemCharge = $groupBasisTotal > 0
|
||||||
|
? ($itemBasisValue / $groupBasisTotal) * $group->total_charge
|
||||||
|
: 0;
|
||||||
|
$gstPct = $group->gst_percent ?? 0;
|
||||||
|
$itemGst = $itemCharge * $gstPct / 100;
|
||||||
|
$itemTotal = $itemCharge + $itemGst;
|
||||||
|
@endphp
|
||||||
|
<tr class="{{ $idx % 2 === 1 ? 'row-even' : '' }}">
|
||||||
|
<td class="tc">{{ $idx + 1 }}</td>
|
||||||
|
<td>{{ $item->description }}</td>
|
||||||
|
<td>{{ $item->mark_no ?? '—' }}</td>
|
||||||
|
<td class="tc">{{ $item->qty ?? 0 }}</td>
|
||||||
|
<td class="tr">{{ number_format($item->ttl_qty ?? 0, 0) }}</td>
|
||||||
|
<td class="tr">{{ number_format($item->cbm ?? 0, 3) }}</td>
|
||||||
|
<td class="tr">{{ number_format($item->ttl_cbm ?? 0, 3) }}</td>
|
||||||
|
<td class="tr">{{ number_format($item->kg ?? 0, 2) }}</td>
|
||||||
|
<td class="tr">{{ number_format($item->ttl_kg ?? 0, 2) }}</td>
|
||||||
|
<td class="tr">₹{{ number_format($item->ttl_amount ?? 0, 2) }}</td>
|
||||||
|
<td class="tr">{{ number_format($group->rate, 2) }}</td>
|
||||||
|
<td class="tr">₹{{ number_format($itemCharge, 2) }}</td>
|
||||||
|
<td class="tr">{{ number_format($gstPct, 2) }}%</td>
|
||||||
|
<td class="tr">₹{{ number_format($itemGst, 2) }}</td>
|
||||||
|
<td class="tr"><strong>₹{{ number_format($itemTotal, 2) }}</strong></td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- ══ GRAND TOTAL ══ --}}
|
||||||
|
<table class="grand-outer">
|
||||||
|
<tr>
|
||||||
|
<td class="grand-spacer"></td>
|
||||||
|
<td class="grand-inner">
|
||||||
|
<table class="grand-table">
|
||||||
|
<tr>
|
||||||
|
<td class="g-lbl">Charge Groups Total:</td>
|
||||||
|
<td class="g-val">₹{{ number_format($invoice->charge_groups_total ?? 0, 2) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="g-lbl">GST Amount ({{ number_format($invoice->gst_percent ?? 0, 2) }}%):</td>
|
||||||
|
<td class="g-val">₹{{ number_format($invoice->gst_amount ?? 0, 2) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="g-total-lbl">Grand Total:</td>
|
||||||
|
<td class="g-total-val">₹{{ number_format($invoice->grand_total_with_charges ?? 0, 2) }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{-- ══ INSTALLMENTS ══ --}}
|
||||||
|
@if($invoice->installments && $invoice->installments->count() > 0)
|
||||||
|
<div class="install-section">
|
||||||
|
<div class="install-header">Payment Installments</div>
|
||||||
|
<table class="install-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:6%;" class="tc">#</th>
|
||||||
|
<th style="width:20%;">Date</th>
|
||||||
|
<th style="width:25%;">Payment Method</th>
|
||||||
|
<th style="width:25%;">Reference No</th>
|
||||||
|
<th style="width:24%;" class="tr">Amount (₹)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($invoice->installments as $idx => $inst)
|
||||||
|
<tr>
|
||||||
|
<td class="tc">{{ $idx + 1 }}</td>
|
||||||
|
<td>{{ \Carbon\Carbon::parse($inst->installment_date)->format('d M, Y') }}</td>
|
||||||
|
<td>{{ strtoupper($inst->payment_method) }}</td>
|
||||||
|
<td>{{ $inst->reference_no ?? '—' }}</td>
|
||||||
|
<td class="tr">₹{{ number_format($inst->amount, 2) }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
@php
|
||||||
|
$totalPaid = $invoice->installments->sum('amount');
|
||||||
|
$grandTotal = $invoice->grand_total_with_charges ?? 0;
|
||||||
|
$remaining = max(0, $grandTotal - $totalPaid);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<table class="grand-outer" style="margin-top:6px;">
|
||||||
|
<tr>
|
||||||
|
<td class="grand-spacer"></td>
|
||||||
|
<td class="grand-inner">
|
||||||
|
<table class="grand-table">
|
||||||
|
<tr>
|
||||||
|
<td class="g-lbl" style="color:#059669;">Total Paid:</td>
|
||||||
|
<td class="g-val" style="color:#059669;">₹{{ number_format($totalPaid, 2) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="g-lbl" style="color:#dc2626;">Remaining:</td>
|
||||||
|
<td class="g-val" style="color:#dc2626;">₹{{ number_format($remaining, 2) }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- ══ FOOTER ══ --}}
|
||||||
|
<div class="footer">
|
||||||
|
Thank you for your business! | For queries: info@company.com | +91 1234567890
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -5,242 +5,421 @@
|
|||||||
@section('content')
|
@section('content')
|
||||||
<div class="container-fluid px-0 bg-transparent">
|
<div class="container-fluid px-0 bg-transparent">
|
||||||
<style>
|
<style>
|
||||||
body {
|
/* ── HEADER ── */
|
||||||
background: linear-gradient(135deg, #f2f6ff, #fefefe);
|
|
||||||
font-family: "Poppins", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* PROFILE HEADER */
|
|
||||||
.profile-header {
|
.profile-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 1rem;
|
gap: 1.5rem;
|
||||||
padding: 36px 32px;
|
padding: 32px 36px;
|
||||||
background: linear-gradient(120deg, #4e73df 68%, #1cc88a 100%);
|
background: linear-gradient(120deg, #4e73df 60%, #1cc88a 100%);
|
||||||
border-radius: 20px;
|
border-radius: 22px;
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: 0 8px 28px rgba(24,40,90,0.09);
|
box-shadow: 0 10px 36px rgba(78,115,223,0.22);
|
||||||
margin-bottom: 34px;
|
margin-bottom: 28px;
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 140px;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.profile-header::before {
|
||||||
|
content:"";position:absolute;top:-60px;right:-60px;
|
||||||
|
width:220px;height:220px;
|
||||||
|
background:rgba(255,255,255,0.08);border-radius:50%;
|
||||||
}
|
}
|
||||||
.profile-header::after {
|
.profile-header::after {
|
||||||
content: "";
|
content:"";position:absolute;bottom:-80px;right:180px;
|
||||||
position: absolute;
|
width:180px;height:180px;
|
||||||
top: -58px;
|
background:rgba(255,255,255,0.05);border-radius:50%;
|
||||||
right: -85px;
|
|
||||||
width: 210px;
|
|
||||||
height: 210px;
|
|
||||||
background: rgba(255,255,255,0.14);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.avatar {
|
|
||||||
width: 98px;
|
|
||||||
height: 98px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 4px solid rgba(255,255,255,0.7);
|
|
||||||
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
|
|
||||||
background: #fff;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.main-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
z-index: 1;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.main-row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 25px;
|
|
||||||
}
|
|
||||||
.field-group {
|
|
||||||
min-width: 210px;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #dbe7f4;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #fff;
|
|
||||||
background: rgba(255,255,255,0.13);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border: 1.5px solid rgba(255,255,255,0.18);
|
|
||||||
transition: background 0.13s;
|
|
||||||
}
|
|
||||||
.value:hover { background: rgba(255,255,255,0.22); }
|
|
||||||
|
|
||||||
/* STATS + CARDS COMMON */
|
|
||||||
.profile-stats-row, .profile-grid-row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 18px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.stat-card, .profile-card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 14px;
|
|
||||||
box-shadow: 0 3px 18px rgba(60,80,120,0.08);
|
|
||||||
padding: 20px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
flex: 1 1 200px;
|
|
||||||
}
|
|
||||||
.stat-card:hover, .profile-card:hover {
|
|
||||||
transform: translateY(-4px);
|
|
||||||
box-shadow: 0 6px 25px rgba(50,70,120,0.18);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* STATS */
|
.avatar-initials {
|
||||||
.stat-card::before {
|
width:100px;height:100px;border-radius:50%;
|
||||||
content: "";
|
border:4px solid rgba(255,255,255,0.75);
|
||||||
position: absolute;
|
box-shadow:0 6px 24px rgba(0,0,0,0.18);
|
||||||
top: -20px;
|
background:#fff;z-index:1;flex-shrink:0;
|
||||||
right: -20px;
|
display:flex;align-items:center;justify-content:center;
|
||||||
width: 90px;
|
font-size:2.6rem;font-weight:800;color:#4e73df;
|
||||||
height: 90px;
|
letter-spacing:-1px;
|
||||||
background: linear-gradient(135deg, #c4d7ff, #e9f0ff);
|
|
||||||
border-radius: 50%;
|
|
||||||
opacity: 0.25;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-info { display:flex;flex-direction:column;gap:14px;z-index:1;flex:1; }
|
||||||
|
.main-row { display:flex;flex-wrap:wrap;gap:18px;align-items:flex-start; }
|
||||||
|
.field-group { min-width:160px; }
|
||||||
|
|
||||||
|
.ph-label {
|
||||||
|
font-size:0.72rem;color:rgba(255,255,255,0.65);
|
||||||
|
font-weight:600;text-transform:uppercase;letter-spacing:0.07em;margin-bottom:3px;
|
||||||
|
}
|
||||||
|
.ph-value {
|
||||||
|
font-size:0.95rem;font-weight:600;color:#fff;
|
||||||
|
background:rgba(255,255,255,0.12);border-radius:9px;
|
||||||
|
padding:6px 13px;border:1.5px solid rgba(255,255,255,0.2);
|
||||||
|
backdrop-filter:blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-badge {
|
||||||
|
display:inline-block;padding:4px 14px;border-radius:50px;
|
||||||
|
font-size:0.72rem;font-weight:700;letter-spacing:0.07em;text-transform:uppercase;
|
||||||
|
}
|
||||||
|
.type-admin { background:rgba(255,210,60,0.22);color:#ffe066;border:1px solid rgba(255,220,80,0.4); }
|
||||||
|
.type-staff { background:rgba(100,255,180,0.18);color:#a0ffda;border:1px solid rgba(100,255,180,0.35); }
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
display:inline-block;width:9px;height:9px;
|
||||||
|
border-radius:50%;margin-right:5px;
|
||||||
|
}
|
||||||
|
.status-active { background:#22c55e;box-shadow:0 0 5px #22c55e; }
|
||||||
|
.status-inactive { background:#ef4444;box-shadow:0 0 5px #ef4444; }
|
||||||
|
|
||||||
|
/* ── STATS ── */
|
||||||
|
.profile-stats-row { display:flex;flex-wrap:wrap;gap:16px;margin-bottom:24px; }
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background:#fff;border-radius:16px;
|
||||||
|
box-shadow:0 3px 18px rgba(60,80,120,0.07);
|
||||||
|
padding:20px 22px;
|
||||||
|
transition:all 0.3s ease;
|
||||||
|
position:relative;
|
||||||
|
flex:1 1 160px;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
.stat-card::after {
|
||||||
|
content:"";position:absolute;
|
||||||
|
top:-25px;right:-25px;
|
||||||
|
width:80px;height:80px;
|
||||||
|
border-radius:50%;
|
||||||
|
opacity:0.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Per-card accent colors */
|
||||||
|
.stat-card.sc-blue { border-top:3px solid #4e73df; }
|
||||||
|
.stat-card.sc-blue::after { background:linear-gradient(135deg,#c4d7ff,#e9f0ff); }
|
||||||
|
|
||||||
|
.stat-card.sc-green { border-top:3px solid #1cc88a; }
|
||||||
|
.stat-card.sc-green::after { background:linear-gradient(135deg,#bbf7d0,#dcfce7); }
|
||||||
|
|
||||||
|
.stat-card.sc-yellow { border-top:3px solid #f6c23e; }
|
||||||
|
.stat-card.sc-yellow::after { background:linear-gradient(135deg,#fef9c3,#fef08a); }
|
||||||
|
|
||||||
|
.stat-card.sc-red { border-top:3px solid #e74a3b; }
|
||||||
|
.stat-card.sc-red::after { background:linear-gradient(135deg,#fee2e2,#fecaca); }
|
||||||
|
|
||||||
|
.stat-card.sc-purple { border-top:3px solid #8b5cf6; }
|
||||||
|
.stat-card.sc-purple::after { background:linear-gradient(135deg,#ede9fe,#ddd6fe); }
|
||||||
|
|
||||||
|
.stat-card.sc-teal { border-top:3px solid #0ea5e9; }
|
||||||
|
.stat-card.sc-teal::after { background:linear-gradient(135deg,#e0f2fe,#bae6fd); }
|
||||||
|
|
||||||
|
.stat-card:hover { transform:translateY(-5px);box-shadow:0 10px 30px rgba(78,115,223,0.15); }
|
||||||
|
|
||||||
|
.stat-icon-wrap {
|
||||||
|
width:42px;height:42px;border-radius:11px;
|
||||||
|
display:flex;align-items:center;justify-content:center;
|
||||||
|
font-size:1.25rem;margin-bottom:10px;
|
||||||
|
}
|
||||||
|
.sc-blue .stat-icon-wrap { background:#eff6ff; }
|
||||||
|
.sc-green .stat-icon-wrap { background:#f0fdf4; }
|
||||||
|
.sc-yellow .stat-icon-wrap { background:#fefce8; }
|
||||||
|
.sc-red .stat-icon-wrap { background:#fef2f2; }
|
||||||
|
.sc-purple .stat-icon-wrap { background:#faf5ff; }
|
||||||
|
.sc-teal .stat-icon-wrap { background:#f0f9ff; }
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 0.9rem;
|
font-size:0.78rem;color:#8b96ad;font-weight:600;
|
||||||
color: #8b96ad;
|
text-transform:uppercase;letter-spacing:0.06em;
|
||||||
font-weight: 500;
|
margin-bottom:4px;display:block;
|
||||||
}
|
}
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 1.6rem;
|
font-size:1.7rem;font-weight:800;color:#1b2330;
|
||||||
font-weight: 700;
|
line-height:1;margin-bottom:6px;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #1b2330;
|
|
||||||
}
|
}
|
||||||
.stat-footer { font-size: 0.9rem; }
|
.stat-footer { font-size:0.78rem;color:#94a3b8; }
|
||||||
.positive { color: #23a25d; }
|
.positive { color:#16a34a; }
|
||||||
.negative { color: #e54141; }
|
.negative { color:#dc2626; }
|
||||||
.neutral { color: #9a9ea8; }
|
|
||||||
|
|
||||||
/* PROFILE CARDS */
|
/* ── PROFESSIONAL INFO ── */
|
||||||
|
.pro-section { margin-bottom:26px; }
|
||||||
|
.pro-section-title {
|
||||||
|
font-size:0.88rem;font-weight:700;color:#1b2330;
|
||||||
|
border-left:4px solid #4e73df;padding-left:9px;margin-bottom:14px;
|
||||||
|
display:flex;align-items:center;gap:7px;
|
||||||
|
}
|
||||||
|
.pro-grid { display:flex;flex-wrap:wrap;gap:14px; }
|
||||||
|
.pro-box {
|
||||||
|
background:#fff;border-radius:14px;
|
||||||
|
padding:16px 20px;
|
||||||
|
box-shadow:0 2px 14px rgba(60,80,120,0.07);
|
||||||
|
border:1px solid #e8edf5;
|
||||||
|
transition:all 0.25s cubic-bezier(.4,0,.2,1);
|
||||||
|
flex:1 1 160px;min-width:0;
|
||||||
|
position:relative;overflow:hidden;
|
||||||
|
}
|
||||||
|
.pro-box::before {
|
||||||
|
content:"";
|
||||||
|
position:absolute;top:0;left:0;right:0;height:3px;
|
||||||
|
background:linear-gradient(90deg,#4e73df,#1cc88a);
|
||||||
|
opacity:0;transition:opacity 0.25s;
|
||||||
|
}
|
||||||
|
.pro-box:hover { transform:translateY(-4px);box-shadow:0 8px 28px rgba(78,115,223,0.15);border-color:#c7d4f8; }
|
||||||
|
.pro-box:hover::before { opacity:1; }
|
||||||
|
|
||||||
|
.box-icon { font-size:1.3rem;margin-bottom:7px;display:inline-block; }
|
||||||
|
.box-label { font-size:0.68rem;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:4px; }
|
||||||
|
.box-value { font-size:0.95rem;font-weight:700;color:#1b2330;white-space:nowrap;overflow:hidden;text-overflow:ellipsis; }
|
||||||
|
|
||||||
|
/* ── BOTTOM CARDS ── */
|
||||||
|
.profile-grid-row { display:flex;flex-wrap:wrap;gap:18px;margin-bottom:10px; }
|
||||||
|
.profile-card {
|
||||||
|
background:#fff;border-radius:16px;
|
||||||
|
box-shadow:0 3px 18px rgba(60,80,120,0.07);
|
||||||
|
padding:22px;transition:all 0.3s ease;flex:1 1 220px;
|
||||||
|
}
|
||||||
|
.profile-card:hover { transform:translateY(-4px);box-shadow:0 8px 28px rgba(78,115,223,0.14); }
|
||||||
.profile-card h6 {
|
.profile-card h6 {
|
||||||
font-size: 1.1rem;
|
font-size:1rem;font-weight:700;margin-bottom:14px;color:#1b2330;
|
||||||
font-weight: 600;
|
border-left:4px solid #4e73df;padding-left:9px;
|
||||||
margin-bottom: 12px;
|
|
||||||
color: #1b2330;
|
|
||||||
border-left: 4px solid #3767f4;
|
|
||||||
padding-left: 8px;
|
|
||||||
}
|
}
|
||||||
.activity-list, .quick-action-list {
|
.activity-list,.quick-action-list { list-style:none;padding:0;margin:0; }
|
||||||
list-style: none;
|
.activity-list li,.quick-action-list li {
|
||||||
padding: 0;
|
padding:9px 6px;font-size:0.92rem;border-bottom:1px solid #f2f4f8;
|
||||||
margin: 0;
|
display:flex;align-items:center;gap:8px;transition:0.2s ease;border-radius:6px;
|
||||||
}
|
}
|
||||||
.activity-list li, .quick-action-list li {
|
.activity-list li:hover { background:#f4f7ff;padding-left:10px; }
|
||||||
padding: 9px 2px;
|
|
||||||
font-size: 1rem;
|
|
||||||
border-bottom: 1px solid #f2f4f8;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
transition: 0.2s ease;
|
|
||||||
}
|
|
||||||
.activity-list li:hover { background: #f7faff; border-radius: 6px; }
|
|
||||||
.quick-action-list li a {
|
.quick-action-list li a {
|
||||||
text-decoration: none;
|
text-decoration:none;color:#2563eb;font-weight:600;
|
||||||
color: #2563eb;
|
display:flex;align-items:center;gap:8px;transition:0.2s ease;width:100%;
|
||||||
font-weight: 600;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
transition: 0.2s ease;
|
|
||||||
}
|
}
|
||||||
.quick-action-list li a:hover { color: #1e40af; transform: translateX(4px); }
|
.quick-action-list li a:hover { color:#1e40af;transform:translateX(5px); }
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
@media (max-width:900px) {
|
||||||
.profile-header, .profile-stats-row, .profile-grid-row {
|
.profile-header,.profile-stats-row,.profile-grid-row { flex-direction:column; }
|
||||||
flex-direction: column;
|
.avatar-initials { margin:0 auto; }
|
||||||
text-align: center;
|
.main-row { justify-content:center; }
|
||||||
}
|
.pro-grid { flex-direction:column; }
|
||||||
.avatar { margin: 0 auto 15px; }
|
.pro-box { flex:unset;width:100%; }
|
||||||
.main-row { justify-content: center; }
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- PROFILE HEADER -->
|
@php
|
||||||
|
$user = $user ?? Auth::guard('admin')->user();
|
||||||
|
$isAdmin = $user->type === 'admin';
|
||||||
|
$initials = strtoupper(substr($user->name ?? 'U', 0, 1));
|
||||||
|
$joiningFormatted = $user->joining_date
|
||||||
|
? \Carbon\Carbon::parse($user->joining_date)->format('d M Y')
|
||||||
|
: '—';
|
||||||
|
$isActive = ($user->status ?? 'active') === 'active';
|
||||||
|
|
||||||
|
// stats — controller मधून येतात, fallback 0
|
||||||
|
$s = $stats ?? [];
|
||||||
|
$totalContainers = $s['total_containers'] ?? 0;
|
||||||
|
$totalInvoices = $s['total_invoices'] ?? 0;
|
||||||
|
$paidInvoices = $s['paid_invoices'] ?? 0;
|
||||||
|
$pendingInvoices = $s['pending_invoices'] ?? 0;
|
||||||
|
$totalCustomers = $s['total_customers'] ?? 0;
|
||||||
|
$totalMarklist = $s['total_marklist'] ?? 0;
|
||||||
|
$activeMarklist = $s['active_marklist'] ?? 0;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
{{-- ════════ PROFILE HEADER ════════ --}}
|
||||||
<div class="profile-header">
|
<div class="profile-header">
|
||||||
<img src="https://i.pravatar.cc/100" class="avatar" alt="Avatar" />
|
<div class="avatar-initials">{{ $initials }}</div>
|
||||||
|
|
||||||
<div class="main-info">
|
<div class="main-info">
|
||||||
|
{{-- Row 1 --}}
|
||||||
<div class="main-row">
|
<div class="main-row">
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="label">Name</div>
|
<div class="ph-label">Full Name</div>
|
||||||
<div class="value">John Doe</div>
|
<div class="ph-value" style="font-size:1.05rem;">{{ $user->name ?? '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="label">Email</div>
|
<div class="ph-label">Account Type</div>
|
||||||
<div class="value">john@example.com</div>
|
<div class="ph-value" style="padding:4px 10px;">
|
||||||
|
<span class="type-badge {{ $isAdmin ? 'type-admin' : 'type-staff' }}">
|
||||||
|
{{ $isAdmin ? '⭐ Admin' : '👤 Staff' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="label">Mobile No</div>
|
<div class="ph-label">Status</div>
|
||||||
<div class="value">+91 12345 54321</div>
|
<div class="ph-value">
|
||||||
|
<span class="status-dot {{ $isActive ? 'status-active' : 'status-inactive' }}"></span>
|
||||||
|
{{ ucfirst($user->status ?? 'active') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if($user->employee_id)
|
||||||
|
<div class="field-group">
|
||||||
|
<div class="ph-label">Employee ID</div>
|
||||||
|
<div class="ph-value" style="font-family:monospace;letter-spacing:0.05em;">{{ $user->employee_id }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Row 2 --}}
|
||||||
<div class="main-row">
|
<div class="main-row">
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="label">Company Name</div>
|
<div class="ph-label">Email</div>
|
||||||
<div class="value">XYZ Infotech</div>
|
<div class="ph-value" style="font-size:0.88rem;">{{ $user->email ?? '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-group" style="min-width:260px;">
|
<div class="field-group">
|
||||||
<div class="label">Company Address</div>
|
<div class="ph-label">Phone</div>
|
||||||
<div class="value">XYZ Infotech, East Andheri, Mumbai 400 001</div>
|
<div class="ph-value">{{ $user->phone ?? '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if($user->department)
|
||||||
|
<div class="field-group">
|
||||||
|
<div class="ph-label">Department</div>
|
||||||
|
<div class="ph-value">{{ $user->department }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if($user->designation)
|
||||||
|
<div class="field-group">
|
||||||
|
<div class="ph-label">Designation</div>
|
||||||
|
<div class="ph-value">{{ $user->designation }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if($user->address)
|
||||||
|
<div class="field-group" style="min-width:220px;">
|
||||||
|
<div class="ph-label">Address</div>
|
||||||
|
<div class="ph-value" style="font-size:0.88rem;">{{ $user->address }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- STATS ROW -->
|
{{-- ════════ PROFESSIONAL INFO ════════ --}}
|
||||||
|
<div class="pro-section">
|
||||||
|
<div class="pro-section-title">🏢 Professional Information</div>
|
||||||
|
<div class="pro-grid">
|
||||||
|
|
||||||
|
@if($user->employee_id)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">🪪</div>
|
||||||
|
<div class="box-label">Employee ID</div>
|
||||||
|
<div class="box-value" style="font-family:monospace;">{{ $user->employee_id }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($user->username)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">👤</div>
|
||||||
|
<div class="box-label">Username</div>
|
||||||
|
<div class="box-value">{{ $user->username }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($user->role)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">🎯</div>
|
||||||
|
<div class="box-label">Role</div>
|
||||||
|
<div class="box-value">{{ $user->role }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($user->department)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">🏬</div>
|
||||||
|
<div class="box-label">Department</div>
|
||||||
|
<div class="box-value">{{ $user->department }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($user->designation)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">📋</div>
|
||||||
|
<div class="box-label">Designation</div>
|
||||||
|
<div class="box-value">{{ $user->designation }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">📅</div>
|
||||||
|
<div class="box-label">Joining Date</div>
|
||||||
|
<div class="box-value">{{ $joiningFormatted }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($user->emergency_phone)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">🆘</div>
|
||||||
|
<div class="box-label">Emergency Phone</div>
|
||||||
|
<div class="box-value">{{ $user->emergency_phone }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($user->additional_info)
|
||||||
|
<div class="pro-box" style="flex:2 1 320px;">
|
||||||
|
<div class="box-icon">📝</div>
|
||||||
|
<div class="box-label">Additional Info</div>
|
||||||
|
<div class="box-value" style="font-size:0.88rem;font-weight:500;color:#475569;white-space:normal;">{{ $user->additional_info }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ════════ REAL STATS ════════ --}}
|
||||||
<div class="profile-stats-row">
|
<div class="profile-stats-row">
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">Total Shipment</span>
|
<div class="stat-card sc-blue">
|
||||||
<span class="stat-value">📦 1,250</span>
|
<div class="stat-icon-wrap">📦</div>
|
||||||
<div class="stat-footer positive">+12% vs last month</div>
|
<span class="stat-label">Total Containers</span>
|
||||||
|
<div class="stat-value">{{ number_format($totalContainers) }}</div>
|
||||||
|
<div class="stat-footer">All containers in system</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">In Transit</span>
|
<div class="stat-card sc-green">
|
||||||
<span class="stat-value" style="color:#2563eb;">🚚 320</span>
|
<div class="stat-icon-wrap">🧾</div>
|
||||||
<div class="stat-footer neutral">65% of capacity</div>
|
<span class="stat-label">Total Invoices</span>
|
||||||
|
<div class="stat-value">{{ number_format($totalInvoices) }}</div>
|
||||||
|
<div class="stat-footer positive">
|
||||||
|
{{ $paidInvoices }} paid
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">At Customs</span>
|
<div class="stat-card sc-yellow">
|
||||||
<span class="stat-value" style="color:#f59e0b;">🛄 85</span>
|
<div class="stat-icon-wrap">⏳</div>
|
||||||
<div class="stat-footer neutral">Avg 2.3 days processing</div>
|
<span class="stat-label">Pending Invoices</span>
|
||||||
|
<div class="stat-value" style="color:#d97706;">{{ number_format($pendingInvoices) }}</div>
|
||||||
|
<div class="stat-footer">Awaiting payment</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">Delayed</span>
|
<div class="stat-card sc-teal">
|
||||||
<span class="stat-value" style="color:#e11d48;">⏰ 55</span>
|
<div class="stat-icon-wrap">✅</div>
|
||||||
<div class="stat-footer negative">-8% vs last month</div>
|
<span class="stat-label">Paid Invoices</span>
|
||||||
|
<div class="stat-value" style="color:#0ea5e9;">{{ number_format($paidInvoices) }}</div>
|
||||||
|
<div class="stat-footer positive">
|
||||||
|
@if($totalInvoices > 0)
|
||||||
|
{{ round(($paidInvoices / $totalInvoices) * 100, 1) }}% success rate
|
||||||
|
@else
|
||||||
|
No invoices yet
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">Delivered</span>
|
<div class="stat-card sc-purple">
|
||||||
<span class="stat-value" style="color:#22c55e;">✅ 780</span>
|
<div class="stat-icon-wrap">👥</div>
|
||||||
<div class="stat-footer positive">98.2% success rate</div>
|
<span class="stat-label">Total Customers</span>
|
||||||
|
<div class="stat-value" style="color:#8b5cf6;">{{ number_format($totalCustomers) }}</div>
|
||||||
|
<div class="stat-footer">Registered customers</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card sc-red">
|
||||||
|
<div class="stat-icon-wrap">🏷️</div>
|
||||||
|
<span class="stat-label">Mark List</span>
|
||||||
|
<div class="stat-value" style="color:#e74a3b;">{{ number_format($totalMarklist) }}</div>
|
||||||
|
<div class="stat-footer positive">
|
||||||
|
{{ $activeMarklist }} active
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- BOTTOM CARDS -->
|
{{-- ════════ BOTTOM CARDS ════════ --}}
|
||||||
<div class="profile-grid-row">
|
<div class="profile-grid-row">
|
||||||
<div class="profile-card">
|
<div class="profile-card">
|
||||||
<h6>Recent Activity</h6>
|
<h6>Recent Activity</h6>
|
||||||
<ul class="activity-list">
|
<ul class="activity-list">
|
||||||
<li>📦 Shipment #SH-2024-001 delivered <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">5 min ago</span></li>
|
<li>📦 Shipment #SH-2024-001 delivered <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">5 min ago</span></li>
|
||||||
<li>📝 New shipment #SH-2024-002 <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">13 min ago</span></li>
|
<li>📝 New shipment #SH-2024-002 created <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">13 min ago</span></li>
|
||||||
<li>⏰ Delay for #SH-2024-003 <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">1 hr ago</span></li>
|
<li>⏰ Delay reported for #SH-2024-003 <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">1 hr ago</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-card">
|
<div class="profile-card">
|
||||||
@@ -253,5 +432,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,19 @@ Route::prefix('admin')
|
|||||||
Route::get('/dashboard', [AdminOrderController::class, 'dashboard'])
|
Route::get('/dashboard', [AdminOrderController::class, 'dashboard'])
|
||||||
->name('admin.dashboard');
|
->name('admin.dashboard');
|
||||||
|
|
||||||
Route::get('/reports', [AdminReportController::class, 'index'])->name('admin.reports');
|
Route::get('/reports/containers/excel', [AdminReportController::class, 'containerReportExcel'])
|
||||||
|
->name('admin.reports.containers.excel');
|
||||||
|
|
||||||
|
Route::get('/reports/containers/pdf', [AdminReportController::class, 'containerReportPdf'])
|
||||||
|
->name('admin.reports.containers.pdf');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//reports DOWNLOAD
|
||||||
|
|
||||||
|
Route::get('/reports', [AdminReportController::class, 'containerReport'])
|
||||||
|
->name('admin.reports');
|
||||||
|
|
||||||
|
|
||||||
Route::get('/chat-support', fn() => view('admin.chat_support'))
|
Route::get('/chat-support', fn() => view('admin.chat_support'))
|
||||||
->name('admin.chat_support');
|
->name('admin.chat_support');
|
||||||
@@ -82,8 +94,7 @@ Route::prefix('admin')
|
|||||||
Route::get('/account', fn() => view('admin.account'))
|
Route::get('/account', fn() => view('admin.account'))
|
||||||
->name('admin.account');
|
->name('admin.account');
|
||||||
|
|
||||||
Route::get('/profile', fn() => view('admin.profile'))
|
Route::get('/profile', [AdminAuthController::class, 'profile'])->name('admin.profile');
|
||||||
->name('admin.profile');
|
|
||||||
|
|
||||||
Route::post(
|
Route::post(
|
||||||
'admin/orders/upload-excel-preview',
|
'admin/orders/upload-excel-preview',
|
||||||
@@ -378,3 +389,16 @@ Route::get('/admin/containers/{container}/popup', [\App\Http\Controllers\Contain
|
|||||||
->name('containers.popup');
|
->name('containers.popup');
|
||||||
|
|
||||||
|
|
||||||
|
//Invoice pdf download route
|
||||||
|
Route::get('invoice/{id}/download-pdf', [AdminInvoiceController::class, 'downloadPdf'])
|
||||||
|
->name('admin.invoice.download.pdf');
|
||||||
|
|
||||||
|
// Route::get('invoice/{id}/download-excel', [AdminInvoiceController::class, 'downloadExcel'])
|
||||||
|
// ->name('admin.invoice.download.excel');
|
||||||
|
|
||||||
|
Route::get('invoice/{id}/share', [AdminInvoiceController::class, 'share'])
|
||||||
|
->name('admin.invoice.share');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user