gst
This commit is contained in:
@@ -7,6 +7,11 @@ use Illuminate\Http\Request;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceItem;
|
||||
use Mpdf\Mpdf;
|
||||
use App\Models\InvoiceInstallment;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
|
||||
|
||||
|
||||
class AdminInvoiceController extends Controller
|
||||
{
|
||||
@@ -15,7 +20,7 @@ class AdminInvoiceController extends Controller
|
||||
// -------------------------------------------------------------
|
||||
public function index()
|
||||
{
|
||||
$invoices = Invoice::latest()->get();
|
||||
$invoices = Invoice::with(['order.shipments'])->latest()->get();
|
||||
return view('admin.invoice', compact('invoices'));
|
||||
}
|
||||
|
||||
@@ -24,49 +29,99 @@ class AdminInvoiceController extends Controller
|
||||
// -------------------------------------------------------------
|
||||
public function popup($id)
|
||||
{
|
||||
$invoice = Invoice::with('items')->findOrFail($id);
|
||||
return view('admin.popup_invoice', compact('invoice'));
|
||||
$invoice = Invoice::with(['items', 'order'])->findOrFail($id);
|
||||
|
||||
// Find actual Shipment record
|
||||
$shipment = \App\Models\Shipment::whereHas('items', function ($q) use ($invoice) {
|
||||
$q->where('order_id', $invoice->order_id);
|
||||
})
|
||||
->first();
|
||||
|
||||
return view('admin.popup_invoice', compact('invoice', 'shipment'));
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// EDIT INVOICE PAGE
|
||||
// -------------------------------------------------------------
|
||||
public function edit($id)
|
||||
{
|
||||
$invoice = Invoice::findOrFail($id);
|
||||
return view('admin.invoice_edit', compact('invoice'));
|
||||
$invoice = Invoice::with(['order.shipments'])->findOrFail($id);
|
||||
|
||||
$shipment = $invoice->order?->shipments?->first();
|
||||
|
||||
return view('admin.invoice_edit', compact('invoice', 'shipment'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// UPDATE INVOICE
|
||||
// -------------------------------------------------------------
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
Log::info("🟡 Invoice Update Request Received", [
|
||||
'invoice_id' => $id,
|
||||
'request' => $request->all()
|
||||
]);
|
||||
|
||||
$invoice = Invoice::findOrFail($id);
|
||||
|
||||
// Validate editable fields
|
||||
$data = $request->validate([
|
||||
'invoice_date' => 'required|date',
|
||||
'due_date' => 'required|date|after_or_equal:invoice_date',
|
||||
'payment_method' => 'nullable|string',
|
||||
'reference_no' => 'nullable|string|max:255',
|
||||
'final_amount' => 'required|numeric|min:0',
|
||||
'gst_percent' => 'required|numeric|min:0|max:28',
|
||||
'tax_type' => 'required|in:gst,igst',
|
||||
'tax_percent' => 'required|numeric|min:0|max:28',
|
||||
'status' => 'required|in:pending,paid,overdue',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// Auto-calc
|
||||
$gst_amount = ($data['final_amount'] * $data['gst_percent']) / 100;
|
||||
$final_amount_with_gst = $data['final_amount'] + $gst_amount;
|
||||
Log::info("✅ Validated Invoice Update Data", $data);
|
||||
|
||||
$data['gst_amount'] = $gst_amount;
|
||||
$data['final_amount_with_gst'] = $final_amount_with_gst;
|
||||
$finalAmount = floatval($data['final_amount']);
|
||||
$taxPercent = floatval($data['tax_percent']);
|
||||
$taxAmount = 0;
|
||||
|
||||
if ($data['tax_type'] === 'gst') {
|
||||
Log::info("🟢 GST Selected", compact('taxPercent'));
|
||||
|
||||
$data['cgst_percent'] = $taxPercent / 2;
|
||||
$data['sgst_percent'] = $taxPercent / 2;
|
||||
$data['igst_percent'] = 0;
|
||||
} else {
|
||||
Log::info("🔵 IGST Selected", compact('taxPercent'));
|
||||
|
||||
$data['cgst_percent'] = 0;
|
||||
$data['sgst_percent'] = 0;
|
||||
$data['igst_percent'] = $taxPercent;
|
||||
}
|
||||
|
||||
$taxAmount = ($finalAmount * $taxPercent) / 100;
|
||||
|
||||
$data['gst_amount'] = $taxAmount;
|
||||
$data['final_amount_with_gst'] = $finalAmount + $taxAmount;
|
||||
|
||||
// ✅ store original % for UI reference
|
||||
$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'],
|
||||
]);
|
||||
|
||||
// Update DB
|
||||
$invoice->update($data);
|
||||
|
||||
// Generate PDF
|
||||
Log::info("✅ Invoice Updated Successfully", [
|
||||
'invoice_id' => $invoice->id
|
||||
]);
|
||||
|
||||
// regenerate PDF
|
||||
$this->generateInvoicePDF($invoice);
|
||||
|
||||
return redirect()
|
||||
@@ -74,49 +129,133 @@ class AdminInvoiceController extends Controller
|
||||
->with('success', 'Invoice updated & PDF generated successfully.');
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// PDF GENERATION USING mPDF
|
||||
// -------------------------------------------------------------
|
||||
public function generateInvoicePDF($invoice)
|
||||
{
|
||||
// PDF Name
|
||||
// Load relationship including shipment
|
||||
$invoice->load(['items', 'order.shipments']);
|
||||
|
||||
// Fetch shipment
|
||||
$shipment = $invoice->order?->shipments?->first();
|
||||
|
||||
// PDF filename
|
||||
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
|
||||
|
||||
// Save directly in /public/invoices
|
||||
// Directory path
|
||||
$folder = public_path('invoices/');
|
||||
|
||||
// Create folder if not exists
|
||||
if (!file_exists($folder)) {
|
||||
mkdir($folder, 0777, true);
|
||||
}
|
||||
|
||||
// Full path
|
||||
$filePath = $folder . $fileName;
|
||||
|
||||
// Delete old file
|
||||
// Delete old file if exists
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
// Initialize mPDF
|
||||
// Create mPDF instance
|
||||
$mpdf = new Mpdf([
|
||||
'mode' => 'utf-8',
|
||||
'format' => 'A4',
|
||||
'default_font' => 'sans-serif'
|
||||
'default_font' => 'sans-serif',
|
||||
]);
|
||||
|
||||
// Load HTML view
|
||||
$html = view('admin.pdf.invoice', compact('invoice'))->render();
|
||||
$html = view('admin.pdf.invoice', [
|
||||
'invoice' => $invoice,
|
||||
'shipment' => $shipment
|
||||
])->render();
|
||||
|
||||
// Generate
|
||||
// Write HTML to PDF
|
||||
$mpdf->WriteHTML($html);
|
||||
|
||||
// Save to public/invoices
|
||||
// Save PDF
|
||||
$mpdf->Output($filePath, 'F');
|
||||
|
||||
// Save path in DB
|
||||
// Update DB path
|
||||
$invoice->update([
|
||||
'pdf_path' => 'invoices/' . $fileName
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function storeInstallment(Request $request, $invoice_id)
|
||||
{
|
||||
$request->validate([
|
||||
'installment_date' => 'required|date',
|
||||
'payment_method' => 'required|string',
|
||||
'reference_no' => 'nullable|string',
|
||||
'amount' => 'required|numeric|min:1',
|
||||
]);
|
||||
|
||||
$invoice = Invoice::findOrFail($invoice_id);
|
||||
|
||||
$paidTotal = $invoice->installments()->sum('amount');
|
||||
$remaining = $invoice->final_amount - $paidTotal;
|
||||
|
||||
if ($request->amount > $remaining) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Installment amount exceeds remaining balance.'
|
||||
], 422);
|
||||
}
|
||||
|
||||
$installment = InvoiceInstallment::create([
|
||||
'invoice_id' => $invoice_id,
|
||||
'installment_date' => $request->installment_date,
|
||||
'payment_method' => $request->payment_method,
|
||||
'reference_no' => $request->reference_no,
|
||||
'amount' => $request->amount,
|
||||
]);
|
||||
|
||||
// Update invoice status to paid if fully cleared
|
||||
$newPaid = $paidTotal + $request->amount;
|
||||
|
||||
if ($newPaid >= $invoice->final_amount) {
|
||||
$invoice->update(['status' => 'paid']);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Installment added successfully.',
|
||||
'installment' => $installment,
|
||||
'totalPaid' => $newPaid,
|
||||
'remaining' => max(0, $invoice->final_amount - $newPaid),
|
||||
'isCompleted' => $newPaid >= $invoice->final_amount
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteInstallment($id)
|
||||
{
|
||||
$installment = InvoiceInstallment::findOrFail($id);
|
||||
$invoice = $installment->invoice;
|
||||
|
||||
// delete installment
|
||||
$installment->delete();
|
||||
|
||||
// recalc totals
|
||||
$paidTotal = $invoice->installments()->sum('amount');
|
||||
$remaining = $invoice->final_amount - $paidTotal;
|
||||
|
||||
// auto update invoice status
|
||||
if ($remaining > 0 && $invoice->status === "paid") {
|
||||
$invoice->update(['status' => 'pending']);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Installment deleted.',
|
||||
'totalPaid' => $paidTotal,
|
||||
'remaining' => $remaining,
|
||||
'isZero' => $paidTotal == 0
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -22,9 +22,15 @@ class Invoice extends Model
|
||||
'reference_no',
|
||||
'status',
|
||||
|
||||
'final_amount',
|
||||
'gst_percent',
|
||||
'gst_amount',
|
||||
'final_amount', // without tax
|
||||
|
||||
'tax_type', // gst / igst
|
||||
'gst_percent', // only used for gst UI input
|
||||
'cgst_percent',
|
||||
'sgst_percent',
|
||||
'igst_percent',
|
||||
|
||||
'gst_amount', // total tax amount
|
||||
'final_amount_with_gst',
|
||||
|
||||
'customer_name',
|
||||
@@ -38,6 +44,7 @@ class Invoice extends Model
|
||||
'notes',
|
||||
];
|
||||
|
||||
|
||||
/****************************
|
||||
* Relationships
|
||||
****************************/
|
||||
@@ -74,4 +81,16 @@ class Invoice extends Model
|
||||
{
|
||||
return $this->status === 'pending' && now()->gt($this->due_date);
|
||||
}
|
||||
|
||||
public function getShipment()
|
||||
{
|
||||
return $this->order?->shipments?->first();
|
||||
}
|
||||
|
||||
public function installments()
|
||||
{
|
||||
return $this->hasMany(InvoiceInstallment::class);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
24
app/Models/InvoiceInstallment.php
Normal file
24
app/Models/InvoiceInstallment.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class InvoiceInstallment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'invoice_id',
|
||||
'installment_date',
|
||||
'payment_method',
|
||||
'reference_no',
|
||||
'amount',
|
||||
];
|
||||
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo(Invoice::class);
|
||||
}
|
||||
}
|
||||
@@ -46,4 +46,15 @@ class Order extends Model
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function shipmentItems()
|
||||
{
|
||||
return $this->hasMany(\App\Models\ShipmentItem::class, 'order_id', 'id');
|
||||
}
|
||||
|
||||
public function shipments()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\Shipment::class, 'shipment_items', 'order_id', 'shipment_id');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('invoices', function (Blueprint $table) {
|
||||
|
||||
// GST type — gst or igst
|
||||
$table->enum('tax_type', ['gst', 'igst'])
|
||||
->default('gst')
|
||||
->after('final_amount');
|
||||
|
||||
// Old gst_percent becomes optional
|
||||
$table->decimal('gst_percent', 5, 2)
|
||||
->nullable()
|
||||
->change();
|
||||
|
||||
// Split GST %
|
||||
$table->decimal('cgst_percent', 5, 2)
|
||||
->nullable()
|
||||
->after('gst_percent');
|
||||
|
||||
$table->decimal('sgst_percent', 5, 2)
|
||||
->nullable()
|
||||
->after('cgst_percent');
|
||||
|
||||
// IGST %
|
||||
$table->decimal('igst_percent', 5, 2)
|
||||
->nullable()
|
||||
->after('sgst_percent');
|
||||
|
||||
// Tax amount recalculation is the same
|
||||
// gst_amount and final_amount_with_gst already exist
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('invoices', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'tax_type',
|
||||
'cgst_percent',
|
||||
'sgst_percent',
|
||||
'igst_percent',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('invoice_installments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedBigInteger('invoice_id');
|
||||
$table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
|
||||
|
||||
$table->date('installment_date');
|
||||
$table->string('payment_method')->nullable(); // cash, bank, UPI, cheque, etc
|
||||
$table->string('reference_no')->nullable();
|
||||
$table->decimal('amount', 10, 2);
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('invoice_installments');
|
||||
}
|
||||
};
|
||||
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000009.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000009.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000010.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000010.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000011.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000011.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000012.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000012.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000013.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000013.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000014.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000014.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000022.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000022.pdf
Normal file
Binary file not shown.
@@ -3,360 +3,225 @@
|
||||
@section('page-title', 'Edit Invoice')
|
||||
|
||||
@section('content')
|
||||
|
||||
<style>
|
||||
/* World-Class UI Design System - No Blur Effects */
|
||||
/* --------------------------------------------------
|
||||
GLOBAL VARIABLES
|
||||
-------------------------------------------------- */
|
||||
:root {
|
||||
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
--success-gradient: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
--glass-bg: #ffffff;
|
||||
--glass-border: rgba(255, 255, 255, 0.2);
|
||||
--primary-gradient: linear-gradient(135deg,#667eea,#764ba2);
|
||||
--success-gradient: linear-gradient(135deg,#10b981,#059669);
|
||||
--glass-bg: #fff;
|
||||
--shadow-soft: 0 8px 32px rgba(0,0,0,0.1);
|
||||
--shadow-medium: 0 12px 48px rgba(0,0,0,0.15);
|
||||
--shadow-strong: 0 20px 60px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* Enhanced Card - No Blur */
|
||||
/* --------------------------------------------------
|
||||
CARD DESIGN
|
||||
-------------------------------------------------- */
|
||||
.card {
|
||||
background: var(--glass-bg);
|
||||
border-radius: 24px;
|
||||
box-shadow: var(--shadow-strong);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e6ef;
|
||||
animation: cardEntrance 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
box-shadow: var(--shadow-strong);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
animation: fadeUp 0.8s ease;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
content:"";
|
||||
position:absolute; inset:0;
|
||||
background:linear-gradient(45deg,
|
||||
rgba(102, 126, 234, 0.03) 0%,
|
||||
rgba(118, 75, 162, 0.03) 50%,
|
||||
rgba(16, 185, 129, 0.03) 100%);
|
||||
rgba(102,126,234,.03),
|
||||
rgba(118,75,162,.03) 50%,
|
||||
rgba(16,185,129,.03));
|
||||
pointer-events:none;
|
||||
}
|
||||
|
||||
@keyframes cardEntrance {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(30px) scale(0.95);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
@keyframes fadeUp {
|
||||
0% {opacity:0; transform:translateY(30px) scale(.95);}
|
||||
100% {opacity:1; transform:translateY(0) scale(1);}
|
||||
}
|
||||
|
||||
/* Premium Card Header */
|
||||
/* --------------------------------------------------
|
||||
CARD HEADER
|
||||
-------------------------------------------------- */
|
||||
.card-header {
|
||||
background:var(--primary-gradient);
|
||||
color: white;
|
||||
border-bottom: none;
|
||||
padding: 28px 35px;
|
||||
color:#fff;
|
||||
padding:11px 19px;
|
||||
border:none;
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
.card-header::before {
|
||||
content: '';
|
||||
content:"";
|
||||
position:absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(45deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.1) 50%,
|
||||
transparent 100%);
|
||||
animation: headerShimmer 6s infinite linear;
|
||||
transform: rotate(45deg);
|
||||
top:-50%; left:-50%;
|
||||
width:200%; height:200%;
|
||||
background:linear-gradient(45deg,transparent,
|
||||
rgba(255,255,255,.1) 50%,transparent);
|
||||
animation: shimmer 6s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes headerShimmer {
|
||||
0% { transform: translateX(-100%) rotate(45deg); }
|
||||
100% { transform: translateX(100%) rotate(45deg); }
|
||||
@keyframes shimmer {
|
||||
from {transform:translateX(-100%) rotate(45deg);}
|
||||
to {transform:translateX(100%) rotate(45deg);}
|
||||
}
|
||||
|
||||
.card-header h4 {
|
||||
margin: 0;
|
||||
font-weight: 800;
|
||||
font-size: 1.6rem;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
margin:0; font-weight:800; font-size:1.6rem;
|
||||
display:flex; align-items:center; gap:12px;
|
||||
text-shadow:0 2px 4px rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
.card-header h4::before {
|
||||
content: '✨';
|
||||
font-size: 1.4rem;
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
|
||||
content:"✨"; font-size:1.4rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 35px;
|
||||
background: #f8fafc;
|
||||
position: relative;
|
||||
}
|
||||
/* --------------------------------------------------
|
||||
BODY + FORM ELEMENTS
|
||||
-------------------------------------------------- */
|
||||
.card-body { padding:15px; background:#f8fafc; }
|
||||
|
||||
/* World-Class Form Elements - No Blur */
|
||||
.form-label {
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.95rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
position: relative;
|
||||
font-weight:700; color:#1e293b;
|
||||
margin-bottom:10px; font-size:.95rem;
|
||||
letter-spacing:.5px; text-transform:uppercase;
|
||||
display:flex; align-items:center; gap:8px;
|
||||
}
|
||||
|
||||
.form-label::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
content:""; width:4px; height:16px;
|
||||
background:var(--primary-gradient);
|
||||
border-radius:2px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.form-control, .form-select {
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 16px;
|
||||
padding: 16px 20px;
|
||||
padding: 8px 15px;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
font-size: 1rem;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||
position: relative;
|
||||
transition: .4s;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
border-color:#667eea;
|
||||
box-shadow:
|
||||
0 0 0 4px rgba(102, 126, 234, 0.15),
|
||||
0 8px 24px rgba(102, 126, 234, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
background: #ffffff;
|
||||
0 0 0 4px rgba(102,126,234,.15),
|
||||
0 8px 24px rgba(102,126,234,.2);
|
||||
transform:translateY(-2px) scale(1.02);
|
||||
animation: inputGlow 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes inputGlow {
|
||||
0%, 100% { box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.15), 0 8px 24px rgba(102, 126, 234, 0.2); }
|
||||
50% { box-shadow: 0 0 0 6px rgba(102, 126, 234, 0.1), 0 12px 32px rgba(102, 126, 234, 0.25); }
|
||||
}
|
||||
|
||||
/* hover */
|
||||
.form-control:hover, .form-select:hover {
|
||||
border-color:#cbd5e1;
|
||||
transform:translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
|
||||
box-shadow:0 6px 20px rgba(0,0,0,.08);
|
||||
}
|
||||
|
||||
/* Enhanced Grid System */
|
||||
.row.g-3 {
|
||||
margin: -15px;
|
||||
}
|
||||
|
||||
.row.g-3 > [class*="col-"] {
|
||||
padding: 15px;
|
||||
animation: formElementEntrance 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
}
|
||||
|
||||
@keyframes formElementEntrance {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(25px) scale(0.9);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Staggered Animation Delays */
|
||||
.row.g-3 > [class*="col-"]:nth-child(1) { animation-delay: 0.1s; }
|
||||
.row.g-3 > [class*="col-"]:nth-child(2) { animation-delay: 0.2s; }
|
||||
.row.g-3 > [class*="col-"]:nth-child(3) { animation-delay: 0.3s; }
|
||||
.row.g-3 > [class*="col-"]:nth-child(4) { animation-delay: 0.4s; }
|
||||
.row.g-3 > [class*="col-"]:nth-child(5) { animation-delay: 0.5s; }
|
||||
.row.g-3 > [class*="col-"]:nth-child(6) { animation-delay: 0.6s; }
|
||||
.row.g-3 > [class*="col-"]:nth-child(7) { animation-delay: 0.7s; }
|
||||
.row.g-3 > [class*="col-"]:nth-child(8) { animation-delay: 0.8s; }
|
||||
|
||||
/* Premium Textarea */
|
||||
/* textarea */
|
||||
textarea.form-control {
|
||||
height: 65px;
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
line-height: 1.6;
|
||||
background: #ffffff;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
textarea.form-control:focus {
|
||||
transform: translateY(-2px) scale(1.01);
|
||||
box-shadow:
|
||||
0 0 0 4px rgba(102, 126, 234, 0.15),
|
||||
0 12px 32px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
/* World-Class Button Design - No Blur */
|
||||
/* --------------------------------------------------
|
||||
BUTTON STYLING
|
||||
-------------------------------------------------- */
|
||||
.btn-success {
|
||||
background: var(--success-gradient);
|
||||
border: none;
|
||||
padding: 18px 45px;
|
||||
border-radius: 16px;
|
||||
color: white;
|
||||
padding: 10px 25px;
|
||||
border-radius: 7px;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: #fff;
|
||||
transition: .4s;
|
||||
box-shadow: var(--shadow-medium);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-success::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.4),
|
||||
transparent);
|
||||
transition: left 0.6s ease;
|
||||
}
|
||||
|
||||
.btn-success:hover::before {
|
||||
left: 100%;
|
||||
content:"";
|
||||
position:absolute; left:-100%; top:0;
|
||||
width:100%; height:100%;
|
||||
background:linear-gradient(90deg,transparent,
|
||||
rgba(255,255,255,.4),transparent);
|
||||
transition:.6s;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
transform:translateY(-4px) scale(1.05);
|
||||
box-shadow:
|
||||
0 16px 40px rgba(16, 185, 129, 0.4),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
background: var(--success-gradient);
|
||||
box-shadow:0 16px 40px rgba(16,185,129,.4);
|
||||
}
|
||||
|
||||
.btn-success:active {
|
||||
transform: translateY(-1px) scale(1.02);
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
.btn-success:hover::before { left:100%; }
|
||||
|
||||
/* Enhanced Select Styling */
|
||||
.form-select {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23667eea' stroke-linecap='round' stroke-linejoin='round' stroke-width='2.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||
background-position: right 20px center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 18px;
|
||||
padding-right: 50px;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
/* Advanced Loading Animation */
|
||||
.btn-success.loading {
|
||||
pointer-events: none;
|
||||
padding-right: 60px;
|
||||
pointer-events:none; padding-right:60px;
|
||||
}
|
||||
|
||||
.btn-success.loading::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: -10px;
|
||||
border: 2px solid transparent;
|
||||
border-top: 2px solid white;
|
||||
content:"";
|
||||
position:absolute; right:20px; top:50%;
|
||||
width:20px; height:20px; margin-top:-10px;
|
||||
border-radius:50%;
|
||||
border:2px solid transparent;
|
||||
border-top-color:#fff;
|
||||
animation:spin 1s linear infinite;
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes spin {to{transform:rotate(360deg);}}
|
||||
|
||||
/* Premium Focus States */
|
||||
.form-control:focus-visible,
|
||||
.form-select:focus-visible,
|
||||
.btn-success:focus-visible {
|
||||
outline: 3px solid #667eea;
|
||||
outline-offset: 2px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Advanced Responsive Design */
|
||||
/* --------------------------------------------------
|
||||
RESPONSIVE
|
||||
-------------------------------------------------- */
|
||||
@media(max-width:768px){
|
||||
.card-body {
|
||||
padding: 25px 20px;
|
||||
.card-body{padding:25px 20px;}
|
||||
.card-header{padding:22px 25px;}
|
||||
.card-header h4{font-size:1.4rem;}
|
||||
.btn-success{width:100%; padding:16px 30px;}
|
||||
.form-control,.form-select{padding:14px 16px;}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 22px 25px;
|
||||
}
|
||||
|
||||
.card-header h4 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
width: 100%;
|
||||
padding: 16px 30px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-control, .form-select {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Micro-interactions for Enhanced UX */
|
||||
.form-control:valid {
|
||||
border-left: 3px solid #10b981;
|
||||
}
|
||||
|
||||
.form-control:invalid:not(:focus):not(:placeholder-shown) {
|
||||
border-left: 3px solid #ef4444;
|
||||
animation: shake 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-5px); }
|
||||
75% { transform: translateX(5px); }
|
||||
}
|
||||
|
||||
/* Smooth scrolling for better UX */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Performance optimized animations */
|
||||
/* Only animation reduction */
|
||||
@media(prefers-reduced-motion:reduce){
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
*{animation:none!important; transition:none!important;}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
|
||||
|
||||
{{-- Invoice Preview Section --}}
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-light">
|
||||
<h4 class="fw-bold mb-0">
|
||||
<i class="fas fa-file-invoice me-2"></i> Invoice Details
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
@include('admin.popup_invoice', [
|
||||
'invoice' => $invoice,
|
||||
'shipment' => $shipment,
|
||||
'embedded' => true
|
||||
])
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- --------------------------------------------------
|
||||
HTML CONTENT (UNCHANGED)
|
||||
-------------------------------------------------- -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<h4>Edit Invoice</h4>
|
||||
</div>
|
||||
|
||||
@@ -368,44 +233,53 @@ html {
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Invoice Date</label>
|
||||
<input type="date" class="form-control"
|
||||
name="invoice_date"
|
||||
<input type="date" class="form-control" name="invoice_date"
|
||||
value="{{ $invoice->invoice_date }}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Due Date</label>
|
||||
<input type="date" class="form-control"
|
||||
name="due_date"
|
||||
<input type="date" class="form-control" name="due_date"
|
||||
value="{{ $invoice->due_date }}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Payment Method</label>
|
||||
<input type="text" class="form-control"
|
||||
name="payment_method"
|
||||
value="{{ $invoice->payment_method }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Reference No</label>
|
||||
<input type="text" class="form-control"
|
||||
name="reference_no"
|
||||
value="{{ $invoice->reference_no }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Final Amount (Editable)</label>
|
||||
<input type="number" step="0.01" class="form-control"
|
||||
name="final_amount"
|
||||
<label class="form-label">Final Amount (₹)</label>
|
||||
<input type="number" step="0.01" class="form-control" name="final_amount"
|
||||
value="{{ $invoice->final_amount }}" required>
|
||||
</div>
|
||||
|
||||
<!-- ✅ TAX TYPE -->
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">GST Percentage</label>
|
||||
<input type="number" step="0.01" class="form-control"
|
||||
name="gst_percent"
|
||||
value="{{ $invoice->gst_percent }}" required>
|
||||
<label class="form-label d-block">Tax Type</label>
|
||||
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio"
|
||||
name="tax_type" value="gst"
|
||||
@checked($invoice->tax_type === 'gst')>
|
||||
<label class="form-check-label">GST (CGST+SGST)</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio"
|
||||
name="tax_type" value="igst"
|
||||
@checked($invoice->tax_type === 'igst')>
|
||||
<label class="form-check-label">IGST</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ One unified input -->
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Tax Percentage (%)</label>
|
||||
<input type="number" step="0.01" min="0" max="28"
|
||||
class="form-control"
|
||||
name="tax_percent"
|
||||
value="{{ old('tax_percent',
|
||||
$invoice->tax_type == 'gst'
|
||||
? $invoice->cgst_percent + $invoice->sgst_percent
|
||||
: $invoice->igst_percent
|
||||
) }}"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
@@ -419,14 +293,79 @@ html {
|
||||
|
||||
<div class="col-md-12">
|
||||
<label class="form-label">Notes</label>
|
||||
<textarea class="form-control" rows="3" name="notes">
|
||||
{{ $invoice->notes }}
|
||||
</textarea>
|
||||
<textarea class="form-control" rows="3" name="notes">{{ $invoice->notes }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 text-end mt-3">
|
||||
<button type="submit" class="btn btn-success">
|
||||
Update Invoice
|
||||
<button type="submit" class="btn btn-success">Update Invoice</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@php
|
||||
$totalPaid = $invoice->installments->sum('amount');
|
||||
$remaining = $invoice->final_amount_with_gst - $totalPaid;
|
||||
@endphp
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3 class="fw-bold mb-0">Installment Payments</h3>
|
||||
|
||||
@if($remaining > 0)
|
||||
<button id="toggleInstallmentForm" class="btn btn-success">
|
||||
+ Add Installment
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Installment Form -->
|
||||
<div id="installmentForm" class="card shadow-sm d-none mb-4">
|
||||
<div class="card-header">
|
||||
<h4>Add Installment</h4>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form id="installmentSubmitForm">
|
||||
@csrf
|
||||
|
||||
<div class="row g-3">
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Installment Date</label>
|
||||
<input type="date" name="installment_date" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Payment Method</label>
|
||||
<select name="payment_method" class="form-select" required>
|
||||
<option value="cash">Cash</option>
|
||||
<option value="bank">Bank Transfer</option>
|
||||
<option value="upi">UPI</option>
|
||||
<option value="cheque">Cheque</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Reference No (optional)</label>
|
||||
<input type="text" name="reference_no" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label class="form-label">Amount</label>
|
||||
<input type="number" name="amount" id="installmentAmount" class="form-control"
|
||||
step="0.01" min="1" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 text-end mt-2">
|
||||
<button type="submit" class="btn btn-success" id="installmentSubmitBtn">
|
||||
Submit Installment
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -435,47 +374,229 @@ html {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
|
||||
<div class="d-flex justify-content-between fs-5 fw-bold">
|
||||
<span>Total Amount (Before Tax):</span>
|
||||
<span>₹{{ number_format($invoice->final_amount, 2) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between text-primary fs-5 fw-bold">
|
||||
<span>Tax Type:</span>
|
||||
<span>
|
||||
@if($invoice->tax_type === 'gst')
|
||||
GST (CGST + SGST)
|
||||
@else
|
||||
IGST
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between text-primary fs-5 fw-bold">
|
||||
<span>Tax Percentage:</span>
|
||||
<span>
|
||||
@if($invoice->tax_type === 'gst')
|
||||
{{ $invoice->cgst_percent + $invoice->sgst_percent }}%
|
||||
@else
|
||||
{{ $invoice->igst_percent }}%
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between text-warning fs-5 fw-bold">
|
||||
<span>GST Amount:</span>
|
||||
<span>₹{{ number_format($invoice->gst_amount, 2) }}</span>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="d-flex justify-content-between fs-4 fw-bold text-dark">
|
||||
<span>Total Invoice Amount (Including GST):</span>
|
||||
<span>₹{{ number_format($invoice->final_amount_with_gst, 2) }}</span>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="d-flex justify-content-between text-success fs-5 fw-bold">
|
||||
<span>Total Paid:</span>
|
||||
<span id="paidAmount">₹{{ number_format($totalPaid, 2) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between text-danger fs-5 fw-bold">
|
||||
<span>Remaining:</span>
|
||||
<span id="remainingAmount">₹{{ number_format($remaining, 2) }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Installment History -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<h4>Installment History</h4>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-hover mb-0" id="installmentTable">
|
||||
<thead style="background:#f1f5f9;">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Date</th>
|
||||
<th>Payment Method</th>
|
||||
<th>Reference No</th>
|
||||
<th>Amount</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($invoice->installments as $i)
|
||||
<tr data-id="{{ $i->id }}">
|
||||
<td>{{ $loop->iteration }}</td>
|
||||
<td>{{ $i->installment_date }}</td>
|
||||
<td>{{ ucfirst($i->payment_method) }}</td>
|
||||
<td>{{ $i->reference_no }}</td>
|
||||
<td class="fw-bold text-success">₹{{ number_format($i->amount, 2) }}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-sm deleteInstallment">
|
||||
❌ Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
// World-Class Interactive Enhancements - No Parallax
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.querySelector('form');
|
||||
const submitBtn = document.querySelector('.btn-success');
|
||||
const inputs = document.querySelectorAll('input, select, textarea');
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
// Enhanced form submission
|
||||
form.addEventListener('submit', function(e) {
|
||||
submitBtn.classList.add('loading');
|
||||
submitBtn.innerHTML = 'Updating Invoice...';
|
||||
// ✅ Toggle Installment Form
|
||||
const toggleBtn = document.getElementById("toggleInstallmentForm");
|
||||
const formBox = document.getElementById("installmentForm");
|
||||
|
||||
// Add a small delay to show loading state
|
||||
setTimeout(() => {
|
||||
if (!form.checkValidity()) {
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.innerHTML = 'Update Invoice';
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener("click", () => formBox.classList.toggle("d-none"));
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// ✅ Add Installment
|
||||
const submitForm = document.getElementById("installmentSubmitForm");
|
||||
const submitBtn = document.getElementById("installmentSubmitBtn");
|
||||
|
||||
const formatINR = amt =>
|
||||
"₹" + Number(amt).toLocaleString("en-IN", {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
|
||||
// Real-time validation with visual feedback
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('input', function() {
|
||||
if (this.checkValidity()) {
|
||||
this.style.borderLeftColor = '#10b981';
|
||||
} else {
|
||||
this.style.borderLeftColor = '#ef4444';
|
||||
submitForm.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
submitBtn.textContent = "Processing...";
|
||||
submitBtn.disabled = true;
|
||||
|
||||
fetch("{{ route('admin.invoice.installment.store', $invoice->id) }}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": submitForm.querySelector("input[name=_token]").value,
|
||||
"Accept": "application/json"
|
||||
},
|
||||
body: new FormData(submitForm)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
submitBtn.textContent = "Submit Installment";
|
||||
submitBtn.disabled = false;
|
||||
|
||||
if (data.status === "error") {
|
||||
alert(data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const table = document.querySelector("#installmentTable tbody");
|
||||
const index = table.rows.length + 1;
|
||||
|
||||
table.insertAdjacentHTML("beforeend", `
|
||||
<tr data-id="${data.installment.id}">
|
||||
<td>${index}</td>
|
||||
<td>${data.installment.installment_date}</td>
|
||||
<td>${data.installment.payment_method.toUpperCase()}</td>
|
||||
<td>${data.installment.reference_no ?? ""}</td>
|
||||
<td class="fw-bold text-success">${formatINR(data.installment.amount)}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-sm deleteInstallment">❌ Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
|
||||
// ✅ Update totals (Gst included)
|
||||
document.getElementById("paidAmount").textContent = formatINR(data.totalPaid);
|
||||
document.getElementById("remainingAmount").textContent = formatINR(data.remaining);
|
||||
|
||||
submitForm.reset();
|
||||
|
||||
// ✅ If fully paid — hide Add Installment button & form
|
||||
if (data.isCompleted) {
|
||||
toggleBtn?.remove();
|
||||
formBox.classList.add("d-none");
|
||||
}
|
||||
|
||||
alert(data.message);
|
||||
})
|
||||
.catch(() => {
|
||||
submitBtn.textContent = "Submit Installment";
|
||||
submitBtn.disabled = false;
|
||||
alert("Something went wrong");
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ Delete Installment (event delegation)
|
||||
document.addEventListener("click", function (e) {
|
||||
if (!e.target.classList.contains("deleteInstallment")) return;
|
||||
|
||||
if (!confirm("Are you sure you want to delete this installment?")) return;
|
||||
|
||||
const row = e.target.closest("tr");
|
||||
const id = row.getAttribute("data-id");
|
||||
|
||||
fetch("{{ url('/admin/installment') }}/" + id, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": "{{ csrf_token() }}",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === "success") {
|
||||
row.remove();
|
||||
|
||||
document.getElementById("paidAmount").textContent = formatINR(data.totalPaid);
|
||||
document.getElementById("remainingAmount").textContent = formatINR(data.remaining);
|
||||
|
||||
// ✅ If remaining exists but Add Installment button disappeared → reload UI
|
||||
if (data.remaining > 0 && !toggleBtn) {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
// ✅ Remove entire card if no installments left
|
||||
if (data.isZero) {
|
||||
document.getElementById("installmentTable").closest(".card").remove();
|
||||
}
|
||||
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Add focus effects
|
||||
input.addEventListener('focus', function() {
|
||||
this.parentElement.style.transform = 'translateY(-2px)';
|
||||
});
|
||||
|
||||
input.addEventListener('blur', function() {
|
||||
this.parentElement.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
@endsection
|
||||
@@ -164,7 +164,7 @@
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 18px 16px 0 16px;
|
||||
padding: 18px 16px 16px 16px;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -1,207 +1,280 @@
|
||||
<div class="p-4">
|
||||
<!-- Invoice Header -->
|
||||
<div class="row mb-4">
|
||||
{{-- INVOICE CONTENT (NO POPUP WRAPPERS HERE) --}}
|
||||
|
||||
<!-- ============================
|
||||
INVOICE HEADER
|
||||
============================ -->
|
||||
<div class="mb-4">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
<h2 class="fw-bold text-primary mb-1">
|
||||
<i class="fas fa-file-invoice me-2"></i> INVOICE
|
||||
</h2>
|
||||
|
||||
<h4 class="fw-bold text-dark mb-0">{{ $invoice->invoice_number }}</h4>
|
||||
|
||||
{{-- ORDER + SHIPMENT INFO --}}
|
||||
<div class="mt-2 small">
|
||||
|
||||
{{-- ORDER ID --}}
|
||||
@if($invoice->order_id)
|
||||
<div>
|
||||
<strong>Order ID:</strong>
|
||||
{{ $invoice->order->order_id ?? $invoice->order_id }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- SHIPMENT ID --}}
|
||||
@if(isset($shipment) && $shipment)
|
||||
<div>
|
||||
<strong>Shipment ID:</strong> {{ $shipment->shipment_id }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 text-end">
|
||||
<div class="d-inline-block bg-light rounded-3 p-3">
|
||||
<span class="badge
|
||||
|
||||
<span class="badge fs-6 px-3 py-2
|
||||
@if($invoice->status=='paid') bg-success
|
||||
@elseif($invoice->status=='overdue') bg-danger
|
||||
@elseif($invoice->status=='pending') bg-warning text-dark
|
||||
@else bg-secondary @endif
|
||||
fs-6 px-3 py-2">
|
||||
@else bg-secondary @endif">
|
||||
|
||||
<i class="fas
|
||||
@if($invoice->status=='paid') fa-check-circle
|
||||
@elseif($invoice->status=='overdue') fa-exclamation-circle
|
||||
@elseif($invoice->status=='pending') fa-clock
|
||||
@else fa-question-circle @endif me-1"></i>
|
||||
|
||||
{{ ucfirst($invoice->status) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dates - Compact Professional Layout -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body py-2">
|
||||
<div class="row align-items-center text-center">
|
||||
<div class="col-md-5">
|
||||
<div class="mb-0">
|
||||
<div class="text-muted fw-semibold small">INVOICE DATE</div>
|
||||
</div>
|
||||
<div class="fw-bold text-dark" style="font-size: 0.95rem;">
|
||||
|
||||
|
||||
<!-- ============================
|
||||
DATES SECTION
|
||||
============================ -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body py-3">
|
||||
|
||||
<div class="row text-center align-items-center">
|
||||
|
||||
<div class="col-md-5">
|
||||
<small class="text-muted fw-semibold">INVOICE DATE</small>
|
||||
<div class="fw-bold text-dark">
|
||||
{{ \Carbon\Carbon::parse($invoice->invoice_date)->format('M d, Y') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<div class="date-connector">
|
||||
<i class="fas fa-arrow-right text-muted small"></i>
|
||||
</div>
|
||||
<i class="fas fa-arrow-right text-muted"></i>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<div class="mb-0">
|
||||
<div class="text-muted fw-semibold small">DUE DATE</div>
|
||||
</div>
|
||||
<div class="fw-bold @if($invoice->status == 'overdue') text-danger @else text-dark @endif" style="font-size: 0.95rem;">
|
||||
<small class="text-muted fw-semibold">DUE DATE</small>
|
||||
<div class="fw-bold {{ $invoice->status=='overdue' ? 'text-danger' : 'text-dark' }}">
|
||||
{{ \Carbon\Carbon::parse($invoice->due_date)->format('M d, Y') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Customer Details -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
|
||||
<!-- ============================
|
||||
CUSTOMER DETAILS
|
||||
============================ -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
|
||||
<div class="card-header bg-light py-2">
|
||||
<h6 class="mb-0 fw-bold text-dark">
|
||||
<i class="fas fa-user me-2"></i>Customer Details
|
||||
</h6>
|
||||
<h6 class="fw-bold mb-0"><i class="fas fa-user me-2"></i> Customer Details</h6>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6">
|
||||
<h6 class="fw-bold text-primary mb-1">{{ $invoice->customer_name }}</h6>
|
||||
<h6 class="fw-bold text-primary">{{ $invoice->customer_name }}</h6>
|
||||
|
||||
@if($invoice->company_name)
|
||||
<p class="mb-1">
|
||||
<strong>Company:</strong> {{ $invoice->company_name }}
|
||||
</p>
|
||||
<p class="mb-1"><strong>Company:</strong> {{ $invoice->company_name }}</p>
|
||||
@endif
|
||||
<p class="mb-1">
|
||||
<strong>Mobile:</strong> {{ $invoice->customer_mobile }}
|
||||
</p>
|
||||
<p class="mb-1">
|
||||
<strong>Email:</strong> {{ $invoice->customer_email }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="mb-0">
|
||||
<strong>Address:</strong><br>
|
||||
{{ $invoice->customer_address }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mb-1"><strong>Mobile:</strong> {{ $invoice->customer_mobile }}</p>
|
||||
<p class="mb-1"><strong>Email:</strong> {{ $invoice->customer_email }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Invoice Items -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-light py-2">
|
||||
<h6 class="mb-0 fw-bold text-dark">
|
||||
<i class="fas fa-list me-2"></i>Invoice Items
|
||||
</h6>
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Address:</strong><br>{{ $invoice->customer_address }}</p>
|
||||
<p class="mb-1"><strong>Pincode:</strong> {{ $invoice->pincode }}</p>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ============================
|
||||
INVOICE ITEMS
|
||||
============================ -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
|
||||
<div class="card-header bg-light py-2">
|
||||
<h6 class="fw-bold mb-0"><i class="fas fa-list me-2"></i> Invoice Items</h6>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
|
||||
<table class="table table-bordered table-hover align-middle mb-0">
|
||||
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="text-center">#</th>
|
||||
<th>#</th>
|
||||
<th>Description</th>
|
||||
<th class="text-center">CTN</th>
|
||||
<th class="text-center">QTY</th>
|
||||
<th class="text-center">TTL/QTY</th>
|
||||
<th class="text-center">Unit</th>
|
||||
<th class="text-center">Price</th>
|
||||
<th class="text-center">TTL Amount</th>
|
||||
<th class="text-center">CBM</th>
|
||||
<th class="text-center">TTL CBM</th>
|
||||
<th class="text-center">KG</th>
|
||||
<th class="text-center">TTL KG</th>
|
||||
<th class="text-center">Shop No</th>
|
||||
<th>CTN</th>
|
||||
<th>QTY</th>
|
||||
<th>TTL/QTY</th>
|
||||
<th>Unit</th>
|
||||
<th>Price</th>
|
||||
<th>TTL Amount</th>
|
||||
<th>CBM</th>
|
||||
<th>TTL CBM</th>
|
||||
<th>KG</th>
|
||||
<th>TTL KG</th>
|
||||
<th>Shop No</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach($invoice->items as $i => $item)
|
||||
<tr>
|
||||
<td class="text-center fw-bold text-muted">{{ $i+1 }}</td>
|
||||
<td class="fw-semibold">{{ $item->description }}</td>
|
||||
<td class="text-center fw-bold">{{ $i + 1 }}</td>
|
||||
<td>{{ $item->description }}</td>
|
||||
<td class="text-center">{{ $item->ctn }}</td>
|
||||
<td class="text-center">{{ $item->qty }}</td>
|
||||
<td class="text-center fw-bold">{{ $item->ttl_qty }}</td>
|
||||
<td class="text-center">{{ $item->ttl_qty }}</td>
|
||||
<td class="text-center">{{ $item->unit }}</td>
|
||||
<td class="text-center text-success fw-bold">₹{{ number_format($item->price,2) }}</td>
|
||||
<td class="text-center text-primary fw-bold">₹{{ number_format($item->ttl_amount,2) }}</td>
|
||||
<td class="text-center">₹{{ number_format($item->price,2) }}</td>
|
||||
<td class="text-center fw-bold text-primary">₹{{ number_format($item->ttl_amount,2) }}</td>
|
||||
<td class="text-center">{{ $item->cbm }}</td>
|
||||
<td class="text-center">{{ $item->ttl_cbm }}</td>
|
||||
<td class="text-center">{{ $item->kg }}</td>
|
||||
<td class="text-center">{{ $item->ttl_kg }}</td>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-light text-dark border">{{ $item->shop_no }}</span>
|
||||
</td>
|
||||
<td class="text-center">{{ $item->shop_no }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Final Summary -->
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ============================
|
||||
FINAL SUMMARY
|
||||
============================ -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-6">
|
||||
|
||||
<div class="card border-0 bg-light">
|
||||
|
||||
<div class="card-header bg-dark text-white py-2">
|
||||
<h6 class="mb-0 fw-bold">
|
||||
<i class="fas fa-calculator me-2"></i>Final Summary
|
||||
</h6>
|
||||
<h6 class="fw-bold mb-0"><i class="fas fa-calculator me-2"></i> Final Summary</h6>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2 border-bottom pb-1">
|
||||
<span class="fw-semibold">Amount:</span>
|
||||
<span class="fw-bold text-dark">₹{{ number_format($invoice->final_amount,2) }}</span>
|
||||
<span class="fw-bold">₹{{ number_format($invoice->final_amount, 2) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
|
||||
<span class="fw-semibold">GST ({{ $invoice->gst_percent }}%):</span>
|
||||
|
||||
@if($invoice->tax_type === 'gst')
|
||||
{{-- CGST --}}
|
||||
<div class="d-flex justify-content-between mb-2 border-bottom pb-1">
|
||||
<span class="fw-semibold">CGST ({{ $invoice->cgst_percent }}%):</span>
|
||||
<span class="fw-bold text-danger">₹{{ number_format($invoice->gst_amount/2, 2) }}</span>
|
||||
</div>
|
||||
|
||||
{{-- SGST --}}
|
||||
<div class="d-flex justify-content-between mb-2 border-bottom pb-1">
|
||||
<span class="fw-semibold">SGST ({{ $invoice->sgst_percent }}%):</span>
|
||||
<span class="fw-bold text-danger">₹{{ number_format($invoice->gst_amount/2, 2) }}</span>
|
||||
</div>
|
||||
|
||||
@elseif($invoice->tax_type === 'igst')
|
||||
{{-- IGST --}}
|
||||
<div class="d-flex justify-content-between mb-2 border-bottom pb-1">
|
||||
<span class="fw-semibold">IGST ({{ $invoice->igst_percent }}%):</span>
|
||||
<span class="fw-bold text-danger">₹{{ number_format($invoice->gst_amount, 2) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center pt-1">
|
||||
<span class="fw-bold text-dark">Total With GST:</span>
|
||||
@endif
|
||||
|
||||
<div class="d-flex justify-content-between pt-1">
|
||||
<span class="fw-bold text-dark">Total Payable:</span>
|
||||
<span class="fw-bold text-success">₹{{ number_format($invoice->final_amount_with_gst, 2) }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.date-connector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #6c757d;
|
||||
|
||||
<!-- ============================
|
||||
FOOTER — DOWNLOAD & SHARE
|
||||
============================ -->
|
||||
<div class="modal-footer">
|
||||
|
||||
@if($invoice->pdf_path)
|
||||
<a href="{{ asset($invoice->pdf_path) }}" class="btn btn-primary" download>
|
||||
<i class="bi bi-download me-1"></i> Download PDF
|
||||
</a>
|
||||
|
||||
<button class="btn btn-success" onclick="shareInvoice()">
|
||||
<i class="bi bi-share me-1"></i> Share
|
||||
</button>
|
||||
@endif
|
||||
|
||||
<!-- <button class="btn btn-secondary" data-bs-dismiss="modal">Close</button> -->
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ============================
|
||||
SHARE SCRIPT
|
||||
============================ -->
|
||||
<script>
|
||||
function shareInvoice() {
|
||||
const shareData = {
|
||||
title: "Invoice {{ $invoice->invoice_number }}",
|
||||
text: "Sharing invoice {{ $invoice->invoice_number }}",
|
||||
url: "{{ asset($invoice->pdf_path) }}"
|
||||
};
|
||||
|
||||
if (navigator.share) {
|
||||
navigator.share(shareData).catch(() => {});
|
||||
} else {
|
||||
navigator.clipboard.writeText(shareData.url);
|
||||
alert("Link copied! Sharing not supported on this browser.");
|
||||
}
|
||||
.date-connector i {
|
||||
background: #f8f9fa;
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
.card {
|
||||
border-radius: 6px;
|
||||
}
|
||||
.table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.table > :not(caption) > * > * {
|
||||
padding: 10px 6px;
|
||||
}
|
||||
</style>
|
||||
</script>
|
||||
|
||||
@@ -143,6 +143,18 @@ Route::prefix('admin')
|
||||
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
|
||||
->name('admin.invoices.update');
|
||||
|
||||
Route::post('/invoices/{invoice}/installments', [AdminInvoiceController::class, 'storeInstallment'])
|
||||
->name('admin.invoice.installment.store');
|
||||
|
||||
Route::post('/invoices/{id}/installment', [AdminInvoiceController::class, 'storeInstallment'])
|
||||
->name('admin.invoice.installment.store');
|
||||
|
||||
Route::delete('/installment/{id}', [AdminInvoiceController::class, 'deleteInstallment'])
|
||||
->name('admin.invoice.installment.delete');
|
||||
|
||||
|
||||
|
||||
|
||||
//Add New Invoice
|
||||
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user