324 lines
15 KiB
PHP
324 lines
15 KiB
PHP
// 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 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,
|
|
purchase_price: item.purchase_price || 0,
|
|
last_sale_price: item.last_sale_price || 0,
|
|
stock_quantity: item.stock_quantity || 0
|
|
}, tableBody, null, null, grandTotalEl, subtotalEl, totalVatEl, {
|
|
quantity: item.quantity,
|
|
unit_price: item.unit_price,
|
|
purchase_price: item.purchase_price || 0,
|
|
last_sale_price: item.last_sale_price || 0
|
|
});
|
|
});
|
|
};
|
|
|
|
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 discountAmountInput = document.getElementById('edit_discount_amount');
|
|
|
|
const partyId = data.customer_id ?? data.supplier_id ?? '';
|
|
const partyLabel = data.party_name || data.customer_name || data.supplier_name || '';
|
|
|
|
invoiceEnsureSelectOption('edit_customer_id', partyId, partyLabel);
|
|
|
|
if (invoiceIdInput) invoiceIdInput.value = data.id || '';
|
|
if (partySelect) {
|
|
invoiceSetBlankSelectOptionLabel(partySelect, (partyId === '' && partyLabel) ? partyLabel : '---');
|
|
partySelect.value = partyId;
|
|
invoiceSyncSelect2Value(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 (discountAmountInput) discountAmountInput.value = parseFloat(data.discount_amount || 0).toFixed(3);
|
|
if (paidAmountContainer) {
|
|
paidAmountContainer.style.display = data.status === 'partially_paid' ? 'block' : 'none';
|
|
}
|
|
|
|
renderEditInvoiceItems(data.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 && typeof recalculate === 'function') {
|
|
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
|
|
}
|
|
};
|
|
|
|
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
|
|
const invoiceActionErrorTitle = <?= json_encode(($lang ?? 'en') === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
const invoiceActionLoadError = <?= json_encode(($lang ?? 'en') === 'ar' ? 'تعذر تحميل بيانات الفاتورة' : 'Failed to load invoice details', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
const invoicePrintChooserTitle = <?= json_encode(($lang ?? 'en') === 'ar' ? 'اختر طريقة الطباعة' : 'Choose print format', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
const invoicePrintChooserText = <?= json_encode(($lang ?? 'en') === 'ar' ? 'هل تريد طباعة الفاتورة كإيصال أو كفاتورة عادية؟' : 'Do you want to print this sale as a receipt or a normal invoice?', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
const invoicePrintNormalLabel = <?= json_encode(($lang ?? 'en') === 'ar' ? 'فاتورة عادية' : 'Normal Invoice', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
const invoicePrintReceiptLabel = <?= json_encode(($lang ?? 'en') === 'ar' ? 'إيصال' : 'Receipt', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
const invoicePrintCancelLabel = <?= json_encode(($lang ?? 'en') === 'ar' ? 'إلغاء' : 'Cancel', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
const invoicePrintFallbackPrompt = <?= json_encode(($lang ?? 'en') === 'ar' ? 'اضغط موافق لطباعة الفاتورة العادية، أو إلغاء لطباعة الإيصال.' : 'Press OK for a normal invoice, or Cancel for a receipt.', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
|
|
const buildInvoiceDocumentNo = (data) => {
|
|
const prefix = (data && data.type === 'purchase') ? 'PUR-' : 'INV-';
|
|
const numericId = parseInt(data && data.id ? data.id : 0, 10);
|
|
return prefix + String(Number.isFinite(numericId) && numericId > 0 ? numericId : 0).padStart(5, '0');
|
|
};
|
|
|
|
const normalizeInvoiceActionData = (payload, fallback = {}) => {
|
|
const merged = {
|
|
...(fallback || {}),
|
|
...(payload || {})
|
|
};
|
|
|
|
merged.type = String(merged.type || invoiceType || 'sale').toLowerCase() === 'purchase' ? 'purchase' : 'sale';
|
|
merged.party_name = merged.party_name || merged.customer_name || merged.supplier_name || (merged.type === 'sale' && merged.is_pos ? 'Walk-in Customer' : '---');
|
|
merged.customer_name = merged.customer_name || merged.supplier_name || merged.party_name || '---';
|
|
merged.customer_phone = merged.customer_phone || merged.party_phone || '';
|
|
merged.customer_tax_id = merged.customer_tax_id || merged.party_tax_id || '';
|
|
merged.outlet_name = merged.outlet_name || '';
|
|
merged.items = Array.isArray(merged.items) ? merged.items : (Array.isArray(fallback.items) ? fallback.items : []);
|
|
merged.document_no = merged.document_no || merged.transaction_no || (merged.id ? buildInvoiceDocumentNo(merged) : '');
|
|
merged.total_in_words = merged.total_in_words || fallback.total_in_words || '';
|
|
|
|
return merged;
|
|
};
|
|
|
|
const showInvoiceActionError = (message = invoiceActionLoadError) => {
|
|
if (window.Swal) {
|
|
Swal.fire(invoiceActionErrorTitle, message, 'error');
|
|
return;
|
|
}
|
|
window.alert(message);
|
|
};
|
|
|
|
const loadInvoiceActionData = async (btn) => {
|
|
const fallbackData = normalizeInvoiceActionData(parseInvoiceButtonPayload(btn));
|
|
const invoiceId = btn?.dataset?.id || fallbackData.id || '';
|
|
const type = btn?.dataset?.type || fallbackData.type || invoiceType || 'sale';
|
|
const baseData = normalizeInvoiceActionData({ ...fallbackData, id: invoiceId || fallbackData.id, type }, fallbackData);
|
|
|
|
if (!invoiceId) {
|
|
return baseData;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`index.php?action=get_invoice_details&invoice_id=${encodeURIComponent(invoiceId)}&type=${encodeURIComponent(type)}`);
|
|
const data = await response.json();
|
|
|
|
if (data && data.id) {
|
|
return normalizeInvoiceActionData(data, baseData);
|
|
}
|
|
|
|
if (data && data.error) {
|
|
throw new Error(data.error);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load invoice details for view/print.', error);
|
|
}
|
|
|
|
return baseData;
|
|
};
|
|
|
|
const printInvoiceAsReceipt = (data) => {
|
|
if (typeof window.printPosReceiptFromInvoice !== 'function') {
|
|
if (typeof window.viewAndPrintA4Invoice === 'function') {
|
|
window.viewAndPrintA4Invoice(data, true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
window.printPosReceiptFromInvoice(data);
|
|
|
|
setTimeout(() => {
|
|
const receiptContent = document.getElementById('posReceiptContent');
|
|
if (!receiptContent) {
|
|
return;
|
|
}
|
|
|
|
if (typeof window.printReceiptHtml === 'function') {
|
|
window.printReceiptHtml(receiptContent.innerHTML, {
|
|
title: data.document_no || data.transaction_no || 'Sale Receipt'
|
|
});
|
|
} else {
|
|
const printArea = document.getElementById('posPrintArea');
|
|
if (printArea) {
|
|
if (typeof window.prepareReceiptPrintArea === 'function') {
|
|
window.prepareReceiptPrintArea(receiptContent.innerHTML, printArea);
|
|
} else {
|
|
printArea.innerHTML = receiptContent.innerHTML;
|
|
const receipt = printArea.querySelector('.thermal-receipt');
|
|
if (receipt) {
|
|
receipt.classList.add('thermal-receipt-print');
|
|
}
|
|
}
|
|
|
|
document.body.classList.add('printing-receipt');
|
|
window.print();
|
|
document.body.classList.remove('printing-receipt');
|
|
printArea.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
const receiptModalEl = document.getElementById('posReceiptModal');
|
|
const receiptModal = receiptModalEl ? bootstrap.Modal.getInstance(receiptModalEl) : null;
|
|
if (receiptModal) {
|
|
receiptModal.hide();
|
|
}
|
|
}, 250);
|
|
};
|
|
|
|
const promptInvoicePrintMode = async (data) => {
|
|
const isSaleInvoice = String(data?.type || invoiceType || 'sale').toLowerCase() === 'sale';
|
|
|
|
if (!isSaleInvoice || typeof window.printPosReceiptFromInvoice !== 'function') {
|
|
if (typeof window.viewAndPrintA4Invoice === 'function') {
|
|
window.viewAndPrintA4Invoice(data, true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (window.Swal) {
|
|
const result = await Swal.fire({
|
|
title: invoicePrintChooserTitle,
|
|
text: invoicePrintChooserText,
|
|
icon: 'question',
|
|
showCancelButton: true,
|
|
showDenyButton: true,
|
|
confirmButtonText: invoicePrintNormalLabel,
|
|
denyButtonText: invoicePrintReceiptLabel,
|
|
cancelButtonText: invoicePrintCancelLabel,
|
|
reverseButtons: true
|
|
});
|
|
|
|
if (result.isConfirmed) {
|
|
if (typeof window.viewAndPrintA4Invoice === 'function') {
|
|
setTimeout(() => window.viewAndPrintA4Invoice(data, true), 120);
|
|
}
|
|
} else if (result.isDenied) {
|
|
setTimeout(() => printInvoiceAsReceipt(data), 120);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (window.confirm(invoicePrintFallbackPrompt)) {
|
|
if (typeof window.viewAndPrintA4Invoice === 'function') {
|
|
window.viewAndPrintA4Invoice(data, true);
|
|
}
|
|
} else {
|
|
printInvoiceAsReceipt(data);
|
|
}
|
|
};
|
|
|
|
document.querySelectorAll('.view-invoice-btn').forEach(btn => {
|
|
btn.addEventListener('click', async function(event) {
|
|
event.preventDefault();
|
|
const data = await loadInvoiceActionData(this);
|
|
|
|
if (!data || !data.id) {
|
|
showInvoiceActionError();
|
|
return;
|
|
}
|
|
|
|
if (typeof window.viewAndPrintA4Invoice === 'function') {
|
|
window.viewAndPrintA4Invoice(data, false);
|
|
}
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.print-a4-btn').forEach(btn => {
|
|
btn.addEventListener('click', async function(event) {
|
|
event.preventDefault();
|
|
const data = await loadInvoiceActionData(this);
|
|
|
|
if (!data || !data.id) {
|
|
showInvoiceActionError();
|
|
return;
|
|
}
|
|
|
|
await promptInvoicePrintMode(data);
|
|
});
|
|
});
|