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}
${dateHtml}
${priceHtml}
`; } 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 = ""; const outletName = ""; const companyPhone = ""; const companyVat = ""; const companyLogo = ""; container.innerHTML = `
${companyLogo ? `Logo` : ''}
${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 / عميل عابر'}
${itemsHtml}
ITEM / الصنف VAT / الضريبة TOTAL / الإجمالي
Subtotal (Excl. VAT) / المجموع الفرعي (دون الضريبة) ${(subtotal - totalVat).toFixed(3)}
VAT / الضريبة ${totalVat.toFixed(2)}
TOTAL (Incl. VAT) / الإجمالي (شامل الضريبة) ${subtotal.toFixed(3)}
PAID / المدفوع ${parseFloat(inv.paid_amount).toFixed(3)}
BALANCE / الرصيد ${(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(); }