Autosave: 20260419-133057
This commit is contained in:
parent
6e3e0c556a
commit
c4d7baf9d6
123
print_labels.php
123
print_labels.php
@ -8,6 +8,7 @@ $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']];
|
||||
}
|
||||
@ -23,8 +24,63 @@ if (!empty($skus)) {
|
||||
}
|
||||
}
|
||||
|
||||
$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',
|
||||
],
|
||||
'avery_65' => [
|
||||
'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,
|
||||
@ -35,6 +91,8 @@ $templates = [
|
||||
'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)',
|
||||
@ -46,6 +104,8 @@ $templates = [
|
||||
'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)',
|
||||
@ -57,12 +117,23 @@ $templates = [
|
||||
'margin_left' => '5mm',
|
||||
'gap_x' => '2mm',
|
||||
'gap_y' => '0mm',
|
||||
'page_width' => '210mm',
|
||||
'page_height' => '297mm',
|
||||
]
|
||||
];
|
||||
|
||||
$templateId = $_REQUEST['template'] ?? 'avery_65';
|
||||
$tpl = $templates[$templateId] ?? $templates['avery_65'];
|
||||
|
||||
$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)) {
|
||||
@ -85,6 +156,8 @@ if (!empty($items)) {
|
||||
}
|
||||
}
|
||||
|
||||
$isSinglePrint = isset($_GET['sku']) && count($skus) === 1;
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= current_lang() ?>" dir="<?= current_lang() === 'ar' ? 'rtl' : 'ltr' ?>">
|
||||
@ -99,8 +172,8 @@ if (!empty($items)) {
|
||||
.print-only { display: none; }
|
||||
|
||||
.page {
|
||||
width: 210mm;
|
||||
min-height: 297mm;
|
||||
width: <?= $pageWidth ?>;
|
||||
height: <?= $pageHeight ?>;
|
||||
padding-top: <?= $tpl['margin_top'] ?>;
|
||||
padding-left: <?= $tpl['margin_left'] ?>;
|
||||
padding-right: <?= $tpl['margin_left'] ?>;
|
||||
@ -116,6 +189,7 @@ if (!empty($items)) {
|
||||
row-gap: <?= $tpl['gap_y'] ?>;
|
||||
box-sizing: border-box;
|
||||
page-break-after: always;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.label-item {
|
||||
@ -146,16 +220,21 @@ if (!empty($items)) {
|
||||
margin: 0;
|
||||
border: initial;
|
||||
border-radius: initial;
|
||||
width: 210mm;
|
||||
min-height: 297mm;
|
||||
width: <?= $pageWidth ?>;
|
||||
height: <?= $pageHeight ?>;
|
||||
box-shadow: initial;
|
||||
background: initial;
|
||||
page-break-after: always;
|
||||
overflow: hidden;
|
||||
}
|
||||
.page:last-of-type {
|
||||
page-break-after: auto;
|
||||
}
|
||||
.label-item { border: none; }
|
||||
@page { size: A4 portrait; margin: 0; }
|
||||
@page { size: <?= $pageCssSize ?>; margin: 0; }
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -171,13 +250,15 @@ if (!empty($items)) {
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold"><?= h(tr('قالب الملصقات', 'Label Template')) ?></label>
|
||||
<select name="template" class="form-select" onchange="this.form.submit()">
|
||||
<?php foreach($templates as $key => $t): ?>
|
||||
<option value="<?= $key ?>" <?= $templateId === $key ? 'selected' : '' ?>><?= $t['name'] ?></option>
|
||||
<?php foreach($templates as $key => $t):
|
||||
?><option value="<?= $key ?>" <?= $templateId === $key ? 'selected' : '' ?>><?= $t['name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-8 text-end">
|
||||
<button type="button" class="btn btn-success" onclick="window.print()">
|
||||
|
||||
|
||||
<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>
|
||||
@ -195,8 +276,8 @@ if (!empty($items)) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($items as $sku => $item): ?>
|
||||
<tr>
|
||||
<?php foreach($items as $sku => $item):
|
||||
?><tr>
|
||||
<td class="fw-bold text-secondary">
|
||||
<?= h($sku) ?>
|
||||
<input type="hidden" name="skus[]" value="<?= h($sku) ?>">
|
||||
@ -213,8 +294,8 @@ if (!empty($items)) {
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if(empty($items)): ?>
|
||||
<tr>
|
||||
<?php if(empty($items)):
|
||||
?><tr>
|
||||
<td colspan="5" class="text-center text-muted py-4">
|
||||
<i class="bi bi-inbox fs-3 d-block mb-2"></i>
|
||||
<?= h(tr('لم يتم تحديد أصناف', 'No items selected')) ?>
|
||||
@ -224,8 +305,8 @@ if (!empty($items)) {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if(!empty($items)): ?>
|
||||
<div class="mt-3">
|
||||
<?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>
|
||||
@ -245,8 +326,8 @@ if (!empty($items)) {
|
||||
foreach ($pages as $pageIdx => $pageLabels):
|
||||
?>
|
||||
<div class="page bg-white">
|
||||
<?php foreach ($pageLabels as $label): ?>
|
||||
<div class="label-item">
|
||||
<?php foreach ($pageLabels as $label):
|
||||
?><div class="label-item">
|
||||
<div class="label-name"><?= h($label['name']) ?></div>
|
||||
<div class="label-barcode">
|
||||
<svg class="barcode"
|
||||
@ -265,6 +346,12 @@ if (!empty($items)) {
|
||||
</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>
|
||||
@ -275,4 +362,4 @@ if (!empty($items)) {
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
374
print_single_label.php
Normal file
374
print_single_label.php
Normal file
@ -0,0 +1,374 @@
|
||||
<?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++) {
|
||||
$labelsToPrint[] = $items[$sku];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Default 1 per item
|
||||
foreach ($skus as $sku) {
|
||||
if (isset($items[$sku])) {
|
||||
$labelsToPrint[] = $items[$sku];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$isSinglePrint = isset($_GET['sku']) && count($skus) === 1;
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= current_lang() ?>" dir="<?= current_lang() === 'ar' ? 'rtl' : 'ltr' ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= h(tr('طباعة ملصقات', 'Print Labels')) ?></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-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-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 width="150" class="text-center"><?= h(tr('عدد الملصقات', 'Labels Count')) ?></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="number" name="qty[<?= h($sku) ?>]" class="form-control 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="5" 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-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="15"
|
||||
jsbarcode-margin="0">
|
||||
</svg>
|
||||
</div>
|
||||
<div class="label-sku"><?= h($label['sku']) ?></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>
|
||||
158
stock.php
158
stock.php
@ -5,6 +5,68 @@ $pageTitle = tr('المخزون', 'Stock');
|
||||
$activeNav = 'stock';
|
||||
$dbError = null;
|
||||
|
||||
// Handle Export CSV
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['action'] === 'export_csv') {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id FROM items ORDER BY id DESC");
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename=stock_export_' . date('Ymd_His') . '.csv');
|
||||
echo "";
|
||||
$output = fopen('php://output', 'w');
|
||||
fputcsv($output, ['SKU', 'Name', 'Price', 'Cost Price', 'Stock', 'VAT', 'Category ID', 'Supplier ID', 'Unit ID']);
|
||||
foreach ($items as $row) { fputcsv($output, $row); }
|
||||
fclose($output);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle Import CSV
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'import_csv') {
|
||||
if (isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] === UPLOAD_ERR_OK) {
|
||||
$pdo = db();
|
||||
$file = fopen($_FILES['csv_file']['tmp_name'], 'r');
|
||||
$bom = fread($file, 3);
|
||||
if ($bom !== "") rewind($file);
|
||||
$header = fgetcsv($file);
|
||||
$imported = 0; $updated = 0;
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$stmtInsert = $pdo->prepare("INSERT INTO items (sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmtUpdate = $pdo->prepare("UPDATE items SET name=?, price=?, cost_price=?, base_stock=?, vat=?, category_id=?, supplier_id=?, unit_id=? WHERE sku=?");
|
||||
$stmtCheck = $pdo->prepare("SELECT id FROM items WHERE sku=?");
|
||||
while (($row = fgetcsv($file)) !== false) {
|
||||
if (count($row) < 5) continue;
|
||||
$sku = trim($row[0]); $name = trim($row[1]);
|
||||
if ($sku === '' || $name === '') continue;
|
||||
$price = (float)($row[2] ?? 0);
|
||||
$cost_price = (float)($row[3] ?? 0);
|
||||
$base_stock = (int)($row[4] ?? 0);
|
||||
$vat = (float)($row[5] ?? 5);
|
||||
$category_id = !empty($row[6]) ? (int)$row[6] : null;
|
||||
$supplier_id = !empty($row[7]) ? (int)$row[7] : null;
|
||||
$unit_id = !empty($row[8]) ? (int)$row[8] : null;
|
||||
$stmtCheck->execute([$sku]);
|
||||
if ($stmtCheck->fetchColumn()) {
|
||||
$stmtUpdate->execute([$name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $sku]);
|
||||
$updated++;
|
||||
} else {
|
||||
$stmtInsert->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id]);
|
||||
$imported++;
|
||||
}
|
||||
}
|
||||
$pdo->commit();
|
||||
header('Location: stock.php?import_success=1&imported='.$imported.'&updated='.$updated);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
header('Location: stock.php?import_error='.urlencode($e->getMessage()));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
header('Location: stock.php?import_error=No+file');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle AJAX actions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
header('Content-Type: application/json');
|
||||
@ -144,20 +206,37 @@ require __DIR__ . '/includes/header.php';
|
||||
|
||||
<section class="surface-card mb-4">
|
||||
<div class="row g-4 align-items-center mb-3">
|
||||
<div class="col-lg-8">
|
||||
<div class="col-lg-5">
|
||||
<h3 class="h5 mb-1"><i class="bi bi-box-seam me-2"></i><?= h(tr('قائمة الأصناف والمخزون', 'Items & Stock List')) ?></h3>
|
||||
<p class="text-muted mb-0"><?= h(tr('إدارة الأصناف وجرد المخزون.', 'Manage items and inventory.')) ?></p>
|
||||
</div>
|
||||
<div class="col-lg-4 text-lg-end">
|
||||
<button type="button" class="btn btn-secondary shadow-sm me-2" onclick="printBulkLabels()">
|
||||
<i class="bi bi-upc-scan"></i> <?= h(tr('طباعة ملصقات', 'Print Labels')) ?>
|
||||
<div class="col-lg-7 text-lg-end">
|
||||
<a href="stock.php?action=export_csv" class="btn btn-success shadow-sm me-1 mb-1" title="<?= h(tr('تصدير إكسل/CSV', 'Export Excel/CSV')) ?>">
|
||||
<i class="bi bi-file-earmark-excel"></i> <span class="d-none d-md-inline"><?= h(tr('تصدير', 'Export')) ?></span>
|
||||
</a>
|
||||
<button type="button" class="btn btn-info text-white shadow-sm me-1 mb-1" onclick="openImportModal()" title="<?= h(tr('استيراد إكسل/CSV', 'Import Excel/CSV')) ?>">
|
||||
<i class="bi bi-file-earmark-arrow-up"></i> <span class="d-none d-md-inline"><?= h(tr('استيراد', 'Import')) ?></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary shadow-sm" onclick="openItemModal()">
|
||||
<i class="bi bi-plus-lg"></i> <?= h(tr('إضافة صنف', 'Add Item')) ?>
|
||||
<button type="button" class="btn btn-secondary shadow-sm me-1 mb-1" onclick="printBulkLabels()">
|
||||
<i class="bi bi-upc-scan"></i> <span class="d-none d-md-inline"><?= h(tr('ملصقات', 'Labels')) ?></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary shadow-sm mb-1" onclick="openItemModal()">
|
||||
<i class="bi bi-plus-lg"></i> <span class="d-none d-md-inline"><?= h(tr('إضافة', 'Add')) ?></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<?php if (isset($_GET['import_success'])):
|
||||
$import_success_message = h(tr('تم الاستيراد بنجاح! أصناف جديدة: ', 'Import successful! New items: ')) . (int)$_GET['imported'] . h(tr('، محدثة: ', ', updated: ')) . (int)$_GET['updated'];
|
||||
echo "<div class=\"alert alert-success alert-dismissible fade show mb-0\">" . $import_success_message . "<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button></div>";
|
||||
endif; ?>
|
||||
<?php if (isset($_GET['import_error'])):
|
||||
$import_error_message = h(tr('خطأ في الاستيراد: ', 'Import error: ')) . h($_GET['import_error']);
|
||||
echo "<div class=\"alert alert-danger alert-dismissible fade show mb-0\">" . $import_error_message . "<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button></div>";
|
||||
endif; ?>
|
||||
</div>
|
||||
|
||||
<form class="mb-4" method="GET" action="stock.php">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
@ -252,9 +331,9 @@ require __DIR__ . '/includes/header.php';
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="openItemModal('<?= h($row['sku']) ?>', '<?= h(addslashes($row['name'])) ?>', '<?= h($row['price']) ?>', '<?= h($row['cost_price'] ?? 0) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? get_setting('vat_percentage', 5)) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['unit_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>')" data-bs-toggle="tooltip" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<a href="print_labels.php?sku=<?= urlencode($row['sku']) ?>" class="btn btn-sm btn-outline-secondary rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0; line-height: 32px;" data-bs-toggle="tooltip" title="<?= h(tr('طباعة ملصق', 'Print Label')) ?>">
|
||||
<button type="button" onclick="printSingleLabel('<?= h(addslashes($row['sku'])) ?>')" class="btn btn-sm btn-outline-secondary rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" data-bs-toggle="tooltip" title="<?= h(tr('طباعة ملصق', 'Print Label')) ?>">
|
||||
<i class="bi bi-printer"></i>
|
||||
</a>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="deleteItem('<?= h(addslashes($row['sku'])) ?>')" data-bs-toggle="tooltip" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
@ -279,6 +358,38 @@ require __DIR__ . '/includes/header.php';
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<!-- Import Modal -->
|
||||
<div class="modal fade" id="importModal" tabindex="-1" aria-labelledby="importModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="stock.php" method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="action" value="import_csv">
|
||||
<div class="modal-header bg-info text-white">
|
||||
<h5 class="modal-title" id="importModalLabel"><i class="bi bi-file-earmark-arrow-up me-2"></i><?= h(tr('استيراد من إكسل / CSV', 'Import from Excel / CSV')) ?></h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning small">
|
||||
<?= h(tr('ملاحظة: يدعم النظام ملفات CSV فقط. يرجى حفظ ملف Excel بصيغة (CSV UTF-8).', 'Note: The system supports CSV files only. Please save your Excel file as (CSV UTF-8).')) ?>
|
||||
</div>
|
||||
<p class="text-muted small mb-2">
|
||||
<?= h(tr('يجب أن يحتوي الملف على الأعمدة التالية بالترتيب الدقيق:', 'The file must contain exactly these columns in order:')) ?><br>
|
||||
<strong>SKU, Name, Price, Cost Price, Stock, VAT, Category ID, Supplier ID, Unit ID</strong>
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('ملف الإكسل / CSV', 'Excel / CSV File')) ?></label>
|
||||
<input type="file" class="form-control" name="csv_file" accept=".csv" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= h(tr('إلغاء', 'Cancel')) ?></button>
|
||||
<button type="submit" class="btn btn-info text-white"><?= h(tr('استيراد', 'Import')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Item Modal -->
|
||||
<div class="modal fade" id="itemModal" tabindex="-1" aria-labelledby="itemModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
@ -433,6 +544,11 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
});
|
||||
|
||||
function openImportModal() {
|
||||
var m = new bootstrap.Modal(document.getElementById('importModal'));
|
||||
m.show();
|
||||
}
|
||||
|
||||
function openItemModal(sku = '', name = '', price = '', cost_price = '', base_stock = '', vat = '<?= h(get_setting('vat_percentage', 5)) ?>', category_id = '', supplier_id = '', unit_id = '', image_url = '') {
|
||||
document.getElementById('item_original_sku').value = sku;
|
||||
document.getElementById('item_existing_image_url').value = image_url;
|
||||
@ -556,26 +672,34 @@ function toggleAllCheckboxes(source) {
|
||||
function printBulkLabels() {
|
||||
const checkboxes = document.querySelectorAll(".item-checkbox:checked");
|
||||
if (checkboxes.length === 0) {
|
||||
Swal.fire('<?= h(tr("تنبيه", "Warning")) ?>', '<?= h(tr("يرجى تحديد صنف واحد على الأقل", "Please select at least one item")) ?>', 'warninginaly);
|
||||
Swal.fire('<?= h(tr("تنبيه", "Warning")) ?>', '<?= h(tr("يرجى تحديد صنف واحد على الأقل", "Please select at least one item")) ?>', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
let skus = [];
|
||||
checkboxes.forEach((cb) => skus.push(cb.value));
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = 'print_labels.php';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'skus';
|
||||
input.value = skus.join(\",\");
|
||||
checkboxes.forEach((cb) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'skus[]';
|
||||
input.value = cb.value;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
form.appendChild(input);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function printSingleLabel(sku) {
|
||||
const checked = document.querySelectorAll(".item-checkbox:checked");
|
||||
if (checked.length > 1) {
|
||||
printBulkLabels();
|
||||
return;
|
||||
}
|
||||
window.location.href = 'print_single_label.php?sku=' + encodeURIComponent(sku);
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require __DIR__ . "/includes/footer.php"; ?>
|
||||
Loading…
x
Reference in New Issue
Block a user