394 lines
17 KiB
PHP
394 lines
17 KiB
PHP
<?php
|
|
require_once __DIR__ . '/includes/app.php';
|
|
require_permission('stock', 'show');
|
|
|
|
$pdo = db();
|
|
$items = [];
|
|
|
|
$skus = [];
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['skus'])) {
|
|
$skus = is_array($_POST['skus']) ? $_POST['skus'] : explode(',', $_POST['skus']);
|
|
$skus = array_values(array_filter(array_map('trim', $skus)));
|
|
} elseif (!empty($_GET['sku'])) {
|
|
$skus = [$_GET['sku']];
|
|
}
|
|
|
|
if (!empty($skus)) {
|
|
$placeholders = str_repeat('?,', count($skus) - 1) . '?';
|
|
$stmt = $pdo->prepare("SELECT * FROM items WHERE sku IN ($placeholders)");
|
|
$stmt->execute($skus);
|
|
$results = $stmt->fetchAll();
|
|
// Index by SKU
|
|
foreach ($results as $row) {
|
|
$items[$row['sku']] = $row;
|
|
}
|
|
}
|
|
|
|
$customWidth = $_REQUEST['custom_width'] ?? '40';
|
|
$customHeight = $_REQUEST['custom_height'] ?? '25';
|
|
|
|
// Templates configuration
|
|
$templates = [
|
|
'custom_roll' => [
|
|
'name' => 'Custom Size / مقاس مخصص (أدخل المقاس أدناه)',
|
|
'cols' => 1,
|
|
'rows' => 1,
|
|
'width' => $customWidth . 'mm',
|
|
'height' => $customHeight . 'mm',
|
|
'margin_top' => '0mm',
|
|
'margin_left' => '0mm',
|
|
'gap_x' => '0mm',
|
|
'gap_y' => '0mm',
|
|
'page_width' => $customWidth . 'mm',
|
|
'page_height' => $customHeight . 'mm',
|
|
],
|
|
'roll_40x25' => [
|
|
'name' => 'Roll / Zebra (40 x 25 mm)',
|
|
'cols' => 1,
|
|
'rows' => 1,
|
|
'width' => '40mm',
|
|
'height' => '25mm',
|
|
'margin_top' => '0mm',
|
|
'margin_left' => '0mm',
|
|
'gap_x' => '0mm',
|
|
'gap_y' => '0mm',
|
|
'page_width' => '40mm',
|
|
'page_height' => '25mm',
|
|
],
|
|
'roll_25x40' => [
|
|
'name' => 'Roll / Zebra (25 x 40 mm)',
|
|
'cols' => 1,
|
|
'rows' => 1,
|
|
'width' => '25mm',
|
|
'height' => '40mm',
|
|
'margin_top' => '0mm',
|
|
'margin_left' => '0mm',
|
|
'gap_x' => '0mm',
|
|
'gap_y' => '0mm',
|
|
'page_width' => '25mm',
|
|
'page_height' => '40mm',
|
|
],
|
|
'roll_38x25' => [
|
|
'name' => 'Roll / Zebra (38 x 25 mm)',
|
|
'cols' => 1,
|
|
'rows' => 1,
|
|
'width' => '38mm',
|
|
'height' => '25mm',
|
|
'margin_top' => '0mm',
|
|
'margin_left' => '0mm',
|
|
'gap_x' => '0mm',
|
|
'gap_y' => '0mm',
|
|
'page_width' => '38mm',
|
|
'page_height' => '25mm',
|
|
],
|
|
'avery_65' => [
|
|
'name' => 'Avery L7651 - 65 Labels (38.1 x 21.2 mm)',
|
|
'cols' => 5,
|
|
'rows' => 13,
|
|
'width' => '38.1mm',
|
|
'height' => '21.2mm',
|
|
'margin_top' => '10.5mm',
|
|
'margin_left' => '4.7mm',
|
|
'gap_x' => '2.5mm',
|
|
'gap_y' => '0mm',
|
|
'page_width' => '210mm',
|
|
'page_height' => '297mm',
|
|
],
|
|
'avery_54' => [
|
|
'name' => 'Avery 54 Labels (32 x 25.4 mm)',
|
|
'cols' => 6,
|
|
'rows' => 9,
|
|
'width' => '32mm',
|
|
'height' => '25.4mm',
|
|
'margin_top' => '10mm',
|
|
'margin_left' => '5mm',
|
|
'gap_x' => '2mm',
|
|
'gap_y' => '0mm',
|
|
'page_width' => '210mm',
|
|
'page_height' => '297mm',
|
|
],
|
|
'avery_45' => [
|
|
'name' => 'Avery 45 Labels (38.1 x 29.6 mm)',
|
|
'cols' => 5,
|
|
'rows' => 9,
|
|
'width' => '38.1mm',
|
|
'height' => '29.6mm',
|
|
'margin_top' => '15mm',
|
|
'margin_left' => '5mm',
|
|
'gap_x' => '2mm',
|
|
'gap_y' => '0mm',
|
|
'page_width' => '210mm',
|
|
'page_height' => '297mm',
|
|
]
|
|
];
|
|
|
|
$templateId = $_REQUEST['template'] ?? 'custom_roll';
|
|
$tpl = $templates[$templateId] ?? $templates['custom_roll'];
|
|
|
|
$pageWidth = $tpl['page_width'] ?? '210mm';
|
|
$pageHeight = $tpl['page_height'] ?? '297mm';
|
|
$pageCssSize = (isset($tpl['page_width']) && $tpl['page_width'] !== '210mm') ? "{$tpl['page_width']} {$tpl['page_height']}" : "A4 portrait";
|
|
|
|
if ($templateId !== 'custom_roll') {
|
|
$customWidth = floatval(str_replace('mm', '', $tpl['width']));
|
|
$customHeight = floatval(str_replace('mm', '', $tpl['height']));
|
|
}
|
|
|
|
// Prepare labels to print
|
|
$labelsToPrint = [];
|
|
if (!empty($items)) {
|
|
// Check if quantities are posted
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['qty'])) {
|
|
foreach ($_POST['qty'] as $sku => $qty) {
|
|
if (isset($items[$sku])) {
|
|
for ($i = 0; $i < (int)$qty; $i++) {
|
|
$itemData = $items[$sku];
|
|
$itemData['prod_date'] = $_POST['prod_date'][$sku] ?? date('Y-m-d');
|
|
$itemData['exp_date'] = $_POST['exp_date'][$sku] ?? date('Y-m-d', strtotime('+1 year'));
|
|
$labelsToPrint[] = $itemData;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Default 1 per item
|
|
foreach ($skus as $sku) {
|
|
if (isset($items[$sku])) {
|
|
$itemData = $items[$sku];
|
|
$itemData['prod_date'] = date('Y-m-d');
|
|
$itemData['exp_date'] = date('Y-m-d', strtotime('+1 year'));
|
|
$labelsToPrint[] = $itemData;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$isSinglePrint = isset($_GET['sku']) && count($skus) === 1;
|
|
$companyName = current_lang() === 'ar' ? get_setting('company_name_ar', 'حلوى الريامي') : get_setting('company_name_en', 'Al Riyami Sweets');
|
|
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="<?= current_lang() ?>" dir="<?= current_lang() === 'ar' ? 'rtl' : 'ltr' ?>">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title><?= h(tr('طباعة ملصقات مع التواريخ', 'Print Labels with Dates')) ?></title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
|
|
<style>
|
|
body { background: #f0f2f5; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
|
|
.no-print { display: block; }
|
|
.print-only { display: none; }
|
|
|
|
.page {
|
|
width: <?= $pageWidth ?>;
|
|
height: <?= $pageHeight ?>;
|
|
padding-top: <?= $tpl['margin_top'] ?>;
|
|
padding-left: <?= $tpl['margin_left'] ?>;
|
|
padding-right: <?= $tpl['margin_left'] ?>;
|
|
margin: 10mm auto;
|
|
border: 1px solid #D3D3D3;
|
|
border-radius: 5px;
|
|
background: white;
|
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
|
display: grid;
|
|
grid-template-columns: repeat(<?= $tpl['cols'] ?>, <?= $tpl['width'] ?>);
|
|
grid-auto-rows: <?= $tpl['height'] ?>;
|
|
column-gap: <?= $tpl['gap_x'] ?>;
|
|
row-gap: <?= $tpl['gap_y'] ?>;
|
|
box-sizing: border-box;
|
|
page-break-after: always;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.label-item {
|
|
width: <?= $tpl['width'] ?>;
|
|
height: <?= $tpl['height'] ?>;
|
|
border: 1px dashed #ccc;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overflow: hidden;
|
|
box-sizing: border-box;
|
|
padding: 2px;
|
|
text-align: center;
|
|
}
|
|
|
|
.label-company { font-size: 9px; font-weight: bold; margin-bottom: 1px; text-transform: uppercase; }
|
|
.label-name { font-size: 9px; font-weight: bold; line-height: 1.1; margin-bottom: 2px; max-height: 20px; overflow: hidden; }
|
|
.label-price { font-size: 10px; font-weight: bold; margin-top: 1px; }
|
|
.label-sku { font-size: 8px; color: #555; }
|
|
.label-dates { font-size: 6.5px; font-weight: bold; margin-top: 1px; color: #111; letter-spacing: 0.2px; }
|
|
.label-barcode { margin: 0; display: flex; align-items: center; justify-content: center; }
|
|
.label-barcode svg { width: 100%; height: 100%; max-height: 14px; }
|
|
|
|
@media print {
|
|
@page { size: <?= $pageCssSize ?>; margin: 0; }
|
|
html, body {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
background: white;
|
|
height: 100%;
|
|
}
|
|
.no-print { display: none !important; }
|
|
.print-only { display: block; }
|
|
|
|
.print-preview { margin: 0 !important; padding: 0 !important; border: none !important; }
|
|
|
|
.page {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
border: none !important;
|
|
width: <?= $pageWidth ?> !important;
|
|
height: <?= $pageHeight ?> !important;
|
|
box-shadow: none !important;
|
|
background: white !important;
|
|
page-break-after: auto !important;
|
|
page-break-inside: avoid !important;
|
|
overflow: hidden;
|
|
}
|
|
.page + .page {
|
|
page-break-before: always !important;
|
|
}
|
|
.label-item { border: none !important; margin: 0; padding: 2px; box-sizing: border-box; }
|
|
}
|
|
</style>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
|
</head>
|
|
<body>
|
|
|
|
<div class="container mt-4 no-print">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><?= h(tr('إعدادات الطباعة', 'Print Settings')) ?></h5>
|
|
<a href="stock.php" class="btn btn-sm btn-light"><?= h(tr('رجوع للمخزون', 'Back to Stock')) ?></a>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST">
|
|
<div class="row g-3 align-items-end mb-3">
|
|
<input type="hidden" name="template" value="custom_roll">
|
|
<div class="col-md-2 col-6">
|
|
<label class="form-label fw-bold"><?= h(tr('العرض', 'Width')) ?> (mm)</label>
|
|
<input type="number" name="custom_width" class="form-control" value="<?= h($customWidth) ?>" onchange="this.form.submit()" min="10">
|
|
</div>
|
|
<div class="col-md-2 col-6">
|
|
<label class="form-label fw-bold"><?= h(tr('الارتفاع', 'Height')) ?> (mm)</label>
|
|
<input type="number" name="custom_height" class="form-control" value="<?= h($customHeight) ?>" onchange="this.form.submit()" min="10">
|
|
</div>
|
|
|
|
<div class="col-md-auto text-end flex-grow-1">
|
|
<button type="button" class="btn btn-success btn-lg shadow" onclick="window.print()">
|
|
<i class="bi bi-printer"></i> <?= h(tr('طباعة الآن', 'Print Now')) ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive border rounded">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>SKU</th>
|
|
<th><?= h(tr('الصنف', 'Item')) ?></th>
|
|
<th><?= h(tr('السعر', 'Price')) ?></th>
|
|
<th><?= h(tr('تاريخ الإنتاج', 'Prod. Date')) ?></th>
|
|
<th><?= h(tr('تاريخ الانتهاء', 'Exp. Date')) ?></th>
|
|
<th width="120" class="text-center"><?= h(tr('الكمية', 'Qty')) ?></th>
|
|
<th width="80" class="text-center"><?= h(tr('إزالة', 'Remove')) ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach($items as $sku => $item):
|
|
?><tr>
|
|
<td class="fw-bold text-secondary">
|
|
<?= h($sku) ?>
|
|
<input type="hidden" name="skus[]" value="<?= h($sku) ?>">
|
|
</td>
|
|
<td><?= h($item['name']) ?></td>
|
|
<td><span class="badge bg-light text-dark border"><?= h(currency($item['price'])) ?></span></td>
|
|
<td>
|
|
<input type="date" name="prod_date[<?= h($sku) ?>]" class="form-control form-control-sm" value="<?= h($_POST['prod_date'][$sku] ?? date('Y-m-d')) ?>">
|
|
</td>
|
|
<td>
|
|
<input type="date" name="exp_date[<?= h($sku) ?>]" class="form-control form-control-sm" value="<?= h($_POST['exp_date'][$sku] ?? date('Y-m-d', strtotime('+1 year'))) ?>">
|
|
</td>
|
|
<td>
|
|
<input type="number" name="qty[<?= h($sku) ?>]" class="form-control form-control-sm text-center" value="<?= isset($_POST['qty'][$sku]) ? (int)$_POST['qty'][$sku] : 1 ?>" min="1">
|
|
</td>
|
|
<td class="text-center">
|
|
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle" onclick="this.closest('tr').remove();" title="<?= h(tr('إزالة', 'Remove')) ?>">
|
|
<i class="bi bi-x-lg"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php if(empty($items)):
|
|
?><tr>
|
|
<td colspan="7" class="text-center text-muted py-4">
|
|
<i class="bi bi-inbox fs-3 d-block mb-2"></i>
|
|
<?= h(tr('لم يتم تحديد أصناف', 'No items selected')) ?>
|
|
</td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php if(!empty($items)):
|
|
?><div class="mt-3">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-arrow-clockwise"></i> <?= h(tr('تحديث المعاينة', 'Update Preview')) ?>
|
|
</button>
|
|
</div>
|
|
<?php endif; ?>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (!empty($labelsToPrint)): ?>
|
|
<div class="print-preview mt-4">
|
|
<h5 class="text-center no-print text-muted mb-3"><i class="bi bi-view-list"></i> <?= h(tr('معاينة الطباعة', 'Print Preview')) ?></h5>
|
|
<?php
|
|
$labelsPerPage = $tpl['cols'] * $tpl['rows'];
|
|
$pages = array_chunk($labelsToPrint, $labelsPerPage);
|
|
foreach ($pages as $pageIdx => $pageLabels):
|
|
?>
|
|
<div class="page bg-white">
|
|
<?php foreach ($pageLabels as $label):
|
|
?><div class="label-item">
|
|
<div class="label-company"><?= h($companyName) ?></div>
|
|
<div class="label-name"><?= h($label['name']) ?></div>
|
|
<div class="label-barcode">
|
|
<svg class="barcode"
|
|
jsbarcode-format="code128"
|
|
jsbarcode-value="<?= h($label['sku']) ?>"
|
|
jsbarcode-displayvalue="false"
|
|
jsbarcode-width="1"
|
|
jsbarcode-height="12"
|
|
jsbarcode-margin="0">
|
|
</svg>
|
|
</div>
|
|
<div class="label-dates" dir="ltr" style="display: flex; justify-content: space-around; width: 100%;">
|
|
<span>P:<?= !empty($label['prod_date']) ? date('d/m/Y', strtotime($label['prod_date'])) : '' ?></span><span>E:<?= !empty($label['exp_date']) ? date('d/m/Y', strtotime($label['exp_date'])) : '' ?></span>
|
|
</div>
|
|
<div class="label-price"><?= h(currency($label['price'])) ?></div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<div class="text-center mt-4 mb-5 no-print">
|
|
<button type="button" class="btn btn-success btn-lg px-5 py-3 shadow-lg rounded-pill" onclick="window.print()">
|
|
<i class="bi bi-printer fs-4"></i> <?= h(tr('الطباعة الآن', 'Print Labels Now')) ?>
|
|
</button>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
if (typeof JsBarcode !== 'undefined') {
|
|
JsBarcode(".barcode").init();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|