Frontend Changes
This commit is contained in:
@@ -22,7 +22,7 @@ class AdminInvoiceController extends Controller
|
|||||||
{
|
{
|
||||||
$query = Invoice::with(['items', 'customer', 'container']);
|
$query = Invoice::with(['items', 'customer', 'container']);
|
||||||
|
|
||||||
// Search (जर पुढे form मधून पाठवलंस तर)
|
// Search
|
||||||
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) {
|
||||||
@@ -400,6 +400,40 @@ class AdminInvoiceController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->ajax()) {
|
||||||
|
// load items with invoice item relation
|
||||||
|
$group->load(['items.item']);
|
||||||
|
|
||||||
|
// prepare simple array for JS
|
||||||
|
$itemsPayload = $group->items->map(function ($gi) {
|
||||||
|
$it = $gi->item;
|
||||||
|
return [
|
||||||
|
'description' => $it->description,
|
||||||
|
'qty' => $it->qty,
|
||||||
|
'ttlqty' => $it->ttl_qty,
|
||||||
|
'cbm' => $it->cbm,
|
||||||
|
'ttlcbm' => $it->ttl_cbm,
|
||||||
|
'kg' => $it->kg,
|
||||||
|
'ttlkg' => $it->ttl_kg,
|
||||||
|
'amount' => $it->ttl_amount,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Charge group created successfully.',
|
||||||
|
'group' => [
|
||||||
|
'id' => $group->id,
|
||||||
|
'group_name' => $group->group_name,
|
||||||
|
'basis_type' => $group->basis_type,
|
||||||
|
'basis_value' => $group->basis_value,
|
||||||
|
'rate' => $group->rate,
|
||||||
|
'total_charge'=> $group->total_charge,
|
||||||
|
'items' => $itemsPayload,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
->back()
|
->back()
|
||||||
->with('success', 'Charge group saved successfully.');
|
->with('success', 'Charge group saved successfully.');
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use App\Models\Invoice;
|
|||||||
use App\Models\InvoiceItem;
|
use App\Models\InvoiceItem;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Maatwebsite\Excel\Facades\Excel;
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
class ContainerController extends Controller
|
class ContainerController extends Controller
|
||||||
{
|
{
|
||||||
@@ -530,7 +531,9 @@ class ContainerController extends Controller
|
|||||||
|
|
||||||
$invoice->invoice_number = $this->generateInvoiceNumber();
|
$invoice->invoice_number = $this->generateInvoiceNumber();
|
||||||
$invoice->invoice_date = now()->toDateString();
|
$invoice->invoice_date = now()->toDateString();
|
||||||
$invoice->due_date = null;
|
// NEW (add this):
|
||||||
|
$invoice->due_date = Carbon::parse($invoice->invoice_date)->addDays(10)->format('Y-m-d');
|
||||||
|
|
||||||
|
|
||||||
if ($snap) {
|
if ($snap) {
|
||||||
$invoice->customer_name = $snap['customer_name'] ?? null;
|
$invoice->customer_name = $snap['customer_name'] ?? null;
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?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('invoices', function (Blueprint $table) {
|
||||||
|
$table->date('duedate')->nullable()->after('invoicedate');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('duedate');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
@@ -1683,6 +1683,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
@@ -875,5 +875,29 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
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())) {
|
||||||
|
// १० दिवस पुढे नेण्यासाठी logic
|
||||||
|
selectedDate.setDate(selectedDate.getDate() + 10);
|
||||||
|
|
||||||
|
// तारीख YYYY-MM-DD format मध्ये करण्यासाठी
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -1314,25 +1314,42 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
|
|
||||||
if (cgForm) {
|
if (cgForm) {
|
||||||
cgForm.addEventListener('submit', function (e) {
|
cgForm.addEventListener('submit', function (e) {
|
||||||
const selectedIds = [];
|
// Stop normal form submit (no page reload)
|
||||||
itemCheckboxes.forEach(cb => { if (cb.checked && !cb.disabled) selectedIds.push(cb.value); });
|
|
||||||
if (selectedIds.length === 0) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 1) Collect selected item IDs
|
||||||
|
const selectedIds = [];
|
||||||
|
itemCheckboxes.forEach(cb => {
|
||||||
|
if (cb.checked && !cb.disabled) {
|
||||||
|
selectedIds.push(cb.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2) Frontend validations (same as before)
|
||||||
|
if (selectedIds.length === 0) {
|
||||||
alert('Please select at least one item for this charge group.');
|
alert('Please select at least one item for this charge group.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cgBasisSelect || !cgBasisSelect.value) {
|
if (!cgBasisSelect || !cgBasisSelect.value) {
|
||||||
e.preventDefault();
|
|
||||||
alert('Please select a basis for this charge group.');
|
alert('Please select a basis for this charge group.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!cgTotalChargeInput || !cgTotalChargeInput.value || parseFloat(cgTotalChargeInput.value) <= 0) {
|
|
||||||
e.preventDefault();
|
if (
|
||||||
|
!cgTotalChargeInput ||
|
||||||
|
!cgTotalChargeInput.value ||
|
||||||
|
parseFloat(cgTotalChargeInput.value) <= 0
|
||||||
|
) {
|
||||||
alert('Please enter total charges for this group.');
|
alert('Please enter total charges for this group.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3) Remove previously added hidden item_ids[]
|
||||||
const oldHidden = cgForm.querySelectorAll('input[name="item_ids[]"]');
|
const oldHidden = cgForm.querySelectorAll('input[name="item_ids[]"]');
|
||||||
oldHidden.forEach(el => el.remove());
|
oldHidden.forEach(el => el.remove());
|
||||||
|
|
||||||
|
// 4) Add fresh hidden item_ids[] for current selection
|
||||||
selectedIds.forEach(id => {
|
selectedIds.forEach(id => {
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'hidden';
|
input.type = 'hidden';
|
||||||
@@ -1340,10 +1357,185 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
input.value = id;
|
input.value = id;
|
||||||
cgForm.appendChild(input);
|
cgForm.appendChild(input);
|
||||||
});
|
});
|
||||||
if (chargeGroupBox) chargeGroupBox.classList.add('d-none');
|
|
||||||
itemCheckboxes.forEach(cb => { if (!cb.disabled) cb.checked = false; });
|
// 5) Build AJAX request
|
||||||
if (selectAll) { selectAll.checked = false; selectAll.indeterminate = false; }
|
const url = cgForm.action;
|
||||||
|
const formData = new FormData(cgForm);
|
||||||
|
|
||||||
|
// Optional: disable save button while processing
|
||||||
|
if (cgSaveBtn) {
|
||||||
|
cgSaveBtn.disabled = true;
|
||||||
|
cgSaveBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Saving...';
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('Request failed with status ' + res.status);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error(data.message || 'Failed to create charge group.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = data.group;
|
||||||
|
|
||||||
|
// 6) Append new group main row + hidden items row
|
||||||
|
const groupsTbody = document.querySelector(
|
||||||
|
'.cg-groups-panel table.invoice-table tbody'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (groupsTbody && group) {
|
||||||
|
const currentMainRows = groupsTbody.querySelectorAll(
|
||||||
|
'tr:not(.cg-items-row)'
|
||||||
|
).length;
|
||||||
|
const index = currentMainRows + 1;
|
||||||
|
|
||||||
|
// Main summary row (same structure as Blade)
|
||||||
|
const mainRow = document.createElement('tr');
|
||||||
|
mainRow.innerHTML = `
|
||||||
|
<td>${index}</td>
|
||||||
|
<td style="font-weight:600;">
|
||||||
|
${group.group_name ? group.group_name : 'Group #' + group.id}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span style="text-transform:uppercase;font-size:0.75rem;font-weight:700;color:var(--text-muted);">
|
||||||
|
${group.basis_type}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end" style="font-family:'JetBrains Mono',monospace;font-size:0.82rem;">
|
||||||
|
${Number(group.basis_value).toFixed(3)}
|
||||||
|
</td>
|
||||||
|
<td class="text-end" style="font-family:'JetBrains Mono',monospace;font-size:0.82rem;">
|
||||||
|
${Number(group.rate).toFixed(2)}
|
||||||
|
</td>
|
||||||
|
<td class="text-end price-blue">
|
||||||
|
${Number(group.total_charge).toFixed(2)}
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button type="button"
|
||||||
|
class="cg-toggle-btn cg-toggle-items"
|
||||||
|
data-group-id="${group.id}">
|
||||||
|
<i class="fas fa-eye"></i> View
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Hidden items row (same idea as Blade .cg-items-row)
|
||||||
|
const itemsRow = document.createElement('tr');
|
||||||
|
itemsRow.className = 'cg-items-row d-none';
|
||||||
|
itemsRow.setAttribute('data-group-id', group.id);
|
||||||
|
|
||||||
|
let itemsTableHtml = `
|
||||||
|
<div style="padding:1rem;background:#f8faff;border-radius:8px;margin:0.25rem 0;">
|
||||||
|
<div style="font-weight:700;font-size:0.8rem;margin-bottom:0.6rem;color:var(--primary);">
|
||||||
|
Items in this group
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!group.items || group.items.length === 0) {
|
||||||
|
itemsTableHtml += `
|
||||||
|
<div style="color:var(--text-muted);font-size:0.82rem;">
|
||||||
|
No items linked.
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
itemsTableHtml += `
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="invoice-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th class="text-center">QTY</th>
|
||||||
|
<th class="text-center">TTL QTY</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-end">TTL Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
group.items.forEach((it, idx) => {
|
||||||
|
itemsTableHtml += `
|
||||||
|
<tr>
|
||||||
|
<td>${idx + 1}</td>
|
||||||
|
<td>${it.description ?? ''}</td>
|
||||||
|
<td class="text-center">${it.qty ?? ''}</td>
|
||||||
|
<td class="text-center">${it.ttlqty ?? ''}</td>
|
||||||
|
<td class="text-center">${it.cbm ?? ''}</td>
|
||||||
|
<td class="text-center">${it.ttlcbm ?? ''}</td>
|
||||||
|
<td class="text-center">${it.kg ?? ''}</td>
|
||||||
|
<td class="text-center">${it.ttlkg ?? ''}</td>
|
||||||
|
<td class="text-end">${Number(it.amount ?? 0).toFixed(2)}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
itemsTableHtml += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsTableHtml += `</div>`;
|
||||||
|
|
||||||
|
itemsRow.innerHTML = `
|
||||||
|
<td colspan="7">
|
||||||
|
${itemsTableHtml}
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Append main row + items row in order
|
||||||
|
groupsTbody.appendChild(mainRow);
|
||||||
|
groupsTbody.appendChild(itemsRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7) Reset Charge Group UI (hide panel, uncheck items)
|
||||||
|
if (chargeGroupBox) {
|
||||||
|
chargeGroupBox.classList.add('d-none');
|
||||||
|
}
|
||||||
|
itemCheckboxes.forEach(cb => {
|
||||||
|
if (!cb.disabled) cb.checked = false;
|
||||||
|
});
|
||||||
|
if (selectAll) {
|
||||||
|
selectAll.checked = false;
|
||||||
|
selectAll.indeterminate = false;
|
||||||
|
}
|
||||||
updateSelectionState();
|
updateSelectionState();
|
||||||
|
|
||||||
|
// Optional: clear form
|
||||||
|
if (cgGroupName) cgGroupName.value = '';
|
||||||
|
if (cgBasisSelect) cgBasisSelect.value = '';
|
||||||
|
if (cgRateInput) cgRateInput.value = '';
|
||||||
|
if (cgBasisValueSpan) cgBasisValueSpan.textContent = '0';
|
||||||
|
if (cgBasisLabelSpan) cgBasisLabelSpan.textContent = '–';
|
||||||
|
if (cgSuggestedTotalSpan) cgSuggestedTotalSpan.textContent = '0';
|
||||||
|
if (cgTotalChargeInput) cgTotalChargeInput.value = '';
|
||||||
|
})
|
||||||
|
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
alert('Error creating charge group. Please try again.');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (cgSaveBtn) {
|
||||||
|
cgSaveBtn.disabled = false;
|
||||||
|
cgSaveBtn.innerHTML = '<i class="fas fa-save"></i> Save Charge Group';
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user