38471-vm/pages/sales_purchases_print_script.php
2026-05-08 06:09:21 +00:00

366 lines
18 KiB
PHP

const escapeHtml = (value) => {
const stringValue = value == null ? '' : String(value);
return stringValue.replace(/[&<>"']/g, (character) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
}[character] || character));
};
const humanizeInvoiceText = (value) => {
const raw = String(value ?? '').trim();
if (!raw) {
return '---';
}
return raw
.replace(/[_-]+/g, ' ')
.replace(/\s+/g, ' ')
.toLowerCase()
.replace(/\b\w/g, (letter) => letter.toUpperCase());
};
const formatInvoiceCurrency = (amount, decimals = 3) => {
const numericAmount = Number.isFinite(parseFloat(amount)) ? parseFloat(amount) : 0;
return `<span class="invoice-currency"><?= __('currency') ?></span>${numericAmount.toFixed(decimals)}`;
};
const setInvoiceText = (id, value) => {
const element = document.getElementById(id);
if (element) {
element.textContent = value == null || value === '' ? '---' : String(value);
}
return element;
};
const toggleInvoiceField = (containerId, valueId, value) => {
const container = document.getElementById(containerId);
const valueElement = document.getElementById(valueId);
if (!container || !valueElement) {
return;
}
if (value) {
valueElement.textContent = value;
container.style.display = '';
} else {
valueElement.textContent = '';
container.style.display = 'none';
}
};
let invoicePrintOriginalParent = null;
let invoicePrintOriginalNextSibling = null;
let invoicePrintRestoreTimer = null;
const prepareInvoiceForPrint = () => {
const modal = document.getElementById('viewInvoiceModal');
if (!modal || !document.body) {
return;
}
if (!invoicePrintOriginalParent) {
invoicePrintOriginalParent = modal.parentNode;
invoicePrintOriginalNextSibling = modal.nextSibling;
}
if (modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
document.body.classList.add('printing-invoice');
};
const restoreInvoiceAfterPrint = () => {
const modal = document.getElementById('viewInvoiceModal');
if (!document.body) {
return;
}
document.body.classList.remove('printing-invoice');
if (!modal || !invoicePrintOriginalParent || modal.parentNode === invoicePrintOriginalParent) {
return;
}
invoicePrintOriginalParent.insertBefore(modal, invoicePrintOriginalNextSibling);
};
window.printInvoiceDocument = function() {
prepareInvoiceForPrint();
window.print();
if (invoicePrintRestoreTimer) {
window.clearTimeout(invoicePrintRestoreTimer);
}
invoicePrintRestoreTimer = window.setTimeout(() => {
restoreInvoiceAfterPrint();
}, 1200);
};
window.addEventListener('beforeprint', prepareInvoiceForPrint);
window.addEventListener('afterprint', restoreInvoiceAfterPrint);
window.viewAndPrintA4Invoice = function(data, autoPrint = true) {
if (!data) return;
const invoiceType = String(data.type || 'sale').toLowerCase() === 'purchase' ? 'purchase' : 'sale';
const invoiceDisplayNo = data.document_no || data.transaction_no || ((invoiceType === 'purchase' ? 'PUR-' : 'INV-') + data.id.toString().padStart(5, '0'));
const paymentText = humanizeInvoiceText(data.payment_type || 'cash');
const statusKey = String(data.status || '').toLowerCase();
const statusText = statusKey === 'partially_paid' ? 'Partially Paid' : humanizeInvoiceText(statusKey);
const typeText = invoiceType === 'purchase' ? 'Purchase Invoice' : 'Sales Invoice';
const documentTitle = invoiceType === 'purchase' ? 'Purchase Invoice / فاتورة شراء' : 'Tax Invoice / فاتورة ضريبية';
const documentSubtitle = invoiceType === 'purchase' ? 'Official purchase record / مستند شراء رسمي' : 'Official tax document / مستند ضريبي رسمي';
const partyLabelText = invoiceType === 'purchase' ? 'Supplier Details / بيانات المورد' : 'Bill To / بيانات العميل';
const partyLabelEn = invoiceType === 'purchase' ? 'Supplier Details' : 'Bill To';
const partyLabelAr = invoiceType === 'purchase' ? 'بيانات المورد' : 'بيانات العميل';
const formatQty = typeof window.formatQuantity === 'function'
? window.formatQuantity
: (value) => {
const numericValue = Number.isFinite(parseFloat(value)) ? parseFloat(value) : 0;
return Number.isInteger(numericValue)
? String(numericValue)
: numericValue.toFixed(3).replace(/\.?0+$/, '');
};
setInvoiceText('invNumber', invoiceDisplayNo);
setInvoiceText('invDate', data.invoice_date || '---');
setInvoiceText('invPaymentType', paymentText);
setInvoiceText('invPaymentTypeSummary', paymentText);
setInvoiceText('invCustomerName', data.customer_name || '---');
setInvoiceText('invStatusText', statusText);
setInvoiceText('invDocumentTitle', documentTitle);
setInvoiceText('invDocumentSubtitle', documentSubtitle);
setInvoiceText('invAmountInWords', data.total_in_words || '---');
toggleInvoiceField('invCustomerPhoneContainer', 'invCustomerPhone', data.customer_phone || '');
toggleInvoiceField('invCustomerTaxIdContainer', 'invCustomerTaxId', data.customer_tax_id || '');
toggleInvoiceField('invOutletRow', 'invOutletName', data.outlet_name || '');
const partyLabel = document.getElementById('invPartyLabel');
if (partyLabel) {
partyLabel.textContent = partyLabelText;
partyLabel.setAttribute('data-en', partyLabelEn);
partyLabel.setAttribute('data-ar', partyLabelAr);
}
const invoiceTypeLabel = document.getElementById('invoiceTypeLabel');
if (invoiceTypeLabel) {
invoiceTypeLabel.textContent = typeText;
invoiceTypeLabel.className = 'invoice-pill ' + (invoiceType === 'purchase' ? 'invoice-pill--purchase' : 'invoice-pill--sale');
}
const statusLabel = document.getElementById('invoiceStatusLabel');
let statusClass = 'invoice-pill--neutral';
if (statusKey === 'paid') {
statusClass = 'invoice-pill--paid';
} else if (statusKey === 'unpaid') {
statusClass = 'invoice-pill--unpaid';
} else if (statusKey === 'partially_paid') {
statusClass = 'invoice-pill--partial';
}
if (statusLabel) {
statusLabel.textContent = statusText;
statusLabel.className = 'invoice-pill ' + statusClass;
}
const body = document.getElementById('invItemsBody');
if (body) {
body.innerHTML = '';
if (Array.isArray(data.items) && data.items.length > 0) {
data.items.forEach((item, index) => {
const tr = document.createElement('tr');
const englishName = escapeHtml(item.name_en || item.name || 'Item');
const arabicName = escapeHtml(item.name_ar || '');
const vatRateText = `${(Number.isFinite(parseFloat(item.vat_rate)) ? parseFloat(item.vat_rate) : 0).toFixed(2)}%`;
const itemDetails = [];
if (arabicName) {
itemDetails.push(arabicName);
}
itemDetails.push(`VAT ${vatRateText}`);
const itemNameHtml = `
<div class="invoice-item-name">${englishName}</div>
<div class="invoice-item-secondary">${itemDetails.join(' • ')}</div>
`;
tr.innerHTML = `
<td class="text-center invoice-line-no">${index + 1}</td>
<td>${itemNameHtml}</td>
<td class="text-center">${formatQty(item.quantity)}</td>
<td class="text-end invoice-amount">${formatInvoiceCurrency(item.unit_price, 3)}</td>
<td class="text-end invoice-amount">${formatInvoiceCurrency(item.total_price, 3)}</td>
`;
body.appendChild(tr);
});
} else {
body.innerHTML = '<tr><td colspan="5" class="text-center text-muted py-4">No invoice items / لا توجد أصناف</td></tr>';
}
}
const vatVal = Number.isFinite(parseFloat(data.vat_amount)) ? parseFloat(data.vat_amount) : 0;
const totalVal = Number.isFinite(parseFloat(data.total_amount)) ? parseFloat(data.total_amount) : 0;
const discountVal = Math.max(0, Number.isFinite(parseFloat(data.discount_amount)) ? parseFloat(data.discount_amount) : 0);
const isPosInvoice = String(data.is_pos || '0') === '1';
const grossBeforeDiscount = isPosInvoice ? totalVal : (totalVal + vatVal);
const grandTotalParsed = parseFloat(data.total_with_vat);
const grandTotalValue = Number.isFinite(grandTotalParsed) ? grandTotalParsed : Math.max(0, grossBeforeDiscount - discountVal);
const subtotalExVat = isPosInvoice ? Math.max(0, grossBeforeDiscount - vatVal) : totalVal;
const paidAmount = Number.isFinite(parseFloat(data.paid_amount)) ? parseFloat(data.paid_amount) : 0;
const balance = Math.max(0, grandTotalValue - paidAmount);
const subtotalEl = document.getElementById('invSubtotal');
if (subtotalEl) subtotalEl.innerHTML = formatInvoiceCurrency(subtotalExVat, 3);
const vatEl = document.getElementById('invVatAmount');
if (vatEl) vatEl.innerHTML = formatInvoiceCurrency(vatVal, 3);
const discountRow = document.getElementById('invDiscountRow');
const discountAmountEl = document.getElementById('invDiscountAmount');
if (discountRow && discountAmountEl) {
if (discountVal > 0) {
discountRow.style.display = 'flex';
discountAmountEl.innerHTML = formatInvoiceCurrency(discountVal, 3);
} else {
discountRow.style.display = 'none';
discountAmountEl.innerHTML = '';
}
}
const grandTotalEl = document.getElementById('invGrandTotal');
if (grandTotalEl) grandTotalEl.innerHTML = formatInvoiceCurrency(grandTotalValue, 3);
const paidInfoEl = document.getElementById('invPaidInfo');
if (paidInfoEl) paidInfoEl.innerHTML = formatInvoiceCurrency(paidAmount, 3);
const balanceInfoEl = document.getElementById('invBalanceInfo');
if (balanceInfoEl) balanceInfoEl.innerHTML = formatInvoiceCurrency(balance, 3);
const companyName = <?= json_encode($data['settings']['company_name'] ?? 'Accounting System') ?>;
const vatNo = <?= json_encode($data['settings']['vat_number'] ?? '') ?>;
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=112x112&data=${encodeURIComponent(qrData)}`;
const qrCode = document.getElementById('invQrCode');
if (qrCode) {
qrCode.innerHTML = `<img src="${qrUrl}" alt="QR Code" width="112" height="112" class="bg-white rounded" style="width: 112px; height: 112px;">`;
}
const viewModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('viewInvoiceModal'));
viewModal.show();
const paymentsPromise = 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 (Array.isArray(payments) && payments.length > 0) {
if (paymentsBody) {
payments.slice(0, 3).forEach((payment) => {
const paymentChip = document.createElement('div');
paymentChip.className = 'invoice-payment-pill';
paymentChip.innerHTML = `
<span>${escapeHtml(payment.payment_date || '')}</span>
<span class="invoice-payment-sep">•</span>
<span>${escapeHtml(humanizeInvoiceText(payment.payment_method || ''))}</span>
<span class="invoice-payment-sep">•</span>
<strong>${formatInvoiceCurrency(payment.amount, 3)}</strong>
`;
paymentsBody.appendChild(paymentChip);
});
if (payments.length > 3) {
const moreChip = document.createElement('div');
moreChip.className = 'invoice-payment-pill invoice-payment-pill--more';
moreChip.textContent = `+${payments.length - 3} more payments`;
paymentsBody.appendChild(moreChip);
}
}
if (paymentsSection) paymentsSection.style.display = 'block';
} else if (paymentsSection) {
paymentsSection.style.display = 'none';
}
})
.catch((err) => {
console.error('Error fetching payments:', err);
const paymentsSection = document.getElementById('invPaymentsSection');
if (paymentsSection) paymentsSection.style.display = 'none';
});
if (autoPrint) {
paymentsPromise.finally(() => {
setTimeout(() => {
window.printInvoiceDocument();
}, 450);
});
}
};
<?php
$autoInvoicePayload = null;
if (
isset($_SESSION['trigger_invoice_modal'], $_SESSION['show_invoice_id'], $_SESSION['show_invoice_page']) &&
in_array($page, ['sales', 'purchases'], true) &&
$_SESSION['show_invoice_page'] === $page
) {
$autoInvoiceId = (int)$_SESSION['show_invoice_id'];
$autoInvoiceType = $page === 'purchases' ? 'purchase' : 'sale';
unset($_SESSION['trigger_invoice_modal'], $_SESSION['show_invoice_id'], $_SESSION['show_invoice_page']);
try {
$autoTable = $autoInvoiceType === 'purchase' ? 'purchases' : 'invoices';
$autoPartyTable = $autoInvoiceType === 'purchase' ? 'suppliers' : 'customers';
$autoPartyCol = $autoInvoiceType === 'purchase' ? 'supplier_id' : 'customer_id';
$autoTaxColumn = entity_tax_column($autoPartyTable);
$autoTaxSelect = $autoTaxColumn !== null ? "c.$autoTaxColumn AS customer_tax_id" : "'' AS customer_tax_id";
$autoOutletSelect = "'' AS outlet_name";
$autoOutletJoin = '';
if (db_column_exists($autoTable, 'outlet_id') && db_table_exists('outlets')) {
$autoOutletSelect = 'o.name AS outlet_name';
$autoOutletJoin = 'LEFT JOIN outlets o ON doc.outlet_id = o.id';
}
$autoStmt = db()->prepare("SELECT doc.*, c.name AS customer_name, c.phone AS customer_phone, $autoTaxSelect, $autoOutletSelect
FROM $autoTable doc
LEFT JOIN $autoPartyTable c ON doc.$autoPartyCol = c.id
$autoOutletJoin
WHERE doc.id = ?
LIMIT 1");
$autoStmt->execute([$autoInvoiceId]);
$autoInvoicePayload = $autoStmt->fetch(PDO::FETCH_ASSOC);
if ($autoInvoicePayload) {
$autoItemSql = $autoInvoiceType === 'purchase'
? "SELECT pi.*, i.name_en, i.name_ar, i.vat_rate FROM purchase_items pi LEFT JOIN stock_items i ON pi.item_id = i.id WHERE pi.purchase_id = ?"
: "SELECT ii.*, i.name_en, i.name_ar, i.vat_rate FROM invoice_items ii LEFT JOIN stock_items i ON ii.item_id = i.id WHERE ii.invoice_id = ?";
$autoItemsStmt = db()->prepare($autoItemSql);
$autoItemsStmt->execute([$autoInvoiceId]);
$autoInvoicePayload['items'] = $autoItemsStmt->fetchAll(PDO::FETCH_ASSOC);
$autoInvoicePayload['type'] = $autoInvoiceType;
$autoInvoicePayload['total_with_vat'] = (float)($autoInvoicePayload['total_with_vat'] ?? (($autoInvoicePayload['total_amount'] ?? 0) + ($autoInvoicePayload['vat_amount'] ?? 0)));
$autoInvoicePayload['paid_amount'] = (float)($autoInvoicePayload['paid_amount'] ?? 0);
$autoInvoicePayload['total_in_words'] = numberToWordsOMR($autoInvoicePayload['total_with_vat']);
$autoTransactionNo = trim((string)($autoInvoicePayload['transaction_no'] ?? ''));
$autoPrefix = $autoInvoiceType === 'purchase' ? 'PUR' : 'INV';
$autoInvoicePayload['document_no'] = ($autoInvoiceType === 'sale' && $autoTransactionNo !== '')
? $autoTransactionNo
: $autoPrefix . '-' . str_pad((string)$autoInvoicePayload['id'], 5, '0', STR_PAD_LEFT);
}
} catch (Throwable $e) {
$autoInvoicePayload = null;
}
}
?>
<?php if (!empty($autoInvoicePayload)): ?>
setTimeout(() => {
if (window.viewAndPrintA4Invoice) {
window.viewAndPrintA4Invoice(<?= json_encode($autoInvoicePayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, true);
}
}, 300);
<?php endif; ?>