add date barcode

This commit is contained in:
Flatlogic Bot 2026-05-06 12:44:38 +00:00
parent 8fccefd923
commit d52dfbe9df
2 changed files with 387 additions and 5 deletions

View File

@ -7277,11 +7277,20 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
</td>
<td><?= !empty($item['expiry_date']) ? htmlspecialchars((string)$item['expiry_date']) : '---' ?></td>
<td><?= number_format((float)$item['vat_rate'], 2) ?>%</td>
<?php
$itemBarcodePrice = number_format((float)$item['sale_price'] * (1 + (float)($item['vat_rate'] ?? 0) / 100), 3);
$itemSkuJs = htmlspecialchars(json_encode((string)($item['sku'] ?? ''), JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
$itemNameArJs = htmlspecialchars(json_encode((string)($item['name_ar'] ?? ''), JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
$itemNameEnJs = htmlspecialchars(json_encode((string)($item['name_en'] ?? ''), JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
$itemBarcodePriceJs = htmlspecialchars(json_encode((string)$itemBarcodePrice, JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
$itemExpiryDateJs = htmlspecialchars(json_encode(!empty($item['expiry_date']) ? (string)$item['expiry_date'] : '', JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
?>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info" title="View" data-bs-toggle="modal" data-bs-target="#viewItemModal<?= $item['id'] ?>"><i class="bi bi-eye"></i></button>
<button class="btn btn-outline-primary" title="Edit" data-bs-toggle="modal" data-bs-target="#editItemModal<?= $item['id'] ?>"><i class="bi bi-pencil"></i></button>
<button class="btn btn-outline-dark" title="Barcode" onclick="printItemBarcode('<?= htmlspecialchars($item['sku']) ?>', '<?= htmlspecialchars($item['name_ar']) ?>', '<?= htmlspecialchars($item['name_en']) ?>', '<?= number_format((float)$item['sale_price'] * (1 + (float)($item['vat_rate'] ?? 0) / 100), 3) ?>')"><i class="bi bi-upc"></i></button>
<button class="btn btn-outline-dark" title="Barcode" onclick="printItemBarcode(<?= $itemSkuJs ?>, <?= $itemNameArJs ?>, <?= $itemNameEnJs ?>, <?= $itemBarcodePriceJs ?>)"><i class="bi bi-upc"></i></button>
<button class="btn btn-outline-secondary" title="Barcode + Dates" onclick="printItemBarcodeWithDates(<?= $itemSkuJs ?>, <?= $itemNameArJs ?>, <?= $itemNameEnJs ?>, <?= $itemExpiryDateJs ?>)"><i class="bi bi-calendar-date"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $item['id'] ?>">
<button type="submit" name="delete_item" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
@ -7318,7 +7327,8 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
<div class="modal-footer">
<button class="btn btn-outline-dark" onclick="printItemBarcode('<?= htmlspecialchars($item['sku']) ?>', '<?= htmlspecialchars($item['name_ar']) ?>', '<?= htmlspecialchars($item['name_en']) ?>', '<?= number_format((float)$item['sale_price'] * (1 + (float)($item['vat_rate'] ?? 0) / 100), 3) ?>')"><i class="bi bi-printer"></i> Print Barcode</button>
<button class="btn btn-outline-dark" onclick="printItemBarcode(<?= $itemSkuJs ?>, <?= $itemNameArJs ?>, <?= $itemNameEnJs ?>, <?= $itemBarcodePriceJs ?>)"><i class="bi bi-printer"></i> Print Barcode</button>
<button class="btn btn-outline-secondary" onclick="printItemBarcodeWithDates(<?= $itemSkuJs ?>, <?= $itemNameArJs ?>, <?= $itemNameEnJs ?>, <?= $itemExpiryDateJs ?>)"><i class="bi bi-calendar-date"></i> Barcode + Dates</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
</div>
</div>
@ -12223,6 +12233,54 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</div>
<!-- Barcode + Dates Print Modal -->
<div class="modal fade" id="datedBarcodePrintModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header">
<h5 class="modal-title">Print Barcode + Dates Label</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<div id="datedBarcodeContainer" class="p-3 bg-white border mb-3 mx-auto" style="width: fit-content; max-width: 100%;">
<div id="datedBarcodeLabelName" class="fw-bold small mb-1"></div>
<svg id="datedBarcodeSvg" style="max-width: 100%; height: auto;"></svg>
<div id="datedBarcodeLabelDates" class="small mt-2 text-start mx-auto" style="width: fit-content; min-width: 145px;"></div>
</div>
<div class="row g-2 mb-3 text-start">
<div class="col-6">
<label class="form-label small" data-en="Production Date" data-ar="تاريخ الإنتاج">Production Date</label>
<input type="date" id="datedBarcodeProductionDate" class="form-control form-control-sm">
</div>
<div class="col-6">
<label class="form-label small" data-en="Expiry Date" data-ar="تاريخ الانتهاء">Expiry Date</label>
<input type="date" id="datedBarcodeExpiryDate" class="form-control form-control-sm">
</div>
</div>
<div class="mb-3">
<label class="form-label small">Number of Labels</label>
<input type="number" id="datedBarcodeQty" class="form-control form-control-sm mx-auto" value="1" min="1" style="width: 80px;">
</div>
<div class="row mb-2 mx-auto" style="max-width: 200px;">
<div class="col-6">
<label class="form-label small">Width (mm)</label>
<input type="number" id="datedBarcodeWidth" class="form-control form-control-sm" value="50" min="10">
</div>
<div class="col-6">
<label class="form-label small">Height (mm)</label>
<input type="number" id="datedBarcodeHeight" class="form-control form-control-sm" value="35" min="10">
</div>
</div>
<div class="form-text text-center mb-1">For barcode labels with P / E dates, 50 × 35 mm or larger is recommended.</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
<button type="button" class="btn btn-primary" onclick="executeDatedBarcodePrint()"><i class="bi bi-printer me-2"></i>Print Now</button>
</div>
</div>
</div>
</div>
<!-- Avery Labels Modal -->
<div class="modal fade" id="averyLabelsModal" tabindex="-1">
<div class="modal-dialog modal-xl">

View File

@ -18,6 +18,19 @@
};
}
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;
@ -77,10 +90,14 @@
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.style.width = '100%';
svg.style.width = Math.round(scale * 100) + '%';
svg.style.maxWidth = '100%';
svg.style.height = 'auto';
svg.style.display = 'block';
svg.style.margin = '0 auto';
return svg.outerHTML;
}
@ -93,6 +110,7 @@
const dimensions = getSingleBarcodePrintDimensions();
const width = dimensions.width;
const height = dimensions.height;
const scale = getSingleBarcodeScale(width, height);
const nameContainer = document.getElementById('barcodeLabelName');
const priceContainer = document.getElementById('barcodeLabelPrice');
const previewContainer = document.getElementById('barcodeContainer');
@ -104,15 +122,18 @@
nameContainer.innerHTML = buildSingleBarcodeNameHtml(label.nameAr, label.nameEn);
priceContainer.textContent = label.price ? 'OMR ' + label.price : '';
previewContainer.style.width = Math.min(Math.max(width * 4, 180), 320) + 'px';
previewContainer.style.width = Math.min(Math.max(width * 3.6, 170), 280) + 'px';
previewContainer.style.maxWidth = '100%';
previewContainer.style.padding = height <= 25 ? '12px' : '16px';
svg.innerHTML = '';
JsBarcode(svg, label.sku, getSingleBarcodeOptions(label.sku, width, height));
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
svg.style.width = '100%';
svg.style.width = Math.round(scale * 100) + '%';
svg.style.maxWidth = '100%';
svg.style.height = 'auto';
svg.style.display = 'block';
svg.style.margin = '0 auto';
}
function initSingleBarcodePreviewControls() {
@ -272,6 +293,309 @@
}, 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 `
<div class="label-date-row"><span class="label-date-key">P:</span><span class="label-date-value">${escapeBarcodeLabelHtml(productionText)}</span></div>
<div class="label-date-row"><span class="label-date-key">E:</span><span class="label-date-value">${escapeBarcodeLabelHtml(expiryText)}</span></div>
`;
}
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.88;
}
if (labelWidthMm <= 40 || labelHeightMm <= 30) {
return 0.84;
}
if (labelWidthMm <= 50 || labelHeightMm <= 35) {
return 0.80;
}
return 0.76;
}
function getDatedBarcodeOptions(sku, labelWidthMm, labelHeightMm) {
const baseOptions = getSingleBarcodeOptions(sku, labelWidthMm, labelHeightMm);
const skuLength = String(sku || '').length;
const showValue = labelHeightMm >= 42 && labelWidthMm >= 50 && skuLength <= 16;
return {
...baseOptions,
displayValue: showValue,
fontSize: showValue ? 9 : 8,
textMargin: showValue ? 2 : 0,
height: Math.max(Math.round(baseOptions.height * (showValue ? 0.78 : 0.86)), 24),
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.style.width = Math.round(scale * 100) + '%';
svg.style.maxWidth = '100%';
svg.style.height = 'auto';
svg.style.display = 'block';
svg.style.margin = '0 auto';
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 nameContainer = document.getElementById('datedBarcodeLabelName');
const datesContainer = document.getElementById('datedBarcodeLabelDates');
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);
datesContainer.innerHTML = buildDatedBarcodeDateRowsHtml(
productionInput ? productionInput.value : '',
expiryInput ? expiryInput.value : ''
);
previewContainer.style.width = Math.min(Math.max(width * 3.8, 180), 300) + 'px';
previewContainer.style.maxWidth = '100%';
previewContainer.style.padding = height <= 30 ? '10px 12px' : '12px 14px';
svg.innerHTML = '';
JsBarcode(svg, label.sku, getDatedBarcodeOptions(label.sku, width, height));
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
svg.style.width = Math.round(scale * 100) + '%';
svg.style.maxWidth = '100%';
svg.style.height = 'auto';
svg.style.display = 'block';
svg.style.margin = '0 auto';
}
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, defaultExpiryDate) {
if (!sku) {
Swal.fire('Error', 'This item has no SKU/Barcode assigned.', 'error');
return;
}
window.currentDatedBarcodeLabel = {
sku: String(sku),
nameAr: nameAr || '',
nameEn: nameEn || ''
};
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 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 += `
<div class="label-container dated-label ${compactClass}">
${nameHtml ? `<div class="label-name">${nameHtml}</div>` : ''}
<div class="barcode-wrap">${svg}</div>
<div class="label-dates">${dateHtml}</div>
</div>
`;
}
doc.open();
doc.write(`
<html>
<head>
<style>
@page { size: ${width}mm ${height}mm; margin: 0; }
body { margin: 0; padding: 0; font-family: sans-serif; }
.label-container {
width: ${width}mm;
height: ${height}mm;
page-break-after: always;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
text-align: center;
overflow: hidden;
box-sizing: border-box;
}
.label-container:last-child { page-break-after: avoid; }
.dated-label {
padding: 1.2mm 2mm;
gap: 0.6mm;
}
.label-name {
width: 100%;
max-width: 100%;
line-height: 1.05;
}
.label-name-ar,
.label-name-en {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.label-name-ar {
font-weight: 700;
font-size: 10px;
direction: rtl;
}
.label-name-en { font-size: 8px; }
.label-dates {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.35mm;
font-size: 8px;
line-height: 1.05;
}
.label-date-row {
display: flex;
align-items: center;
gap: 1mm;
white-space: nowrap;
}
.label-date-key {
font-weight: 700;
}
.label-compact {
padding: 0.9mm 1.4mm;
gap: 0.45mm;
}
.label-compact .label-name-ar { font-size: 9px; }
.label-compact .label-name-en { font-size: 7px; }
.label-compact .label-dates { font-size: 7px; }
.barcode-wrap {
width: 100%;
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0 1.2mm;
overflow: hidden;
}
.barcode-wrap svg {
width: 100%;
height: auto;
display: block;
overflow: visible;
}
</style>
</head>
<body>
${labelsHtml}
</body>
</html>
`);
doc.close();
iframe.contentWindow.focus();
setTimeout(() => {
iframe.contentWindow.print();
setTimeout(() => {
document.body.removeChild(iframe);
}, 2000);
}, 500);
};
<?php require 'pages/sales_purchases_print_script.php'; ?>
window.printPosReceiptFromInvoice = function(inv) {