chore: initial commit of existing codebase
This commit is contained in:
commit
11e94889b2
292 files changed
+38586
No files matched your search
@@ -0,0 +1,128 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'New Sales Invoice')
|
||||
|
||||
@section('content')
|
||||
<div class="mb-6">
|
||||
<h1 class="page-title">New Sales Invoice</h1>
|
||||
<p class="page-subtitle"><a href="{{ route('sales.invoices.index') }}" class="text-blue-600 hover:underline">Sales Invoices</a> / New</p>
|
||||
</div>
|
||||
|
||||
@if($errors->any())
|
||||
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="card card-body max-w-2xl">
|
||||
<form action="{{ route('sales.invoices.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
|
||||
|
||||
<div>
|
||||
<label class="form-label">Sales Order <span class="text-red-500">*</span></label>
|
||||
<select name="sales_order_id" id="inv-so-select" required onchange="fillCustomer(this)" class="form-select">
|
||||
<option value="">-- Select Dispatched Order --</option>
|
||||
@foreach($salesOrders as $so)
|
||||
<option value="{{ $so->id }}"
|
||||
data-customer="{{ $so->customer_id }}"
|
||||
data-total="{{ $so->total_amount }}"
|
||||
{{ old('sales_order_id') == $so->id ? 'selected' : '' }}>
|
||||
{{ $so->order_number ?? 'SO-' . str_pad($so->id, 5, '0', STR_PAD_LEFT) }} - {{ $so->customer->name ?? '' }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">Customer</label>
|
||||
<select name="customer_id" id="inv-customer" class="form-select">
|
||||
<option value="">-- Auto-filled from SO --</option>
|
||||
@foreach($customers as $customer)
|
||||
<option value="{{ $customer->id }}" {{ old('customer_id') == $customer->id ? 'selected' : '' }}>
|
||||
{{ $customer->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">Invoice Date <span class="text-red-500">*</span></label>
|
||||
<input type="date" name="invoice_date" value="{{ old('invoice_date', date('Y-m-d')) }}" required class="form-input">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">Due Date</label>
|
||||
<input type="date" name="due_date" value="{{ old('due_date') }}" class="form-input">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">Subtotal <span class="text-red-500">*</span></label>
|
||||
<input type="number" name="subtotal" id="inv-subtotal" value="{{ old('subtotal') }}"
|
||||
min="0" step="0.01" required onchange="calcSalesInvoiceTotal()" class="form-input">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">VAT Rate (%)</label>
|
||||
<input type="number" name="vat_rate" id="inv-vat-rate" value="{{ old('vat_rate', 0) }}"
|
||||
min="0" max="100" step="0.01" onchange="calcSalesInvoiceTotal()" class="form-input">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">VAT Amount</label>
|
||||
<input type="number" name="vat_amount" id="inv-vat-amount" value="{{ old('vat_amount', 0) }}"
|
||||
min="0" step="0.01" readonly
|
||||
class="w-full border border-gray-200 bg-gray-50 rounded-lg px-3 py-2 text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">Total Amount</label>
|
||||
<input type="number" name="total_amount" id="inv-total" value="{{ old('total_amount') }}"
|
||||
readonly
|
||||
class="w-full border border-gray-200 bg-gray-50 rounded-lg px-3 py-2 text-sm font-semibold text-gray-800">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex items-center gap-3">
|
||||
<button type="submit" class="btn-primary">Save Invoice</button>
|
||||
<a href="{{ route('sales.invoices.index') }}" class="btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function fillCustomer(select) {
|
||||
var option = select.options[select.selectedIndex];
|
||||
var customerId = option.dataset.customer;
|
||||
var total = option.dataset.total;
|
||||
var customerSelect = document.getElementById('inv-customer');
|
||||
|
||||
// Auto-select customer
|
||||
for (var i = 0; i < customerSelect.options.length; i++) {
|
||||
if (customerSelect.options[i].value == customerId) {
|
||||
customerSelect.selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-fill subtotal from SO
|
||||
if (total) {
|
||||
document.getElementById('inv-subtotal').value = parseFloat(total).toFixed(2);
|
||||
calcSalesInvoiceTotal();
|
||||
}
|
||||
}
|
||||
|
||||
function calcSalesInvoiceTotal() {
|
||||
var subtotal = parseFloat(document.getElementById('inv-subtotal').value) || 0;
|
||||
var vatRate = parseFloat(document.getElementById('inv-vat-rate').value) || 0;
|
||||
var vatAmount = subtotal * vatRate / 100;
|
||||
document.getElementById('inv-vat-amount').value = vatAmount.toFixed(2);
|
||||
document.getElementById('inv-total').value = (subtotal + vatAmount).toFixed(2);
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,81 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Edit Sales Invoice')
|
||||
|
||||
@section('content')
|
||||
<div class="mb-6">
|
||||
<h1 class="page-title">Edit Sales Invoice</h1>
|
||||
<p class="page-subtitle"><a href="{{ route('sales.invoices.index') }}" class="text-blue-600 hover:underline">Sales Invoices</a> / Edit</p>
|
||||
</div>
|
||||
|
||||
@if($errors->any())
|
||||
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="card card-body max-w-2xl">
|
||||
<form action="{{ route('sales.invoices.update', $invoice) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
|
||||
|
||||
<div>
|
||||
<label class="form-label">Invoice Date <span class="text-red-500">*</span></label>
|
||||
<input type="date" name="invoice_date" value="{{ old('invoice_date', $invoice->invoice_date ? \Carbon\Carbon::parse($invoice->invoice_date)->format('Y-m-d') : '') }}" required class="form-input">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">Due Date</label>
|
||||
<input type="date" name="due_date" value="{{ old('due_date', $invoice->due_date ? \Carbon\Carbon::parse($invoice->due_date)->format('Y-m-d') : '') }}" class="form-input">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">Subtotal</label>
|
||||
<input type="number" name="subtotal" id="edit-subtotal" value="{{ old('subtotal', $invoice->subtotal) }}"
|
||||
min="0" step="0.01" onchange="calcEditTotal()" class="form-input">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">VAT Amount</label>
|
||||
<input type="number" name="vat_amount" id="edit-vat" value="{{ old('vat_amount', $invoice->vat_amount) }}"
|
||||
min="0" step="0.01" onchange="calcEditTotal()" class="form-input">
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-2">
|
||||
<label class="form-label">Total Amount</label>
|
||||
<input type="number" name="total_amount" id="edit-total" value="{{ old('total_amount', $invoice->total_amount) }}"
|
||||
readonly
|
||||
class="w-full border border-gray-200 bg-gray-50 rounded-lg px-3 py-2 text-sm font-semibold text-gray-800">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label">Status</label>
|
||||
<select name="status" class="form-select">
|
||||
<option value="unpaid" {{ old('status', $invoice->status) === 'unpaid' ? 'selected' : '' }}>Unpaid</option>
|
||||
<option value="partial" {{ old('status', $invoice->status) === 'partial' ? 'selected' : '' }}>Partial</option>
|
||||
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>Paid</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex items-center gap-3">
|
||||
<button type="submit" class="btn-primary">Update Invoice</button>
|
||||
<a href="{{ route('sales.invoices.index') }}" class="btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function calcEditTotal() {
|
||||
var sub = parseFloat(document.getElementById('edit-subtotal').value) || 0;
|
||||
var vat = parseFloat(document.getElementById('edit-vat').value) || 0;
|
||||
document.getElementById('edit-total').value = (sub + vat).toFixed(2);
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,77 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Sales Invoices')
|
||||
|
||||
@section('content')
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Sales Invoices</h1>
|
||||
<p class="page-subtitle">Track customer invoices and payments</p>
|
||||
</div>
|
||||
<a href="{{ route('sales.invoices.create') }}" class="btn-primary">
|
||||
+ New Invoice
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper overflow-x-auto">
|
||||
<table class="table-base">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice #</th>
|
||||
<th>Customer</th>
|
||||
<th>SO #</th>
|
||||
<th>Date</th>
|
||||
<th class="text-right">Total</th>
|
||||
<th class="text-right">Paid</th>
|
||||
<th class="text-right">Outstanding</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($invoices as $invoice)
|
||||
@php $outstanding = $invoice->total_amount - $invoice->paid_amount; @endphp
|
||||
<tr>
|
||||
<td class="font-mono text-gray-700">{{ $invoice->invoice_number }}</td>
|
||||
<td class="text-gray-800">{{ $invoice->customer->name ?? '' }}</td>
|
||||
<td class="font-mono text-gray-600">{{ $invoice->salesOrder->order_number ?? '-' }}</td>
|
||||
<td class="text-gray-600">{{ $invoice->invoice_date ? \Carbon\Carbon::parse($invoice->invoice_date)->format('d M Y') : '' }}</td>
|
||||
<td class="text-right text-gray-800">{{ number_format($invoice->total_amount, 2) }}</td>
|
||||
<td class="text-right text-green-700">{{ number_format($invoice->paid_amount, 2) }}</td>
|
||||
<td class="text-right {{ $outstanding > 0 ? 'text-red-600 font-semibold' : 'text-gray-500' }}">{{ number_format($outstanding, 2) }}</td>
|
||||
<td>
|
||||
@php
|
||||
$badgeClass = match($invoice->status ?? 'unpaid') {
|
||||
'unpaid' => 'badge-red',
|
||||
'partial' => 'badge-yellow',
|
||||
'paid' => 'badge-green',
|
||||
default => 'badge-gray',
|
||||
};
|
||||
@endphp
|
||||
<span class="{{ $badgeClass }}">{{ ucfirst($invoice->status ?? 'unpaid') }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<a href="{{ route('sales.payments.create', ['invoice_id' => $invoice->id]) }}" class="btn-success btn-sm">Receive</a>
|
||||
<a href="{{ route('sales.invoices.edit', $invoice) }}" class="btn-secondary btn-sm">Edit</a>
|
||||
<form action="{{ route('sales.invoices.destroy', $invoice) }}" method="POST"
|
||||
onsubmit="confirmDelete(this,'Delete this invoice?','This invoice will be permanently removed.'); return false;">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn-danger btn-sm">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="9" class="px-4 py-8 text-center text-gray-400">No invoices found.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if($invoices->hasPages())
|
||||
<div class="mt-4">{{ $invoices->links() }}</div>
|
||||
@endif
|
||||
@endsection
|
||||
Reference in new issue
Block a user