diff --git a/assets/pasted-20260506-134907-6b1d58c7.png b/assets/pasted-20260506-134907-6b1d58c7.png new file mode 100644 index 0000000..6191165 Binary files /dev/null and b/assets/pasted-20260506-134907-6b1d58c7.png differ diff --git a/index.php b/index.php index 0a12037..47d3759 100644 --- a/index.php +++ b/index.php @@ -5887,6 +5887,21 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]); .units-table tbody tr:last-child td { border-bottom: none; } + .items-list-heading { + font-size: calc(1.25rem + 1px); + font-weight: 800; + color: #1d4ed8; + letter-spacing: 0.01em; + } + .items-list-table thead th { + font-size: 0.82rem; + font-weight: 800; + color: #1d4ed8; + letter-spacing: 0.05em; + text-transform: uppercase; + background: rgba(29, 78, 216, 0.08); + border-bottom-color: rgba(29, 78, 216, 0.18); + } .units-name-stack { display: grid; gap: 0.2rem; @@ -7181,7 +7196,7 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
-
Stock Items ()
+
Stock Items ()
- +
diff --git a/pages/barcode_pos_script.php b/pages/barcode_pos_script.php index 7b4cf13..764941b 100644 --- a/pages/barcode_pos_script.php +++ b/pages/barcode_pos_script.php @@ -98,13 +98,13 @@ container.style.width = '100%'; container.style.maxWidth = '100%'; - container.style.lineHeight = '1.05'; + container.style.lineHeight = '1'; container.style.marginBottom = '0'; if (arabicLine) { arabicLine.style.display = 'block'; arabicLine.style.fontWeight = '700'; - arabicLine.style.fontSize = compact ? '0.98rem' : '1.10rem'; + arabicLine.style.fontSize = compact ? '1.04rem' : '1.16rem'; arabicLine.style.direction = 'rtl'; arabicLine.style.whiteSpace = 'nowrap'; arabicLine.style.overflow = 'hidden'; @@ -114,7 +114,7 @@ if (englishLine) { englishLine.style.display = 'block'; englishLine.style.fontWeight = '600'; - englishLine.style.fontSize = compact ? '0.84rem' : '0.95rem'; + englishLine.style.fontSize = compact ? '0.90rem' : '0.99rem'; englishLine.style.whiteSpace = 'nowrap'; englishLine.style.overflow = 'hidden'; englishLine.style.textOverflow = 'ellipsis'; @@ -127,21 +127,21 @@ } container.style.width = '100%'; - container.style.maxWidth = '240px'; + container.style.maxWidth = '260px'; container.style.direction = 'ltr'; container.style.textAlign = 'center'; - container.style.lineHeight = '1.05'; + container.style.lineHeight = '1'; container.style.fontWeight = '600'; - container.style.fontSize = compact ? '0.80rem' : '0.92rem'; + 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 ? '10px' : '14px'; + row.style.gap = compact ? '8px' : '12px'; } container.querySelectorAll('.label-date-item').forEach((item) => { - item.style.gap = compact ? '4px' : '6px'; + item.style.gap = compact ? '3px' : '5px'; }); } @@ -186,7 +186,7 @@ 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.fontSize = compact ? '1.04rem' : '1.14rem'; priceContainer.style.lineHeight = '1'; priceContainer.style.marginTop = '0'; @@ -194,10 +194,10 @@ previewContainer.style.flexDirection = 'column'; previewContainer.style.alignItems = 'center'; previewContainer.style.justifyContent = 'center'; - previewContainer.style.gap = compact ? '4px' : '6px'; + 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 ? '10px 12px' : '12px 14px'; + previewContainer.style.padding = height <= 25 ? '9px 11px' : '11px 13px'; svg.innerHTML = ''; JsBarcode(svg, label.sku, getSingleBarcodeOptions(label.sku, width, height)); @@ -299,14 +299,14 @@ text-align: center; overflow: hidden; box-sizing: border-box; - padding: 1.1mm 1.8mm 1.2mm; - gap: 0.45mm; + padding: 0.9mm 1.55mm 1mm; + gap: 0.25mm; } .label-container:last-child { page-break-after: avoid; } .label-name { width: 100%; max-width: 100%; - line-height: 1.02; + line-height: 1; } .label-name-ar, .label-name-en { @@ -317,33 +317,33 @@ } .label-name-ar { font-weight: 700; - font-size: 12px; + font-size: 13.4px; direction: rtl; } .label-name-en { - font-size: 10px; + font-size: 11px; font-weight: 600; } .label-price { - font-size: 12px; + font-size: 13px; font-weight: 700; line-height: 1; white-space: nowrap; } .label-compact { - padding: 0.85mm 1.3mm 0.95mm; - gap: 0.35mm; + padding: 0.7mm 1.15mm 0.75mm; + gap: 0.2mm; } - .label-compact .label-name-ar { font-size: 10.5px; } - .label-compact .label-name-en { font-size: 8.75px; } - .label-compact .label-price { font-size: 10.5px; } + .label-compact .label-name-ar { font-size: 11.6px; } + .label-compact .label-name-en { font-size: 9.6px; } + .label-compact .label-price { font-size: 11.5px; } .barcode-wrap { width: 100%; display: flex; align-items: center; justify-content: center; - padding: 0 1mm; - margin: 0.15mm 0; + padding: 0 0.8mm; + margin: 0.1mm 0; overflow: visible; } .barcode-wrap svg { @@ -483,7 +483,7 @@ priceContainer.textContent = label.price ? 'OMR ' + label.price : ''; priceContainer.style.display = label.price ? 'block' : 'none'; priceContainer.style.fontWeight = '700'; - priceContainer.style.fontSize = compact ? '0.90rem' : '1.02rem'; + priceContainer.style.fontSize = compact ? '0.98rem' : '1.08rem'; priceContainer.style.lineHeight = '1'; priceContainer.style.marginTop = '0'; } @@ -492,10 +492,10 @@ previewContainer.style.flexDirection = 'column'; previewContainer.style.alignItems = 'center'; previewContainer.style.justifyContent = 'center'; - previewContainer.style.gap = compact ? '4px' : '5px'; + 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 ? '10px 11px' : '11px 13px'; + previewContainer.style.padding = height <= 30 ? '9px 10px' : '10px 12px'; svg.innerHTML = ''; JsBarcode(svg, label.sku, getDatedBarcodeOptions(label.sku, width, height)); @@ -625,13 +625,13 @@ } .label-container:last-child { page-break-after: avoid; } .dated-label { - padding: 0.95mm 1.45mm 1mm; - gap: 0.3mm; + padding: 0.78mm 1.25mm 0.8mm; + gap: 0.2mm; } .label-name { width: 100%; max-width: 100%; - line-height: 1.02; + line-height: 1; } .label-name-ar, .label-name-en { @@ -642,11 +642,11 @@ } .label-name-ar { font-weight: 700; - font-size: 11.5px; + font-size: 13px; direction: rtl; } .label-name-en { - font-size: 9.5px; + font-size: 10.6px; font-weight: 600; } .label-meta { @@ -655,8 +655,8 @@ flex-direction: column; align-items: center; justify-content: center; - gap: 0.25mm; - margin-top: 0.1mm; + gap: 0.15mm; + margin-top: 0; } .label-dates { --label-date-row-gap: 1.2mm; @@ -667,7 +667,7 @@ align-items: center; text-align: center; direction: ltr; - font-size: 9px; + font-size: 10.1px; font-weight: 600; line-height: 1; } @@ -687,31 +687,31 @@ font-weight: 700; } .label-price { - font-size: 10px; + font-size: 11.2px; font-weight: 700; line-height: 1; white-space: nowrap; } .label-compact { - padding: 0.75mm 1.05mm 0.8mm; - gap: 0.25mm; + padding: 0.58mm 0.95mm 0.6mm; + gap: 0.15mm; } - .label-compact .label-name-ar { font-size: 10px; } - .label-compact .label-name-en { font-size: 8.3px; } - .label-compact .label-meta { gap: 0.2mm; } + .label-compact .label-name-ar { font-size: 11.1px; } + .label-compact .label-name-en { font-size: 9.1px; } + .label-compact .label-meta { gap: 0.12mm; } .label-compact .label-dates { --label-date-row-gap: 0.85mm; --label-date-item-gap: 0.35mm; - font-size: 7.8px; + font-size: 8.7px; } - .label-compact .label-price { font-size: 8.8px; } + .label-compact .label-price { font-size: 9.7px; } .barcode-wrap { width: 100%; display: flex; align-items: center; justify-content: center; - padding: 0 0.7mm; - margin: 0.15mm 0; + padding: 0 0.55mm; + margin: 0.08mm 0; overflow: visible; } .barcode-wrap svg { diff --git a/tmp_barcode_pos_script_check.js b/tmp_barcode_pos_script_check.js new file mode 100644 index 0000000..5a3cb37 --- /dev/null +++ b/tmp_barcode_pos_script_check.js @@ -0,0 +1,843 @@ + 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 ` + + + + + + `; + }).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 / عميل عابر'} +
+
+
${item.name_en} / ${item.name_ar}
${item.quantity} x ${parseFloat(item.unit_price).toFixed(3)}
${vatAmount.toFixed(2)}${itemTotal.toFixed(3)}
+ + + + + + + + + ${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(); + } +