function escapeBarcodeLabelHtml(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function getSingleBarcodeLabelData() {
return window.currentSingleBarcodeLabel || null;
}
function getSingleBarcodePrintDimensions() {
return {
width: Math.max(parseInt(document.getElementById('barcodeWidth').value, 10) || 50, 10),
height: Math.max(parseInt(document.getElementById('barcodeHeight').value, 10) || 30, 10)
};
}
function getSingleBarcodeScale(labelWidthMm, labelHeightMm) {
if (labelWidthMm <= 32 || labelHeightMm <= 20) {
return 0.96;
}
if (labelWidthMm <= 40 || labelHeightMm <= 25) {
return 0.92;
}
if (labelWidthMm <= 50 || labelHeightMm <= 30) {
return 0.88;
}
return 0.84;
}
function getSingleBarcodeOptions(sku, labelWidthMm, labelHeightMm) {
const skuText = String(sku || '');
const skuLength = skuText.length;
const compact = labelWidthMm <= 40 || labelHeightMm <= 25;
const extraCompact = labelWidthMm <= 32 || labelHeightMm <= 20;
const showValue = !extraCompact && labelHeightMm >= 24 && skuLength <= 18;
let moduleWidth = 1.8;
if (skuLength >= 18 || labelWidthMm <= 35) {
moduleWidth = 1.1;
} else if (skuLength >= 14 || labelWidthMm <= 40) {
moduleWidth = 1.3;
} else if (labelWidthMm <= 50) {
moduleWidth = 1.6;
}
let barcodeHeight = 56;
if (labelHeightMm <= 20) {
barcodeHeight = 30;
} else if (labelHeightMm <= 25) {
barcodeHeight = 38;
} else if (labelHeightMm <= 30) {
barcodeHeight = 46;
} else if (labelHeightMm <= 35) {
barcodeHeight = 56;
} else {
barcodeHeight = 64;
}
const quietZone = extraCompact ? 4 : compact ? 6 : 10;
return {
format: "CODE128",
lineColor: "#000",
width: moduleWidth,
height: barcodeHeight,
displayValue: showValue,
fontSize: compact ? 10 : 12,
textMargin: compact ? 2 : 4,
margin: quietZone,
marginTop: 0,
marginBottom: showValue ? 2 : 0,
fontOptions: "bold"
};
}
function buildSingleBarcodeNameHtml(nameAr, nameEn) {
const lines = [];
if (nameAr) {
lines.push('
' + escapeBarcodeLabelHtml(nameAr) + '
');
}
if (nameEn) {
lines.push('' + escapeBarcodeLabelHtml(nameEn) + '
');
}
return lines.join('');
}
function applyBarcodePreviewNameStyles(container, compact) {
if (!container) {
return;
}
const arabicLine = container.querySelector('.label-name-ar');
const englishLine = container.querySelector('.label-name-en');
container.style.width = '100%';
container.style.maxWidth = '100%';
container.style.lineHeight = '1';
container.style.marginBottom = '0';
if (arabicLine) {
arabicLine.style.display = 'block';
arabicLine.style.fontWeight = '700';
arabicLine.style.fontSize = compact ? '1.04rem' : '1.16rem';
arabicLine.style.direction = 'rtl';
arabicLine.style.whiteSpace = 'nowrap';
arabicLine.style.overflow = 'hidden';
arabicLine.style.textOverflow = 'ellipsis';
}
if (englishLine) {
englishLine.style.display = 'block';
englishLine.style.fontWeight = '600';
englishLine.style.fontSize = compact ? '0.90rem' : '0.99rem';
englishLine.style.whiteSpace = 'nowrap';
englishLine.style.overflow = 'hidden';
englishLine.style.textOverflow = 'ellipsis';
}
}
function applyBarcodePreviewDateStyles(container, compact) {
if (!container) {
return;
}
container.style.width = '100%';
container.style.maxWidth = '260px';
container.style.direction = 'ltr';
container.style.textAlign = 'center';
container.style.lineHeight = '1';
container.style.fontWeight = '600';
container.style.fontSize = compact ? '0.88rem' : '0.98rem';
container.style.marginTop = '0';
const row = container.querySelector('.label-date-row');
if (row) {
row.style.gap = compact ? '8px' : '12px';
}
container.querySelectorAll('.label-date-item').forEach((item) => {
item.style.gap = compact ? '3px' : '5px';
});
}
function buildSingleBarcodeSvgMarkup(sku, labelWidthMm, labelHeightMm) {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
const scale = getSingleBarcodeScale(labelWidthMm, labelHeightMm);
JsBarcode(svg, sku, getSingleBarcodeOptions(sku, labelWidthMm, labelHeightMm));
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
svg.setAttribute('shape-rendering', 'crispEdges');
svg.style.width = Math.round(scale * 100) + '%';
svg.style.maxWidth = '100%';
svg.style.height = 'auto';
svg.style.display = 'block';
svg.style.margin = '0 auto';
svg.style.shapeRendering = 'crispEdges';
return svg.outerHTML;
}
function renderSingleBarcodePreview() {
const label = getSingleBarcodeLabelData();
if (!label) {
return;
}
const dimensions = getSingleBarcodePrintDimensions();
const width = dimensions.width;
const height = dimensions.height;
const scale = getSingleBarcodeScale(width, height);
const compact = width <= 40 || height <= 25;
const nameContainer = document.getElementById('barcodeLabelName');
const priceContainer = document.getElementById('barcodeLabelPrice');
const previewContainer = document.getElementById('barcodeContainer');
const svg = document.getElementById('barcodeSvg');
if (!nameContainer || !priceContainer || !previewContainer || !svg) {
return;
}
nameContainer.innerHTML = buildSingleBarcodeNameHtml(label.nameAr, label.nameEn);
applyBarcodePreviewNameStyles(nameContainer, compact);
priceContainer.textContent = label.price ? 'OMR ' + label.price : '';
priceContainer.style.display = label.price ? 'block' : 'none';
priceContainer.style.fontWeight = '700';
priceContainer.style.fontSize = compact ? '1.04rem' : '1.14rem';
priceContainer.style.lineHeight = '1';
priceContainer.style.marginTop = '0';
previewContainer.style.display = 'flex';
previewContainer.style.flexDirection = 'column';
previewContainer.style.alignItems = 'center';
previewContainer.style.justifyContent = 'center';
previewContainer.style.gap = compact ? '3px' : '4px';
previewContainer.style.width = Math.min(Math.max(width * 3.8, 185), 310) + 'px';
previewContainer.style.maxWidth = '100%';
previewContainer.style.padding = height <= 25 ? '9px 11px' : '11px 13px';
svg.innerHTML = '';
JsBarcode(svg, label.sku, getSingleBarcodeOptions(label.sku, width, height));
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
svg.setAttribute('shape-rendering', 'crispEdges');
svg.style.width = Math.round(scale * 100) + '%';
svg.style.maxWidth = '100%';
svg.style.height = 'auto';
svg.style.display = 'block';
svg.style.margin = '0 auto';
svg.style.shapeRendering = 'crispEdges';
}
function initSingleBarcodePreviewControls() {
['barcodeWidth', 'barcodeHeight'].forEach((id) => {
const input = document.getElementById(id);
if (!input || input.dataset.barcodePreviewBound === '1') {
return;
}
input.dataset.barcodePreviewBound = '1';
input.addEventListener('input', renderSingleBarcodePreview);
input.addEventListener('change', renderSingleBarcodePreview);
});
}
initSingleBarcodePreviewControls();
window.printItemBarcode = function(sku, nameAr, nameEn, price) {
if (!sku) {
Swal.fire('Error', 'This item has no SKU/Barcode assigned.', 'error');
return;
}
window.currentSingleBarcodeLabel = {
sku: String(sku),
nameAr: nameAr || '',
nameEn: nameEn || '',
price: price || ''
};
renderSingleBarcodePreview();
const modal = new bootstrap.Modal(document.getElementById('barcodePrintModal'));
modal.show();
};
window.executeBarcodePrint = function() {
const qty = Math.max(parseInt(document.getElementById('barcodeQty').value, 10) || 1, 1);
const dimensions = getSingleBarcodePrintDimensions();
const width = dimensions.width;
const height = dimensions.height;
const label = getSingleBarcodeLabelData();
if (!label) {
return;
}
const nameHtml = buildSingleBarcodeNameHtml(label.nameAr, label.nameEn);
const price = label.price ? 'OMR ' + escapeBarcodeLabelHtml(label.price) : '';
const svg = buildSingleBarcodeSvgMarkup(label.sku, width, height);
const compactClass = width <= 40 || height <= 25 ? 'label-compact' : '';
const iframe = document.createElement('iframe');
iframe.style.position = 'absolute';
iframe.style.width = '0px';
iframe.style.height = '0px';
iframe.style.border = 'none';
document.body.appendChild(iframe);
const doc = iframe.contentWindow.document;
let labelsHtml = '';
for (let i = 0; i < qty; i++) {
labelsHtml += `
${nameHtml ? `
${nameHtml}
` : ''}
${svg}
${price ? `
${price}
` : ''}
`;
}
doc.open();
doc.write(`
${labelsHtml}
`);
doc.close();
iframe.contentWindow.focus();
setTimeout(() => {
iframe.contentWindow.print();
setTimeout(() => {
document.body.removeChild(iframe);
}, 2000);
}, 500);
};
function formatBarcodeLabelDate(value) {
const dateText = String(value || '').trim();
const match = dateText.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (match) {
return `${match[3]}/${match[2]}/${match[1]}`;
}
return '';
}
function buildDatedBarcodeDateRowsHtml(productionDate, expiryDate) {
const productionText = formatBarcodeLabelDate(productionDate) || '--/--/----';
const expiryText = formatBarcodeLabelDate(expiryDate) || '--/--/----';
return `
P:${escapeBarcodeLabelHtml(productionText)}
E:${escapeBarcodeLabelHtml(expiryText)}
`;
}
function getDatedBarcodeLabelData() {
return window.currentDatedBarcodeLabel || null;
}
function getDatedBarcodePrintDimensions() {
return {
width: Math.max(parseInt(document.getElementById('datedBarcodeWidth').value, 10) || 50, 10),
height: Math.max(parseInt(document.getElementById('datedBarcodeHeight').value, 10) || 35, 10)
};
}
function getDatedBarcodeScale(labelWidthMm, labelHeightMm) {
if (labelWidthMm <= 32 || labelHeightMm <= 24) {
return 0.96;
}
if (labelWidthMm <= 40 || labelHeightMm <= 30) {
return 0.95;
}
if (labelWidthMm <= 50 || labelHeightMm <= 35) {
return 0.94;
}
return 0.90;
}
function getDatedBarcodeOptions(sku, labelWidthMm, labelHeightMm) {
const baseOptions = getSingleBarcodeOptions(sku, labelWidthMm, labelHeightMm);
const skuLength = String(sku || '').length;
const showValue = labelHeightMm >= 46 && labelWidthMm >= 50 && skuLength <= 16;
return {
...baseOptions,
displayValue: showValue,
fontSize: showValue ? 10 : 8,
textMargin: showValue ? 2 : 0,
height: Math.max(Math.round(baseOptions.height * (showValue ? 0.88 : 0.96)), 28),
margin: Math.max(baseOptions.margin, 6),
marginBottom: showValue ? 1 : 0
};
}
function buildDatedBarcodeSvgMarkup(sku, labelWidthMm, labelHeightMm) {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
const scale = getDatedBarcodeScale(labelWidthMm, labelHeightMm);
JsBarcode(svg, sku, getDatedBarcodeOptions(sku, labelWidthMm, labelHeightMm));
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
svg.setAttribute('shape-rendering', 'crispEdges');
svg.style.width = Math.round(scale * 100) + '%';
svg.style.maxWidth = '100%';
svg.style.height = 'auto';
svg.style.display = 'block';
svg.style.margin = '0 auto';
svg.style.shapeRendering = 'crispEdges';
return svg.outerHTML;
}
function renderDatedBarcodePreview() {
const label = getDatedBarcodeLabelData();
if (!label) {
return;
}
const dimensions = getDatedBarcodePrintDimensions();
const width = dimensions.width;
const height = dimensions.height;
const scale = getDatedBarcodeScale(width, height);
const compact = width <= 40 || height <= 30;
const nameContainer = document.getElementById('datedBarcodeLabelName');
const datesContainer = document.getElementById('datedBarcodeLabelDates');
const priceContainer = document.getElementById('datedBarcodeLabelPrice');
const previewContainer = document.getElementById('datedBarcodeContainer');
const svg = document.getElementById('datedBarcodeSvg');
const productionInput = document.getElementById('datedBarcodeProductionDate');
const expiryInput = document.getElementById('datedBarcodeExpiryDate');
if (!nameContainer || !datesContainer || !previewContainer || !svg) {
return;
}
nameContainer.innerHTML = buildSingleBarcodeNameHtml(label.nameAr, label.nameEn);
applyBarcodePreviewNameStyles(nameContainer, compact);
datesContainer.innerHTML = buildDatedBarcodeDateRowsHtml(
productionInput ? productionInput.value : '',
expiryInput ? expiryInput.value : ''
);
applyBarcodePreviewDateStyles(datesContainer, compact);
if (priceContainer) {
priceContainer.textContent = label.price ? 'OMR ' + label.price : '';
priceContainer.style.display = label.price ? 'block' : 'none';
priceContainer.style.fontWeight = '700';
priceContainer.style.fontSize = compact ? '0.98rem' : '1.08rem';
priceContainer.style.lineHeight = '1';
priceContainer.style.marginTop = '0';
}
previewContainer.style.display = 'flex';
previewContainer.style.flexDirection = 'column';
previewContainer.style.alignItems = 'center';
previewContainer.style.justifyContent = 'center';
previewContainer.style.gap = compact ? '3px' : '4px';
previewContainer.style.width = Math.min(Math.max(width * 4.0, 190), 320) + 'px';
previewContainer.style.maxWidth = '100%';
previewContainer.style.padding = height <= 30 ? '9px 10px' : '10px 12px';
svg.innerHTML = '';
JsBarcode(svg, label.sku, getDatedBarcodeOptions(label.sku, width, height));
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
svg.setAttribute('shape-rendering', 'crispEdges');
svg.style.width = Math.round(scale * 100) + '%';
svg.style.maxWidth = '100%';
svg.style.height = 'auto';
svg.style.display = 'block';
svg.style.margin = '0 auto';
svg.style.shapeRendering = 'crispEdges';
}
function initDatedBarcodePreviewControls() {
['datedBarcodeWidth', 'datedBarcodeHeight', 'datedBarcodeProductionDate', 'datedBarcodeExpiryDate'].forEach((id) => {
const input = document.getElementById(id);
if (!input || input.dataset.barcodePreviewBound === '1') {
return;
}
input.dataset.barcodePreviewBound = '1';
input.addEventListener('input', renderDatedBarcodePreview);
input.addEventListener('change', renderDatedBarcodePreview);
});
}
initDatedBarcodePreviewControls();
window.printItemBarcodeWithDates = function(sku, nameAr, nameEn, price, defaultExpiryDate) {
if (!sku) {
Swal.fire('Error', 'This item has no SKU/Barcode assigned.', 'error');
return;
}
window.currentDatedBarcodeLabel = {
sku: String(sku),
nameAr: nameAr || '',
nameEn: nameEn || '',
price: price || ''
};
const productionInput = document.getElementById('datedBarcodeProductionDate');
const expiryInput = document.getElementById('datedBarcodeExpiryDate');
if (productionInput) {
productionInput.value = '';
}
if (expiryInput) {
expiryInput.value = defaultExpiryDate || '';
}
renderDatedBarcodePreview();
const modal = new bootstrap.Modal(document.getElementById('datedBarcodePrintModal'));
modal.show();
};
window.executeDatedBarcodePrint = function() {
const qty = Math.max(parseInt(document.getElementById('datedBarcodeQty').value, 10) || 1, 1);
const dimensions = getDatedBarcodePrintDimensions();
const width = dimensions.width;
const height = dimensions.height;
const label = getDatedBarcodeLabelData();
const productionDate = document.getElementById('datedBarcodeProductionDate').value || '';
const expiryDate = document.getElementById('datedBarcodeExpiryDate').value || '';
if (!label) {
return;
}
if (!productionDate || !expiryDate) {
Swal.fire('Missing dates', 'Please select both production and expiry dates before printing.', 'warning');
return;
}
if (expiryDate < productionDate) {
Swal.fire('Invalid dates', 'Expiry date must be the same as or later than the production date.', 'warning');
return;
}
const nameHtml = buildSingleBarcodeNameHtml(label.nameAr, label.nameEn);
const dateHtml = buildDatedBarcodeDateRowsHtml(productionDate, expiryDate);
const priceHtml = label.price ? `OMR ${escapeBarcodeLabelHtml(label.price)}
` : '';
const svg = buildDatedBarcodeSvgMarkup(label.sku, width, height);
const compactClass = width <= 40 || height <= 30 ? 'label-compact' : '';
const iframe = document.createElement('iframe');
iframe.style.position = 'absolute';
iframe.style.width = '0px';
iframe.style.height = '0px';
iframe.style.border = 'none';
document.body.appendChild(iframe);
const doc = iframe.contentWindow.document;
let labelsHtml = '';
for (let i = 0; i < qty; i++) {
labelsHtml += `
${nameHtml ? `
${nameHtml}
` : ''}
${svg}
`;
}
doc.open();
doc.write(`
${labelsHtml}
`);
doc.close();
iframe.contentWindow.focus();
setTimeout(() => {
iframe.contentWindow.print();
setTimeout(() => {
document.body.removeChild(iframe);
}, 2000);
}, 500);
};
window.printPosReceiptFromInvoice = function(inv) {
const container = document.getElementById('posReceiptContent');
const itemsHtml = inv.items.map(item => {
const itemTotal = item.unit_price * item.quantity;
const vatRate = parseFloat(item.vat_rate !== undefined && item.vat_rate !== null ? item.vat_rate : 0);
const vatAmount = itemTotal * (vatRate / (100 + vatRate));
return `
${item.name_en} / ${item.name_ar} ${item.quantity} x ${parseFloat(item.unit_price).toFixed(3)} |
${vatAmount.toFixed(2)} |
${itemTotal.toFixed(3)} |
`;
}).join('');
const totalVat = inv.items.reduce((sum, item) => {
const itemTotal = item.unit_price * item.quantity;
const vatRate = parseFloat(item.vat_rate !== undefined && item.vat_rate !== null ? item.vat_rate : 0);
return sum + (itemTotal * (vatRate / (100 + vatRate)));
}, 0);
const subtotal = inv.items.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0);
const companyName = "= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>";
const outletName = "= htmlspecialchars($data['settings']['current_outlet_name'] ?? '') ?>";
const companyPhone = "= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>";
const companyVat = "= htmlspecialchars($data['settings']['vat_number'] ?? '') ?>";
const companyLogo = "= htmlspecialchars($data['settings']['company_logo'] ?? '') ?>";
container.innerHTML = `
${companyLogo ? `

` : ''}
${companyName}
${inv.outlet_name ? `
${inv.outlet_name}
` : ''}
${companyPhone ? `
Tel: ${companyPhone}
` : ''}
${companyVat ? `
VAT: ${companyVat}
` : ''}
TAX INVOICE / فاتورة ضريبية
Inv / رقم: INV-${inv.id.toString().padStart(5, '0')}
Date / التاريخ: ${inv.invoice_date}
Customer / العميل: ${inv.customer_name || 'Walk-in / عميل عابر'}
| ITEM / الصنف |
VAT / الضريبة |
TOTAL / الإجمالي |
${itemsHtml}
Subtotal (Excl. VAT) / المجموع الفرعي (دون الضريبة)
= __('currency') ?> ${(subtotal - totalVat).toFixed(3)}
VAT / الضريبة
= __('currency') ?> ${totalVat.toFixed(2)}
TOTAL (Incl. VAT) / الإجمالي (شامل الضريبة)
= __('currency') ?> ${subtotal.toFixed(3)}
PAID / المدفوع
= __('currency') ?> ${parseFloat(inv.paid_amount).toFixed(3)}
BALANCE / الرصيد
= __('currency') ?> ${(subtotal - inv.paid_amount).toFixed(3)}
Thank You for your business! / شكراً لتعاملكم معنا!
`;
const posModal = new bootstrap.Modal(document.getElementById('posReceiptModal'));
posModal.show();
};
function printPosReceipt() {
const content = document.getElementById('posReceiptContent').innerHTML;
const printArea = document.getElementById('posPrintArea');
printArea.innerHTML = `${content}
`;
document.body.classList.add('printing-receipt');
window.print();
document.body.classList.remove('printing-receipt');
location.reload();
}