603 lines
18 KiB
PHP
603 lines
18 KiB
PHP
@extends('admin.layouts.app')
|
|
|
|
@section('page-title', 'Edit Invoice')
|
|
|
|
@section('content')
|
|
|
|
<style>
|
|
/* --------------------------------------------------
|
|
GLOBAL VARIABLES
|
|
-------------------------------------------------- */
|
|
:root {
|
|
--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);
|
|
}
|
|
|
|
/* --------------------------------------------------
|
|
CARD DESIGN
|
|
-------------------------------------------------- */
|
|
.card {
|
|
background: var(--glass-bg);
|
|
border-radius: 12px;
|
|
border: 1px solid #e4e6ef;
|
|
box-shadow: var(--shadow-strong);
|
|
position: relative;
|
|
overflow: hidden;
|
|
animation: fadeUp 0.8s ease;
|
|
}
|
|
|
|
.card::before {
|
|
content:"";
|
|
position:absolute; inset:0;
|
|
background:linear-gradient(45deg,
|
|
rgba(102,126,234,.03),
|
|
rgba(118,75,162,.03) 50%,
|
|
rgba(16,185,129,.03));
|
|
pointer-events:none;
|
|
}
|
|
|
|
@keyframes fadeUp {
|
|
0% {opacity:0; transform:translateY(30px) scale(.95);}
|
|
100% {opacity:1; transform:translateY(0) scale(1);}
|
|
}
|
|
|
|
/* --------------------------------------------------
|
|
CARD HEADER
|
|
-------------------------------------------------- */
|
|
.card-header {
|
|
background:var(--primary-gradient);
|
|
color:#fff;
|
|
padding:11px 19px;
|
|
border:none;
|
|
position:relative;
|
|
overflow:hidden;
|
|
}
|
|
|
|
.card-header::before {
|
|
content:"";
|
|
position:absolute;
|
|
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 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;
|
|
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;
|
|
}
|
|
|
|
/* --------------------------------------------------
|
|
BODY + FORM ELEMENTS
|
|
-------------------------------------------------- */
|
|
.card-body { padding:15px; background:#f8fafc; }
|
|
|
|
.form-label {
|
|
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;
|
|
background:var(--primary-gradient);
|
|
border-radius:2px;
|
|
}
|
|
|
|
.form-control, .form-select {
|
|
border: 2px solid #e2e8f0;
|
|
padding: 8px 15px;
|
|
border-radius: 10px;
|
|
background: #fff;
|
|
font-size: 1rem;
|
|
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,.15),
|
|
0 8px 24px rgba(102,126,234,.2);
|
|
transform:translateY(-2px) scale(1.02);
|
|
}
|
|
|
|
/* hover */
|
|
.form-control:hover, .form-select:hover {
|
|
border-color:#cbd5e1;
|
|
transform:translateY(-2px);
|
|
box-shadow:0 6px 20px rgba(0,0,0,.08);
|
|
}
|
|
|
|
/* textarea */
|
|
textarea.form-control {
|
|
height: 65px;
|
|
resize: vertical;
|
|
}
|
|
|
|
/* --------------------------------------------------
|
|
BUTTON STYLING
|
|
-------------------------------------------------- */
|
|
.btn-success {
|
|
background: var(--success-gradient);
|
|
border: none;
|
|
padding: 10px 25px;
|
|
border-radius: 7px;
|
|
font-weight: 700;
|
|
font-size: 1.1rem;
|
|
color: #fff;
|
|
transition: .4s;
|
|
box-shadow: var(--shadow-medium);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.btn-success::before {
|
|
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,.4);
|
|
}
|
|
|
|
.btn-success:hover::before { left:100%; }
|
|
|
|
.btn-success.loading {
|
|
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-radius:50%;
|
|
border:2px solid transparent;
|
|
border-top-color:#fff;
|
|
animation:spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {to{transform:rotate(360deg);}}
|
|
|
|
/* --------------------------------------------------
|
|
RESPONSIVE
|
|
-------------------------------------------------- */
|
|
@media(max-width:768px){
|
|
.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;}
|
|
}
|
|
|
|
/* Only animation reduction */
|
|
@media(prefers-reduced-motion:reduce){
|
|
*{animation:none!important; transition:none!important;}
|
|
}
|
|
</style>
|
|
|
|
|
|
|
|
{{-- 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>
|
|
|
|
<div class="card-body">
|
|
<form action="{{ route('admin.invoices.update', $invoice->id) }}" method="POST">
|
|
@csrf
|
|
|
|
<div class="row g-3">
|
|
|
|
<div class="col-md-4">
|
|
<label class="form-label">Invoice Date</label>
|
|
<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"
|
|
value="{{ $invoice->due_date }}" required>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<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 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">
|
|
<label class="form-label">Status</label>
|
|
<select class="form-select" name="status" required>
|
|
<option value="pending" @selected($invoice->status=='pending')>Pending</option>
|
|
<option value="paid" @selected($invoice->status=='paid')>Paid</option>
|
|
<option value="overdue" @selected($invoice->status=='overdue')>Overdue</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-md-12">
|
|
<label class="form-label">Notes</label>
|
|
<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>
|
|
</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>
|
|
|
|
</div>
|
|
</form>
|
|
</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>
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
|
|
// ✅ Toggle Installment Form
|
|
const toggleBtn = document.getElementById("toggleInstallmentForm");
|
|
const formBox = document.getElementById("installmentForm");
|
|
|
|
if (toggleBtn) {
|
|
toggleBtn.addEventListener("click", () => formBox.classList.toggle("d-none"));
|
|
}
|
|
|
|
// ✅ Add Installment
|
|
const submitForm = document.getElementById("installmentSubmitForm");
|
|
const submitBtn = document.getElementById("installmentSubmitBtn");
|
|
|
|
const formatINR = amt =>
|
|
"₹" + Number(amt).toLocaleString("en-IN", {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
});
|
|
|
|
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);
|
|
}
|
|
});
|
|
});
|
|
|
|
});
|
|
</script>
|
|
|
|
|
|
|
|
|
|
@endsection
|