1067 lines
53 KiB
JavaScript
1067 lines
53 KiB
JavaScript
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log("DOM Content Loaded - Accounting System");
|
|
try {
|
|
// Initialize Select2 for all searchable dropdowns
|
|
$('.select2').each(function() {
|
|
$(this).select2({
|
|
width: '100%',
|
|
dropdownParent: $(this).closest('.modal').length ? $(this).closest('.modal') : $(document.body)
|
|
});
|
|
});
|
|
|
|
const hasExpiryToggle = document.getElementById('hasExpiryToggle');
|
|
const expiryDateContainer = document.getElementById('expiryDateContainer');
|
|
const suggestSkuBtn = document.getElementById('suggestSkuBtn');
|
|
const skuInput = document.getElementById('skuInput');
|
|
|
|
if (suggestSkuBtn && skuInput) {
|
|
suggestSkuBtn.addEventListener('click', function() {
|
|
const sku = Math.floor(100000000000 + Math.random() * 900000000000).toString();
|
|
skuInput.value = sku;
|
|
});
|
|
|
|
skuInput.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
console.log("Barcode scan detected in SKU field, preventing form submission");
|
|
}
|
|
});
|
|
}
|
|
|
|
// Toggle Expiry Date visibility
|
|
if (hasExpiryToggle && expiryDateContainer) {
|
|
hasExpiryToggle.addEventListener('change', function() {
|
|
expiryDateContainer.style.display = this.checked ? 'block' : 'none';
|
|
});
|
|
}
|
|
|
|
const isPromotionToggle = document.getElementById('isPromotionToggle');
|
|
const promotionFieldsContainer = document.getElementById('promotionFieldsContainer');
|
|
if (isPromotionToggle && promotionFieldsContainer) {
|
|
isPromotionToggle.addEventListener('change', function() {
|
|
promotionFieldsContainer.style.display = this.checked ? 'flex' : 'none';
|
|
});
|
|
}
|
|
|
|
// Translation Logic
|
|
document.querySelectorAll('.btn-translate').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const sourceId = this.getAttribute('data-source');
|
|
const targetId = this.getAttribute('data-target');
|
|
const targetLang = this.getAttribute('data-to');
|
|
const sourceText = document.getElementById(sourceId).value;
|
|
|
|
if (!sourceText) {
|
|
alert(targetLang === 'ar' ? 'يرجى إدخال النص أولاً' : 'Please enter text first');
|
|
return;
|
|
}
|
|
|
|
const originalHtml = this.innerHTML;
|
|
this.disabled = true;
|
|
this.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
|
|
|
const formData = new FormData();
|
|
formData.append('action', 'translate');
|
|
formData.append('text', sourceText);
|
|
formData.append('target', targetLang);
|
|
|
|
fetch('index.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById(targetId).value = data.translated;
|
|
} else {
|
|
alert('Translation error: ' + (data.error || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
alert('Connection error');
|
|
})
|
|
.finally(() => {
|
|
this.disabled = false;
|
|
this.innerHTML = originalHtml;
|
|
});
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.isPromotionToggleEdit').forEach(toggle => {
|
|
toggle.addEventListener('change', function() {
|
|
const id = this.getAttribute('data-id');
|
|
const container = document.getElementById('promotionFieldsContainerEdit' + id);
|
|
if (container) {
|
|
container.style.display = this.checked ? 'flex' : 'none';
|
|
}
|
|
});
|
|
});
|
|
|
|
// Handle Expiry toggle in Edit Modals
|
|
document.querySelectorAll('.hasExpiryToggleEdit').forEach(toggle => {
|
|
toggle.addEventListener('change', function() {
|
|
const container = this.closest('.row').querySelector('.expiryDateContainerEdit');
|
|
if (container) {
|
|
container.style.display = this.checked ? 'block' : 'none';
|
|
if (!this.checked) {
|
|
container.querySelector('input').value = '';
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// LPO Form Logic
|
|
initInvoiceForm('lpoProductSearchInput', 'lpoSearchSuggestions', 'lpoItemsTableBody', 'lpo_grand_display', 'lpo_subtotal_display', 'lpo_vat_display');
|
|
initInvoiceForm('editLpoProductSearchInput', 'editLpoSearchSuggestions', 'editLpoItemsTableBody', 'edit_lpo_grand_display', 'edit_lpo_subtotal_display', 'edit_lpo_vat_display');
|
|
|
|
document.querySelectorAll('.edit-lpo-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const data = JSON.parse(this.dataset.json);
|
|
document.getElementById('edit_lpo_id').value = data.id;
|
|
const supplierSelect = document.getElementById('edit_lpo_supplier_id');
|
|
supplierSelect.value = data.supplier_id;
|
|
if (window.jQuery && $(supplierSelect).data('select2')) {
|
|
$(supplierSelect).trigger('change');
|
|
}
|
|
document.getElementById('edit_lpo_date').value = data.lpo_date;
|
|
document.getElementById('edit_lpo_delivery_date').value = data.delivery_date || '';
|
|
document.getElementById('edit_lpo_status').value = data.status || 'pending';
|
|
document.getElementById('edit_lpo_terms').value = data.terms_conditions || '';
|
|
|
|
const tableBody = document.getElementById('editLpoItemsTableBody');
|
|
tableBody.innerHTML = '';
|
|
|
|
data.items.forEach(item => {
|
|
addItemToTable({
|
|
id: item.item_id,
|
|
name_en: item.name_en,
|
|
name_ar: item.name_ar,
|
|
sku: '',
|
|
vat_rate: item.vat_rate || 0
|
|
}, tableBody, null, null,
|
|
document.getElementById('edit_lpo_grand_display'),
|
|
document.getElementById('edit_lpo_subtotal_display'),
|
|
document.getElementById('edit_lpo_vat_display'),
|
|
{ quantity: item.quantity, unit_price: item.unit_price });
|
|
});
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.view-lpo-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const data = JSON.parse(this.dataset.json);
|
|
window.viewAndPrintLPO(data);
|
|
});
|
|
});
|
|
|
|
window.viewAndPrintLPO = function(data) {
|
|
const modal = new bootstrap.Modal(document.getElementById('viewLpoModal'));
|
|
const content = document.getElementById('lpoDetailsContent');
|
|
|
|
const logoUrl = companySettings.company_logo || '';
|
|
const companyHeader = `
|
|
<div class="row align-items-center mb-4">
|
|
<div class="col-6">
|
|
${logoUrl ? `<img src="${logoUrl}" alt="Logo" style="max-height: 80px;" class="mb-3">` : ''}
|
|
<h4 class="fw-bold mb-0">${companySettings.company_name || 'Your Company'}</h4>
|
|
<p class="text-muted mb-0 small">
|
|
${companySettings.company_address || ''}<br>
|
|
Phone: ${companySettings.company_phone || ''} | Email: ${companySettings.company_email || ''}
|
|
${companySettings.tax_number ? `<br>TRN: ${companySettings.tax_number}` : ''}
|
|
</p>
|
|
</div>
|
|
<div class="col-6 text-end">
|
|
<h2 class="text-primary fw-bold mb-1">LOCAL PURCHASE ORDER</h2>
|
|
<p class="h5 mb-0 text-muted">LPO-${data.id.toString().padStart(5, '0')}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
let itemsHtml = '';
|
|
data.items.forEach((item, index) => {
|
|
itemsHtml += `
|
|
<tr>
|
|
<td>${index + 1}</td>
|
|
<td>${item.name_en}<br><small class="text-muted">${item.name_ar}</small></td>
|
|
<td class="text-center">${item.quantity}</td>
|
|
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
|
|
<td class="text-center">${parseFloat(item.vat_rate || 0).toFixed(2)}%</td>
|
|
<td class="text-end">${parseFloat(item.total_amount).toFixed(3)}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
content.innerHTML = `
|
|
${companyHeader}
|
|
<hr>
|
|
<div class="row mb-4">
|
|
<div class="col-6">
|
|
<h6 class="text-uppercase text-muted fw-bold mb-3" data-en="Supplier" data-ar="المورد">Supplier</h6>
|
|
<p class="h6 mb-1 fw-bold">${data.supplier_name}</p>
|
|
<p class="small text-muted mb-0">
|
|
${data.supplier_phone ? `Phone: ${data.supplier_phone}` : ''}
|
|
</p>
|
|
</div>
|
|
<div class="col-6 text-end">
|
|
<h6 class="text-uppercase text-muted fw-bold mb-3" data-en="Details" data-ar="تفاصيل">Details</h6>
|
|
<div class="d-flex justify-content-end mb-1">
|
|
<span class="text-muted me-2">Date:</span>
|
|
<span class="fw-bold">${data.lpo_date}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-end mb-1">
|
|
<span class="text-muted me-2">Delivery:</span>
|
|
<span class="fw-bold">${data.delivery_date || '---'}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-end">
|
|
<span class="text-muted me-2">Status:</span>
|
|
<span class="badge ${data.status === 'pending' ? 'bg-warning text-dark' : 'bg-success'}">${data.status.toUpperCase()}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-bordered">
|
|
<thead class="table-dark">
|
|
<tr>
|
|
<th style="width: 5%">#</th>
|
|
<th style="width: 45%" data-en="Description" data-ar="الوصف">Description</th>
|
|
<th style="width: 10%" class="text-center" data-en="Qty" data-ar="الكمية">Qty</th>
|
|
<th style="width: 15%" class="text-end">Unit Price</th>
|
|
<th style="width: 10%" class="text-center">VAT</th>
|
|
<th style="width: 15%" class="text-end" data-en="Total" data-ar="الإجمالي">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>${itemsHtml}</tbody>
|
|
<tfoot class="table-light">
|
|
<tr>
|
|
<td colspan="5" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
|
|
<td class="text-end fw-bold">OMR ${parseFloat(data.total_amount).toFixed(3)}</td>
|
|
</tr>
|
|
<tr>
|
|
<td colspan="5" class="text-end fw-bold">VAT Amount</td>
|
|
<td class="text-end fw-bold">OMR ${parseFloat(data.vat_amount).toFixed(2)}</td>
|
|
</tr>
|
|
<tr class="table-primary">
|
|
<td colspan="5" class="text-end fw-bold h5">Grand Total</td>
|
|
<td class="text-end fw-bold h5">OMR ${parseFloat(data.total_with_vat).toFixed(3)}</td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
${data.terms_conditions ? `
|
|
<div class="card bg-light border-0 mt-4">
|
|
<div class="card-body p-3">
|
|
<h6 class="fw-bold mb-2 small text-uppercase text-muted">Terms & Conditions</h6>
|
|
<p class="small mb-0">${data.terms_conditions.replace(/\n/g, '<br>')}</p>
|
|
</div>
|
|
</div>` : ''}
|
|
|
|
<div class="row mt-5 pt-3">
|
|
<div class="col-4 text-center">
|
|
<div style="border-top: 1px solid #dee2e6; padding-top: 10px;">
|
|
<p class="small mb-0">Prepared By</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-4"></div>
|
|
<div class="col-4 text-center">
|
|
<div style="border-top: 1px solid #dee2e6; padding-top: 10px;">
|
|
<p class="small mb-0">Authorized Signature</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
window.printLPO = function() {
|
|
const printWindow = window.open('', '_blank');
|
|
printWindow.document.write('<html><head><title>LPO-' + data.id + '</title>');
|
|
printWindow.document.write('<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.rtl.min.css" rel="stylesheet">');
|
|
printWindow.document.write('<style>body { padding: 40px; } @media print { .no-print { display: none; } }</style>');
|
|
printWindow.document.write('</head><body>');
|
|
printWindow.document.write(content.innerHTML);
|
|
printWindow.document.write('</body></html>');
|
|
printWindow.document.close();
|
|
setTimeout(() => {
|
|
printWindow.print();
|
|
printWindow.close();
|
|
}, 500);
|
|
};
|
|
|
|
modal.show();
|
|
};
|
|
|
|
// Quotation Form Logic
|
|
initInvoiceForm('quotProductSearchInput', 'quotSearchSuggestions', 'quotItemsTableBody', 'quot_grand_display', 'quot_subtotal_display', 'quot_vat_display');
|
|
initInvoiceForm('editQuotProductSearchInput', 'editQuotSearchSuggestions', 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display');
|
|
|
|
document.querySelectorAll('.edit-quotation-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const data = JSON.parse(this.dataset.json);
|
|
document.getElementById('edit_quotation_id').value = data.id;
|
|
document.getElementById('edit_quot_customer_id').value = data.customer_id;
|
|
document.getElementById('edit_quot_date').value = data.quotation_date;
|
|
document.getElementById('edit_quot_valid').value = data.valid_until || '';
|
|
document.getElementById('edit_quot_status').value = data.status || 'pending';
|
|
|
|
const tableBody = document.getElementById('editQuotItemsTableBody');
|
|
tableBody.innerHTML = '';
|
|
|
|
data.items.forEach(item => {
|
|
addItemToTable({
|
|
id: item.item_id,
|
|
name_en: item.name_en,
|
|
name_ar: item.name_ar,
|
|
sku: '',
|
|
vat_rate: item.vat_rate || 0
|
|
}, tableBody, null, null,
|
|
document.getElementById('edit_quot_grand_display'),
|
|
document.getElementById('edit_quot_subtotal_display'),
|
|
document.getElementById('edit_quot_vat_display'),
|
|
{ quantity: item.quantity, unit_price: item.unit_price });
|
|
});
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.convert-quotation-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
if (confirm('Convert this quotation to an invoice? This will reduce stock.')) {
|
|
const f = document.createElement('form');
|
|
f.method = 'POST';
|
|
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${this.dataset.id}">`;
|
|
document.body.appendChild(f);
|
|
f.submit();
|
|
}
|
|
});
|
|
});
|
|
|
|
// View Quotation Logic
|
|
window.viewAndPrintQuotation = function(data, autoPrint = false) {
|
|
const modal = new bootstrap.Modal(document.getElementById('viewQuotationModal'));
|
|
const content = document.getElementById('quotationPrintableArea');
|
|
|
|
let itemsHtml = '';
|
|
data.items.forEach((item, index) => {
|
|
itemsHtml += `
|
|
<tr>
|
|
<td>${index + 1}</td>
|
|
<td>${item.name_en}<br><small>${item.name_ar}</small></td>
|
|
<td class="text-center">${item.quantity}</td>
|
|
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
|
|
<td class="text-center">${parseFloat(item.vat_rate || 0).toFixed(2)}%</td>
|
|
<td class="text-end">${parseFloat(item.total_price).toFixed(3)}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
// Company Logo and Header Construction
|
|
const logoUrl = companySettings.company_logo || '';
|
|
const logoImg = logoUrl ? `<img src="${logoUrl}" alt="Logo" class="invoice-logo mb-3">` : '';
|
|
const companyName = companySettings.company_name || 'Accounting System';
|
|
const companyAddress = (companySettings.company_address || '').replace(/\n/g, '<br>');
|
|
const companyVat = companySettings.vat_number ? `<p class="text-muted small mb-0">VAT: ${companySettings.vat_number}</p>` : '';
|
|
const companyPhone = companySettings.company_phone ? `<p class="text-muted small mb-0">Tel: ${companySettings.company_phone}</p>` : '';
|
|
|
|
// Quotation Header Construction
|
|
const quotDate = data.quotation_date;
|
|
const quotValid = data.valid_until || 'N/A';
|
|
const quotNo = 'QUO-' + data.id.toString().padStart(5, '0');
|
|
const customerName = data.customer_name || 'Walk-in Customer';
|
|
const statusBadge = `<span class="badge ${data.status === 'converted' ? 'bg-success' : 'bg-secondary'}">${data.status.toUpperCase()}</span>`;
|
|
|
|
content.innerHTML = `
|
|
<div class="p-5">
|
|
<div class="invoice-header mb-4">
|
|
<div class="row align-items-center">
|
|
<div class="col-6">
|
|
${logoImg}
|
|
<h3 class="mb-1 fw-bold">${companyName}</h3>
|
|
<p class="text-muted small mb-0">${companyAddress}</p>
|
|
${companyVat}
|
|
${companyPhone}
|
|
</div>
|
|
<div class="col-6 text-end">
|
|
<h1 class="invoice-title fw-bold mb-0 text-uppercase">Quotation / عرض سعر</h1>
|
|
<div class="mt-2">${statusBadge}</div>
|
|
<div class="mt-3">
|
|
<p class="mb-0 fs-5">No / رقم: <strong class="text-primary">${quotNo}</strong></p>
|
|
<p class="mb-0">Date / التاريخ: <span class="fw-bold">${quotDate}</span></p>
|
|
<p class="mb-0">Valid Until / صالح لغاية: <span class="fw-bold">${quotValid}</span></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4 g-3">
|
|
<div class="col-6">
|
|
<div class="invoice-info-card">
|
|
<p class="text-muted small text-uppercase fw-bold mb-2 border-bottom pb-1">To / إلى</p>
|
|
<h5 class="mb-1 fw-bold">${customerName}</h5>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<table class="table table-bordered table-formal">
|
|
<thead class="bg-dark text-white">
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Item Description / وصف الصنف</th>
|
|
<th class="text-center">Qty / الكمية</th>
|
|
<th class="text-end">Unit Price / سعر الوحدة</th>
|
|
<th class="text-center">VAT / الضريبة</th>
|
|
<th class="text-end">Total / الإجمالي</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>${itemsHtml}</tbody>
|
|
<tfoot>
|
|
<tr>
|
|
<th colspan="5" class="text-end">Subtotal / المجموع الفرعي</th>
|
|
<td class="text-end fw-bold">${parseFloat(data.total_amount).toFixed(3)}</td>
|
|
</tr>
|
|
<tr>
|
|
<th colspan="5" class="text-end">VAT Amount / مبلغ الضريبة</th>
|
|
<td class="text-end fw-bold">${parseFloat(data.vat_amount).toFixed(2)}</td>
|
|
</tr>
|
|
<tr class="table-primary">
|
|
<th colspan="5" class="text-end h5">Grand Total (OMR) / المجموع الكلي (رع)</th>
|
|
<td class="text-end h5 fw-bold">${parseFloat(data.total_with_vat).toFixed(3)}</td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
|
|
<div class="mt-5 pt-3 border-top">
|
|
<div class="row">
|
|
<div class="col-6">
|
|
<p class="small text-muted text-uppercase fw-bold">Terms & Conditions / الشروط والأحكام:</p>
|
|
<ul class="small text-muted">
|
|
<li>Quotation is valid until the date mentioned above. / عرض السعر صالح لغاية التاريخ المذكور أعلاه.</li>
|
|
<li>Prices are inclusive of VAT where applicable. / الأسعار تشمل ضريبة القيمة المضافة حيثما ينطبق ذلك.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-6 text-end pt-4">
|
|
<div class="border-top d-inline-block px-5">Authorized Signature / التوقيع المعتمد</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4 text-center">
|
|
<p class="text-muted x-small mb-0">Generated by / تم إنشاؤه بواسطة ${companyName}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const actionButtons = document.getElementById('quotationActionButtons');
|
|
actionButtons.innerHTML = '';
|
|
if (data.status === 'pending') {
|
|
const convertBtn = document.createElement('button');
|
|
convertBtn.className = 'btn btn-success me-2';
|
|
convertBtn.innerHTML = '<i class="bi bi-receipt"></i> <span data-en="Convert to Invoice" data-ar="تحويل إلى فاتورة">Convert to Invoice</span>';
|
|
convertBtn.onclick = function() {
|
|
if (confirm('Convert this quotation to an invoice?')) {
|
|
const f = document.createElement('form');
|
|
f.method = 'POST';
|
|
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${data.id}">`;
|
|
document.body.appendChild(f);
|
|
f.submit();
|
|
}
|
|
};
|
|
actionButtons.appendChild(convertBtn);
|
|
|
|
const editBtn = document.createElement('button');
|
|
editBtn.className = 'btn btn-primary';
|
|
editBtn.innerHTML = '<i class="bi bi-pencil-square"></i> <span data-en="Edit" data-ar="تعديل">Edit</span>';
|
|
editBtn.onclick = function() {
|
|
const editModal = new bootstrap.Modal(document.getElementById('editQuotationModal'));
|
|
modal.hide();
|
|
const originalEditBtn = document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id},']`) ||
|
|
document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id}']`);
|
|
if (originalEditBtn) originalEditBtn.click();
|
|
};
|
|
actionButtons.appendChild(editBtn);
|
|
}
|
|
|
|
modal.show();
|
|
if (autoPrint) {
|
|
setTimeout(() => { window.print(); }, 500);
|
|
}
|
|
};
|
|
|
|
document.querySelectorAll('.view-quotation-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const data = JSON.parse(this.dataset.json);
|
|
window.viewAndPrintQuotation(data, false);
|
|
});
|
|
});
|
|
|
|
|
|
// Status change logic for Paid Amount field
|
|
const togglePaidAmount = (statusId, containerId) => {
|
|
const statusEl = document.getElementById(statusId);
|
|
const containerEl = document.getElementById(containerId);
|
|
if (statusEl && containerEl) {
|
|
statusEl.addEventListener('change', function() {
|
|
if (this.value === 'partially_paid') {
|
|
containerEl.style.display = 'block';
|
|
} else {
|
|
containerEl.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
};
|
|
togglePaidAmount('add_status', 'addPaidAmountContainer');
|
|
togglePaidAmount('edit_status', 'editPaidAmountContainer');
|
|
|
|
// Pay Invoice Logic
|
|
document.querySelectorAll('.pay-invoice-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const id = this.getAttribute('data-id');
|
|
const total = parseFloat(this.getAttribute('data-total'));
|
|
const paid = parseFloat(this.getAttribute('data-paid') || 0);
|
|
const remaining = total - paid;
|
|
|
|
document.getElementById('pay_invoice_id').value = id;
|
|
document.getElementById('pay_invoice_total').value = total.toFixed(3);
|
|
document.getElementById('pay_remaining_amount').value = remaining.toFixed(3);
|
|
document.getElementById('pay_amount').value = remaining.toFixed(3);
|
|
document.getElementById('pay_amount').max = remaining.toFixed(3);
|
|
});
|
|
});
|
|
|
|
// Show receipt modal if needed
|
|
|
|
function showReceipt(paymentId) {
|
|
fetch(`index.php?action=get_payment_details&payment_id=${paymentId}`)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (!data) return;
|
|
document.getElementById('receiptNo').textContent = 'RCP-' + data.id.toString().padStart(5, '0');
|
|
document.getElementById('receiptDate').textContent = data.payment_date;
|
|
document.getElementById('receiptCustomer').textContent = data.customer_name || '---';
|
|
document.getElementById('receiptInvNo').textContent = (data.inv_type === 'purchase' ? 'PUR-' : 'INV-') + data.inv_id.toString().padStart(5, '0');
|
|
document.getElementById('receiptMethod').textContent = data.payment_method;
|
|
document.getElementById('receiptAmount').textContent = parseFloat(data.amount).toFixed(3);
|
|
document.getElementById('receiptAmountWords').textContent = data.amount_words;
|
|
|
|
const outletEl = document.getElementById('receiptOutletName');
|
|
if (outletEl) {
|
|
outletEl.textContent = data.outlet_name ? (data.outlet_name) : '';
|
|
outletEl.style.display = data.outlet_name ? 'block' : 'none';
|
|
}
|
|
|
|
// Update labels for Purchase vs Sale
|
|
const partyLabel = document.getElementById('receiptPartyLabel');
|
|
const againstLabel = document.getElementById('receiptAgainstLabel');
|
|
const receiptTitle = document.querySelector('#receiptModal .modal-title');
|
|
const receiptH4 = document.querySelector('.receipt-container h4.letter-spacing-2');
|
|
|
|
if (data.inv_type === 'purchase') {
|
|
partyLabel.textContent = 'Paid To';
|
|
partyLabel.setAttribute('data-en', 'Paid To');
|
|
partyLabel.setAttribute('data-ar', 'صرف إلى');
|
|
|
|
againstLabel.textContent = 'Against Purchase';
|
|
againstLabel.setAttribute('data-en', 'Against Purchase');
|
|
againstLabel.setAttribute('data-ar', 'مقابل شراء');
|
|
|
|
receiptTitle.textContent = 'Payment Voucher';
|
|
receiptTitle.setAttribute('data-en', 'Payment Voucher');
|
|
receiptTitle.setAttribute('data-ar', 'سند صرف');
|
|
|
|
receiptH4.textContent = 'PAYMENT VOUCHER';
|
|
receiptH4.setAttribute('data-en', 'PAYMENT VOUCHER');
|
|
receiptH4.setAttribute('data-ar', 'سند صرف');
|
|
} else {
|
|
partyLabel.textContent = 'Received From';
|
|
partyLabel.setAttribute('data-en', 'Received From');
|
|
partyLabel.setAttribute('data-ar', 'وصلنا من');
|
|
|
|
againstLabel.textContent = 'Against Invoice';
|
|
againstLabel.setAttribute('data-en', 'Against Invoice');
|
|
againstLabel.setAttribute('data-ar', 'مقابل فاتورة');
|
|
|
|
receiptTitle.textContent = 'Payment Receipt';
|
|
receiptTitle.setAttribute('data-en', 'Payment Receipt');
|
|
receiptTitle.setAttribute('data-ar', 'سند قبض');
|
|
|
|
receiptH4.textContent = 'PAYMENT RECEIPT';
|
|
receiptH4.setAttribute('data-en', 'PAYMENT RECEIPT');
|
|
receiptH4.setAttribute('data-ar', 'سند قبض');
|
|
}
|
|
|
|
const notesContainer = document.getElementById('receiptNotesContainer');
|
|
if (data.notes) {
|
|
document.getElementById('receiptNotes').textContent = data.notes;
|
|
notesContainer.style.display = 'block';
|
|
} else {
|
|
notesContainer.style.display = 'none';
|
|
}
|
|
|
|
const receiptModal = new bootstrap.Modal(document.getElementById('receiptModal'));
|
|
receiptModal.show();
|
|
});
|
|
};
|
|
|
|
document.querySelectorAll('.view-payments-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const invoiceId = this.getAttribute('data-id');
|
|
const tbody = document.getElementById('paymentsTableBody');
|
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center" data-en="Loading..." data-ar="جاري التحميل...">Loading...</td></tr>';
|
|
|
|
fetch(`index.php?action=get_payments&invoice_id=${invoiceId}`)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
tbody.innerHTML = '';
|
|
if (data.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center" data-en="No payments found." data-ar="لا توجد مدفوعات.">No payments found.</td></tr>';
|
|
return;
|
|
}
|
|
data.forEach(p => {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>RCP-${p.id.toString().padStart(5, '0')}</td>
|
|
<td>${p.payment_date}</td>
|
|
<td>${p.payment_method}</td>
|
|
<td class="text-end fw-bold">OMR ${parseFloat(p.amount).toFixed(3)}</td>
|
|
<td class="text-end">
|
|
<button class="btn btn-sm btn-outline-primary" onclick="showReceipt(${p.id})">
|
|
<i class="bi bi-printer"></i>
|
|
</button>
|
|
</td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
window.printReceipt = function() {
|
|
const content = document.getElementById('printableReceipt').innerHTML;
|
|
const originalContent = document.body.innerHTML;
|
|
document.body.innerHTML = content;
|
|
window.print();
|
|
document.body.innerHTML = originalContent;
|
|
window.location.reload();
|
|
};
|
|
// Invoice Form Logic
|
|
const initInvoiceForm = (searchInputId, suggestionsId, tableBodyId, grandTotalId, subtotalId, totalVatId) => {
|
|
const searchInput = document.getElementById(searchInputId);
|
|
const suggestions = document.getElementById(suggestionsId);
|
|
const tableBody = document.getElementById(tableBodyId);
|
|
const grandTotalEl = document.getElementById(grandTotalId);
|
|
const subtotalEl = document.getElementById(subtotalId);
|
|
const totalVatEl = document.getElementById(totalVatId);
|
|
|
|
if (!searchInput || !tableBody) return;
|
|
|
|
let timeout = null;
|
|
searchInput.addEventListener('input', function() {
|
|
clearTimeout(timeout);
|
|
const q = this.value.trim();
|
|
if (q.length < 2) {
|
|
suggestions.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
timeout = setTimeout(() => {
|
|
fetch(`index.php?action=search_items&q=${encodeURIComponent(q)}`)
|
|
.then(res => res.ok ? res.json() : [])
|
|
.then(data => {
|
|
suggestions.innerHTML = '';
|
|
if (data.length > 0) {
|
|
data.forEach(item => {
|
|
const btn = document.createElement('button');
|
|
btn.type = 'button';
|
|
btn.className = 'list-group-item list-group-item-action';
|
|
btn.innerHTML = `
|
|
<div class="d-flex justify-content-between">
|
|
<span><strong>${item.sku}</strong> - ${item.name_en} / ${item.name_ar}</span>
|
|
<span class="text-muted small">Stock: ${item.stock_quantity}</span>
|
|
</div>
|
|
`;
|
|
btn.onclick = () => addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl);
|
|
suggestions.appendChild(btn);
|
|
});
|
|
suggestions.style.display = 'block';
|
|
} else {
|
|
suggestions.style.display = 'none';
|
|
}
|
|
});
|
|
}, 300);
|
|
});
|
|
|
|
// Close suggestions when clicking outside
|
|
document.addEventListener('click', function(e) {
|
|
if (!searchInput.contains(e.target) && !suggestions.contains(e.target)) {
|
|
suggestions.style.display = 'none';
|
|
}
|
|
});
|
|
};
|
|
|
|
function addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl, customData = null) {
|
|
if (suggestions) suggestions.style.display = 'none';
|
|
if (searchInput) searchInput.value = '';
|
|
|
|
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
|
|
const currentStock = parseFloat(item.stock_quantity) || 0;
|
|
|
|
if (invoiceType === 'sale' && !allowZeroStock && !customData) {
|
|
const existingInTable = Array.from(tableBody.querySelectorAll('.item-row')).find(row => row.querySelector('.item-id-input').value == item.id);
|
|
let currentQtyInTable = 0;
|
|
if (existingInTable) {
|
|
currentQtyInTable = parseFloat(existingInTable.querySelector('.item-qty').value) || 0;
|
|
}
|
|
|
|
if (currentQtyInTable + 1 > currentStock) {
|
|
alert('Insufficient stock! Available: ' + currentStock);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const existingRow = Array.from(tableBody.querySelectorAll('.item-id-input')).find(input => input.value == item.id);
|
|
if (existingRow && !customData) {
|
|
const row = existingRow.closest('tr');
|
|
const qtyInput = row.querySelector('.item-qty');
|
|
qtyInput.value = parseFloat(qtyInput.value) + 1;
|
|
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
|
|
return;
|
|
}
|
|
|
|
const row = document.createElement('tr');
|
|
row.className = 'item-row';
|
|
const price = customData ? customData.unit_price : (invoiceType === 'sale' ? item.sale_price : item.purchase_price);
|
|
const qty = customData ? customData.quantity : 1;
|
|
const vatRate = item.vat_rate || 0;
|
|
|
|
row.innerHTML = `
|
|
<td>
|
|
<input type="hidden" name="item_ids[]" class="item-id-input" value="${item.id}">
|
|
<input type="hidden" class="item-row-stock" value="${item.stock_quantity}">
|
|
<input type="hidden" class="item-vat-rate" value="${vatRate}">
|
|
<div><strong>${item.name_en}</strong></div>
|
|
<div class="small text-muted">${item.name_ar} (${item.sku})</div>
|
|
</td>
|
|
<td><input type="number" step="0.001" name="quantities[]" class="form-control item-qty" value="${qty}" required></td>
|
|
<td><input type="number" step="0.001" name="prices[]" class="form-control item-price" value="${price}" required></td>
|
|
<td><input type="text" class="form-control bg-light" value="${parseFloat(vatRate || 0).toFixed(2)}%" readonly></td>
|
|
<td><input type="number" step="0.001" class="form-control item-total" value="${(qty * price).toFixed(3)}" readonly></td>
|
|
<td><button type="button" class="btn btn-outline-danger btn-sm remove-row"><i class="bi bi-trash"></i></button></td>
|
|
`;
|
|
|
|
tableBody.appendChild(row);
|
|
attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl);
|
|
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
|
|
}
|
|
|
|
function recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl) {
|
|
let subtotal = 0;
|
|
let totalVat = 0;
|
|
tableBody.querySelectorAll('.item-row').forEach(row => {
|
|
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
|
|
const price = parseFloat(row.querySelector('.item-price').value) || 0;
|
|
const vatRate = parseFloat(row.querySelector('.item-vat-rate').value) || 0;
|
|
|
|
const total = qty * price;
|
|
const vatAmount = total * (vatRate / 100);
|
|
|
|
row.querySelector('.item-total').value = total.toFixed(3);
|
|
subtotal += total;
|
|
totalVat += vatAmount;
|
|
});
|
|
const grandTotal = subtotal + totalVat;
|
|
|
|
if (subtotalEl) subtotalEl.textContent = 'OMR ' + subtotal.toFixed(3);
|
|
if (totalVatEl) totalVatEl.textContent = 'OMR ' + totalVat.toFixed(2);
|
|
if (grandTotalEl) grandTotalEl.textContent = 'OMR ' + grandTotal.toFixed(3);
|
|
}
|
|
|
|
function attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl) {
|
|
row.querySelector('.item-qty').addEventListener('input', function() {
|
|
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
|
|
if (invoiceType === 'sale' && !allowZeroStock) {
|
|
const stock = parseFloat(row.querySelector('.item-row-stock').value) || 0;
|
|
const qty = parseFloat(this.value) || 0;
|
|
if (qty > stock) {
|
|
alert('Insufficient stock! Available: ' + stock);
|
|
this.value = stock;
|
|
}
|
|
}
|
|
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
|
|
});
|
|
row.querySelector('.item-price').addEventListener('input', () => recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl));
|
|
row.querySelector('.remove-row').addEventListener('click', function() {
|
|
row.remove();
|
|
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
|
|
});
|
|
}
|
|
|
|
const invoiceType = 'sale';
|
|
initInvoiceForm('productSearchInput', 'searchSuggestions', 'invoiceItemsTableBody', 'grandTotal', 'subtotal', 'totalVat');
|
|
initInvoiceForm('editProductSearchInput', 'editSearchSuggestions', 'editInvoiceItemsTableBody', 'edit_grandTotal', 'edit_subtotal', 'edit_totalVat');
|
|
window.viewAndPrintA4Invoice = function(data, autoPrint = true) {
|
|
if (!data) return;
|
|
// Reuse view logic
|
|
const invoiceDisplayNo = data.document_no || data.transaction_no || ((data.type === 'purchase' ? 'PUR-' : 'INV-') + data.id.toString().padStart(5, '0'));
|
|
document.getElementById('invNumber').textContent = invoiceDisplayNo;
|
|
document.getElementById('invDate').textContent = data.invoice_date;
|
|
document.getElementById('invPaymentType').textContent = data.payment_type ? data.payment_type.toUpperCase() : 'CASH';
|
|
document.getElementById('invCustomerName').textContent = data.customer_name || '---';
|
|
|
|
const phoneEl = document.getElementById('invCustomerPhone');
|
|
const phoneContainer = document.getElementById('invCustomerPhoneContainer');
|
|
if (data.customer_phone) {
|
|
phoneEl.textContent = data.customer_phone;
|
|
phoneContainer.style.display = 'block';
|
|
} else {
|
|
phoneContainer.style.display = 'none';
|
|
}
|
|
|
|
const taxIdEl = document.getElementById('invCustomerTaxId');
|
|
const taxIdContainer = document.getElementById('invCustomerTaxIdContainer');
|
|
if (data.customer_tax_id) {
|
|
taxIdEl.textContent = data.customer_tax_id;
|
|
taxIdContainer.style.display = 'block';
|
|
} else {
|
|
taxIdContainer.style.display = 'none';
|
|
}
|
|
|
|
document.getElementById('invAmountInWords').textContent = data.total_in_words || '';
|
|
|
|
const invOutletEl = document.getElementById('invOutletName');
|
|
if (invOutletEl) {
|
|
invOutletEl.textContent = data.outlet_name ? (data.outlet_name) : '';
|
|
invOutletEl.style.display = data.outlet_name ? 'block' : 'none';
|
|
}
|
|
|
|
document.getElementById('invPartyLabel').textContent = data.type === 'sale' ? 'Bill To / فاتورة إلى' : 'Bill From / فاتورة من';
|
|
document.getElementById('invPartyLabel').setAttribute('data-en', data.type === 'sale' ? 'Bill To' : 'Bill From');
|
|
document.getElementById('invPartyLabel').setAttribute('data-ar', data.type === 'sale' ? 'فاتورة إلى' : 'فاتورة من');
|
|
document.getElementById('invoiceTypeLabel').textContent = data.type;
|
|
document.getElementById('invoiceTypeLabel').className = 'badge text-uppercase ' + (data.type === 'sale' ? 'bg-success' : 'bg-warning');
|
|
|
|
const statusLabel = document.getElementById('invoiceStatusLabel');
|
|
let statusClass = 'bg-secondary';
|
|
let statusEn = data.status ? (data.status.charAt(0).toUpperCase() + data.status.slice(1)) : '---';
|
|
if (data.status === 'paid') statusClass = 'bg-success';
|
|
else if (data.status === 'unpaid') statusClass = 'bg-danger';
|
|
else if (data.status === 'partially_paid') {
|
|
statusClass = 'bg-warning text-dark';
|
|
statusEn = 'Partially Paid';
|
|
}
|
|
|
|
statusLabel.textContent = statusEn;
|
|
statusLabel.className = 'badge text-uppercase ' + statusClass;
|
|
|
|
const body = document.getElementById('invItemsBody');
|
|
body.innerHTML = '';
|
|
if (data.items) {
|
|
data.items.forEach(item => {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>${item.name_en} / ${item.name_ar}</td>
|
|
<td class="text-center">${item.quantity}</td>
|
|
<td class="text-end"><small>ر.ع / OMR</small> ${parseFloat(item.unit_price).toFixed(3)}</td>
|
|
<td class="text-end">${parseFloat(item.vat_rate || 0).toFixed(2)}%</td>
|
|
<td class="text-end"><small>ر.ع / OMR</small> ${parseFloat(item.total_price).toFixed(3)}</td>
|
|
`;
|
|
body.appendChild(tr);
|
|
});
|
|
}
|
|
const vatVal = parseFloat(data.vat_amount || 0);
|
|
const totalVal = parseFloat(data.total_amount || 0);
|
|
const grandTotalValue = (parseFloat(data.total_with_vat) || (totalVal + vatVal));
|
|
|
|
if (document.getElementById('invSubtotal')) document.getElementById('invSubtotal').innerHTML = '<small>ر.ع / OMR</small> ' + (grandTotalValue - vatVal).toFixed(3);
|
|
if (document.getElementById('invVatAmount')) document.getElementById('invVatAmount').innerHTML = '<small>ر.ع / OMR</small> ' + vatVal.toFixed(2);
|
|
if (document.getElementById('invGrandTotal')) document.getElementById('invGrandTotal').innerHTML = '<small>ر.ع / OMR</small> ' + grandTotalValue.toFixed(3);
|
|
|
|
if (document.getElementById('invPaidInfo')) document.getElementById('invPaidInfo').innerHTML = '<small>ر.ع / OMR</small> ' + parseFloat(data.paid_amount || 0).toFixed(3);
|
|
const balance = grandTotalValue - parseFloat(data.paid_amount || 0);
|
|
if (document.getElementById('invBalanceInfo')) document.getElementById('invBalanceInfo').innerHTML = '<small>ر.ع / OMR</small> ' + balance.toFixed(3);
|
|
|
|
// Generate QR Code for Zakat, Tax and Customs Authority (ZATCA) style or simple formal
|
|
const companyName = "Bahjet Al-Safa Trading";
|
|
const vatNo = "OM25418";
|
|
const qrData = `Seller: ${companyName}\nVAT: ${vatNo}\nInvoice: ${invoiceDisplayNo}\nDate: ${data.invoice_date}\nTotal: ${grandTotalValue.toFixed(3)}`;
|
|
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=${encodeURIComponent(qrData)}`;
|
|
if (document.getElementById('invQrCode')) {
|
|
document.getElementById('invQrCode').innerHTML = `<img src="${qrUrl}" alt="QR Code" style="width: 100px; height: 100px;" class="border p-1 bg-white">`;
|
|
}
|
|
|
|
const viewModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('viewInvoiceModal'));
|
|
viewModal.show();
|
|
|
|
if (autoPrint) {
|
|
setTimeout(() => { window.print(); }, 1000);
|
|
}
|
|
|
|
fetch(`index.php?action=get_payments&invoice_id=${data.id}`)
|
|
.then(res => res.json())
|
|
.then(payments => {
|
|
const paymentsBody = document.getElementById('invPaymentsBody');
|
|
const paymentsSection = document.getElementById('invPaymentsSection');
|
|
if (paymentsBody) paymentsBody.innerHTML = '';
|
|
if (payments && payments.length > 0) {
|
|
if (paymentsBody) {
|
|
payments.forEach(p => {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `<td>${p.payment_date}</td><td>${p.payment_method}</td><td class="text-end fw-bold">OMR ${parseFloat(p.amount).toFixed(3)}</td>`;
|
|
paymentsBody.appendChild(tr);
|
|
});
|
|
}
|
|
if (paymentsSection) paymentsSection.style.display = 'block';
|
|
} else {
|
|
if (paymentsSection) paymentsSection.style.display = 'none';
|
|
}
|
|
}).catch(err => console.error('Error fetching payments:', err));
|
|
};
|
|
|
|
// Edit Invoice Logic
|
|
const parseInvoiceButtonPayload = (btn) => {
|
|
if (!btn || !btn.dataset || !btn.dataset.json) return {};
|
|
try {
|
|
return JSON.parse(btn.dataset.json);
|
|
} catch (error) {
|
|
console.warn('Failed to parse invoice payload from button data.', error);
|
|
return {};
|
|
}
|
|
};
|
|
|
|
const syncSelect2Value = (select) => {
|
|
if (!select) return;
|
|
if (select.classList.contains('select2') && window.jQuery && jQuery.fn && jQuery.fn.select2) {
|
|
jQuery(select).trigger('change');
|
|
}
|
|
};
|
|
|
|
const ensureSelectOption = (selectId, value, label = '') => {
|
|
const select = document.getElementById(selectId);
|
|
if (!select || value === null || value === undefined || String(value) === '') return;
|
|
|
|
const alreadyExists = Array.from(select.options || []).some(option => option.value == String(value));
|
|
if (!alreadyExists) {
|
|
const option = document.createElement('option');
|
|
option.value = String(value);
|
|
option.textContent = label || String(value);
|
|
select.appendChild(option);
|
|
}
|
|
};
|
|
|
|
const setBlankSelectOptionLabel = (select, label = '---') => {
|
|
if (!select) return;
|
|
const emptyOption = Array.from(select.options || []).find(option => option.value === '');
|
|
if (emptyOption) {
|
|
emptyOption.textContent = label;
|
|
}
|
|
};
|
|
|
|
const normalizeEditPaymentType = (paymentType) => {
|
|
let normalized = String(paymentType || 'cash').toLowerCase().replace(/[\s-]+/g, '_');
|
|
if (normalized === 'pos') normalized = 'cash';
|
|
if (!['cash', 'card', 'bank_transfer', 'credit'].includes(normalized)) {
|
|
normalized = 'cash';
|
|
}
|
|
return normalized;
|
|
};
|
|
|
|
const renderEditInvoiceItems = (items) => {
|
|
const tableBody = document.getElementById('editInvoiceItemsTableBody');
|
|
const grandTotalEl = document.getElementById('edit_grandTotal');
|
|
const subtotalEl = document.getElementById('edit_subtotal');
|
|
const totalVatEl = document.getElementById('edit_totalVat');
|
|
|
|
if (!tableBody) return;
|
|
tableBody.innerHTML = '';
|
|
|
|
if (!Array.isArray(items) || items.length === 0) {
|
|
if (typeof recalculate === 'function') {
|
|
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
items.forEach(item => {
|
|
addItemToTable({
|
|
id: item.item_id || item.id,
|
|
name_en: item.name_en || item.item_name_en || 'Item',
|
|
name_ar: item.name_ar || item.item_name_ar || '',
|
|
sku: item.sku || '',
|
|
vat_rate: item.vat_rate || 0,
|
|
stock_quantity: item.stock_quantity || 0
|
|
}, tableBody, null, null, grandTotalEl, subtotalEl, totalVatEl, {
|
|
quantity: item.quantity,
|
|
unit_price: item.unit_price
|
|
});
|
|
});
|
|
};
|
|
|
|
const populateEditInvoiceModal = (data) => {
|
|
if (!data) return;
|
|
|
|
const invoiceIdInput = document.getElementById('edit_invoice_id');
|
|
const partySelect = document.getElementById('edit_customer_id');
|
|
const invoiceDateInput = document.getElementById('edit_invoice_date');
|
|
const dueDateInput = document.getElementById('edit_due_date');
|
|
const paymentTypeSelect = document.getElementById('edit_payment_type');
|
|
const statusSelect = document.getElementById('edit_status');
|
|
const paidAmountInput = document.getElementById('edit_paid_amount');
|
|
const paidAmountContainer = document.getElementById('editPaidAmountContainer');
|
|
|
|
const partyId = data.customer_id ?? data.supplier_id ?? '';
|
|
const partyLabel = data.party_name || data.customer_name || data.supplier_name || '';
|
|
|
|
ensureSelectOption('edit_customer_id', partyId, partyLabel);
|
|
|
|
if (invoiceIdInput) invoiceIdInput.value = data.id || '';
|
|
if (partySelect) {
|
|
setBlankSelectOptionLabel(partySelect, (partyId === '' && partyLabel) ? partyLabel : '---');
|
|
partySelect.value = partyId;
|
|
syncSelect2Value(partySelect);
|
|
}
|
|
if (invoiceDateInput) invoiceDateInput.value = data.invoice_date || '';
|
|
if (dueDateInput) dueDateInput.value = data.due_date || '';
|
|
if (paymentTypeSelect) paymentTypeSelect.value = normalizeEditPaymentType(data.payment_type);
|
|
if (statusSelect) statusSelect.value = data.status || 'unpaid';
|
|
if (paidAmountInput) paidAmountInput.value = parseFloat(data.paid_amount || 0).toFixed(3);
|
|
if (paidAmountContainer) {
|
|
paidAmountContainer.style.display = data.status === 'partially_paid' ? 'block' : 'none';
|
|
}
|
|
|
|
renderEditInvoiceItems(data.items || []);
|
|
};
|
|
|
|
document.querySelectorAll('.edit-invoice-btn').forEach(btn => {
|
|
btn.addEventListener('click', async function() {
|
|
const fallbackData = parseInvoiceButtonPayload(this);
|
|
const invoiceId = this.dataset.id || fallbackData.id || '';
|
|
const type = this.dataset.type || fallbackData.type || invoiceType || 'sale';
|
|
const tableBody = document.getElementById('editInvoiceItemsTableBody');
|
|
|
|
if (Object.keys(fallbackData).length > 0) {
|
|
populateEditInvoiceModal(fallbackData);
|
|
} else if (tableBody) {
|
|
tableBody.innerHTML = '<tr><td colspan="6" class="text-center"><div class="spinner-border spinner-border-sm text-primary"></div> <span data-en="Loading invoice..." data-ar="جاري تحميل الفاتورة...">Loading invoice...</span></td></tr>';
|
|
}
|
|
|
|
if (!invoiceId) return;
|
|
|
|
try {
|
|
const resp = await fetch(`index.php?action=get_invoice_details&invoice_id=${encodeURIComponent(invoiceId)}&type=${encodeURIComponent(type)}`);
|
|
const data = await resp.json();
|
|
|
|
if (data && data.id) {
|
|
populateEditInvoiceModal(data);
|
|
} else if (data && data.error) {
|
|
throw new Error(data.error);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load invoice details for edit modal.', error);
|
|
if (window.Swal) {
|
|
Swal.fire('Error', 'Failed to load invoice details', 'error');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// View and Print Invoice Logic
|
|
|
|
} catch (e) { console.error("JS Error in DOMContentLoaded:", e); }
|
|
});
|