39728-vm/stock.php
2026-04-23 03:09:14 +00:00

764 lines
37 KiB
PHP

<?php
require_once __DIR__ . '/includes/app.php';
$user = require_permission('stock', 'show');
$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 "\xEF\xBB\xBF";
$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) {
require_once __DIR__ . '/includes/SimpleXLSX.php';
$pdo = db();
$file_path = $_FILES['csv_file']['tmp_name'];
$raw_content = file_get_contents($file_path);
$rows = [];
# Check if XLSX (starts with PK)
if (str_starts_with($raw_content, 'PK')) {
if ( $xlsx = Shuchkin\SimpleXLSX::parse($file_path) ) {
$rows = $xlsx->rows();
if (count($rows) > 0) {
array_shift($rows); # Remove header
}
} else {
header('Location: stock.php?import_error=' . urlencode('خطأ في قراءة ملف الإكسل (XLSX). يرجى التأكد من أن الملف سليم.'));
exit;
}
} else {
# Treat as CSV
# Remove UTF-8 BOM if present
if (str_starts_with($raw_content, "")) {
$raw_content = substr($raw_content, 3);
}
# Fix encoding for Windows-1256 (common in Arabic Excel exports)
if (!mb_check_encoding($raw_content, 'UTF-8')) {
$raw_content = mb_convert_encoding($raw_content, 'UTF-8', 'Windows-1256');
}
# Determine delimiter by checking first line
$first_line = strtok($raw_content, "
");
$delimiter = ',';
if ($first_line !== false && substr_count($first_line, ';') > substr_count($first_line, ',')) {
$delimiter = ';';
}
$clean_file = tmpfile();
fwrite($clean_file, $raw_content);
rewind($clean_file);
$header = fgetcsv($clean_file, 0, $delimiter);
while (($row = fgetcsv($clean_file, 0, $delimiter)) !== false) {
$rows[] = $row;
}
fclose($clean_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=?");
$valid_categories = $pdo->query("SELECT id FROM categories")->fetchAll(PDO::FETCH_COLUMN);
$valid_suppliers = $pdo->query("SELECT id FROM suppliers")->fetchAll(PDO::FETCH_COLUMN);
$valid_units = $pdo->query("SELECT id FROM units")->fetchAll(PDO::FETCH_COLUMN);
foreach ($rows as $row) {
if (count($row) < 5) continue;
$sku = trim((string)$row[0]); $name = trim((string)$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]) && in_array((int)$row[6], $valid_categories)) ? (int)$row[6] : null;
$supplier_id = (!empty($row[7]) && in_array((int)$row[7], $valid_suppliers)) ? (int)$row[7] : null;
$unit_id = (!empty($row[8]) && in_array((int)$row[8], $valid_units)) ? (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');
$pdo = db();
if ($_POST['action'] === 'save') {
try {
$sku = $_POST['sku'] ?? '';
$name = $_POST['name'] ?? '';
$price = (float)($_POST['price'] ?? 0);
$cost_price = (float)($_POST['cost_price'] ?? 0);
$base_stock = (int)($_POST['base_stock'] ?? 0);
$in_catalog = isset($_POST['in_catalog']) && $_POST['in_catalog'] === '1' ? 1 : 0;
$vat = (float)($_POST['vat'] ?? get_setting('vat_percentage', 5));
$category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null;
$supplier_id = !empty($_POST['supplier_id']) ? (int)$_POST['supplier_id'] : null;
$unit_id = !empty($_POST['unit_id']) ? (int)$_POST['unit_id'] : null;
if (!$sku || !$name) {
echo json_encode(['success' => false, 'error' => 'Missing SKU or Name']);
exit;
}
$image_url = $_POST['existing_image_url'] ?? null;
if (isset($_FILES['picture']) && $_FILES['picture']['error'] === UPLOAD_ERR_OK) {
$uploadDir = __DIR__ . '/assets/images/items/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0775, true);
}
$ext = pathinfo($_FILES['picture']['name'], PATHINFO_EXTENSION);
$filename = time() . '_' . rand(1000, 9999) . '.' . $ext;
if (move_uploaded_file($_FILES['picture']['tmp_name'], $uploadDir . $filename)) {
$image_url = 'assets/images/items/' . $filename;
}
}
$stmt = $pdo->prepare('SELECT id FROM items WHERE sku = ?');
$stmt->execute([$sku]);
$existing = $stmt->fetch();
if (isset($_POST['original_sku']) && $_POST['original_sku'] !== '') {
$orig_sku = $_POST['original_sku'];
if ($existing && $existing['id'] != ($pdo->query("SELECT id FROM items WHERE sku = " . $pdo->quote($orig_sku))->fetchColumn() ?: -1)) {
echo json_encode(['success' => false, 'error' => 'SKU already exists']);
exit;
}
$sql = "UPDATE items SET sku=?, name=?, price=?, cost_price=?, base_stock=?, vat=?, category_id=?, supplier_id=?, unit_id=?, in_catalog=? " . ($image_url ? ", image_url=?" : "") . " WHERE sku=?";
$params = [$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $in_catalog];
if ($image_url) {
$params[] = $image_url;
}
$params[] = $orig_sku;
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
} else {
if ($existing) {
echo json_encode(['success' => false, 'error' => 'SKU already exists']);
exit;
}
$stmt = $pdo->prepare("INSERT INTO items (sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id, in_catalog, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $in_catalog, $image_url]);
}
echo json_encode(['success' => true]);
exit;
} catch (Throwable $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
exit;
}
}
if ($_POST['action'] === 'delete') {
try {
$sku = $_POST['sku'] ?? '';
if (!$sku) {
echo json_encode(['success' => false, 'error' => 'Missing SKU']);
exit;
}
$stmt = $pdo->prepare('DELETE FROM items WHERE sku = ?');
$stmt->execute([$sku]);
echo json_encode(['success' => true]);
exit;
} catch (Throwable $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
exit;
}
}
}
$allStock = [];
try {
$allStock = stock_snapshot();
} catch (Throwable $e) {
$dbError = $e->getMessage();
}
$categories = [];
$suppliers = [];
try {
$pdo = db();
$categories = $pdo->query('SELECT id, name_ar, name_en FROM categories ORDER BY name_ar ASC')->fetchAll();
$suppliers = $pdo->query('SELECT id, name FROM suppliers ORDER BY name ASC')->fetchAll();
$units = $pdo->query('SELECT id, name_ar, name_en FROM units ORDER BY name_ar ASC')->fetchAll();
} catch (Throwable $e) {
// Ignore if not present
}
// Search and filter logic
$search = $_GET['q'] ?? '';
$catFilter = $_GET['category'] ?? '';
$supFilter = $_GET['supplier'] ?? '';
$filteredStock = [];
if (empty($dbError)) {
$lowerSearch = strtolower($search);
foreach ($allStock as $key => $row) {
$matchSearch = !$search || str_contains(strtolower((string)$row['sku']), $lowerSearch) || str_contains(strtolower((string)$row['name']), $lowerSearch);
$matchCat = !$catFilter || (isset($row['category_id']) && $row['category_id'] == $catFilter);
$matchSup = !$supFilter || (isset($row['supplier_id']) && $row['supplier_id'] == $supFilter);
if ($matchSearch && $matchCat && $matchSup) {
$filteredStock[$key] = $row;
}
}
}
// Pagination logic
$page = max(1, (int)($_GET['p'] ?? 1));
$limit = 10;
$total = count($filteredStock);
$totalPages = max(1, ceil($total / $limit));
$offset = ($page - 1) * $limit;
$stockRows = array_slice($filteredStock, $offset, $limit, true);
require __DIR__ . '/includes/header.php';
?>
<section class="mb-4">
<div class="row g-4 align-items-center mb-3">
<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-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-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">
<div class="input-group">
<input type="text" name="q" id="searchInput" class="form-control" placeholder="<?= h(tr('بحث برمز الصنف أو الاسم...', 'Search by SKU or name...')) ?>" value="<?= h($search) ?>" autocomplete="off">
<button class="btn btn-outline-secondary bg-white" type="button" id="clearSearchBtn" style="<?= empty($search) ? 'display: none;' : '' ?>" title="<?= h(tr('مسح البحث', 'Clear Search')) ?>">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
<div class="col-md-3">
<select name="category" class="form-select">
<option value=""><?= h(tr('الكل (التصنيف)', 'All Categories')) ?></option>
<?php foreach($categories as $cat): ?>
<option value="<?= h($cat['id']) ?>" <?= ($catFilter == $cat['id']) ? 'selected' : '' ?>><?= h(current_lang() === 'ar' ? $cat['name_ar'] : $cat['name_en']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<select name="supplier" class="form-select">
<option value=""><?= h(tr('الكل (المورد)', 'All Suppliers')) ?></option>
<?php foreach($suppliers as $sup): ?>
<option value="<?= h($sup['id']) ?>" <?= ($supFilter == $sup['id']) ? 'selected' : '' ?>><?= h($sup['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<button class="btn btn-primary w-100 shadow-sm" type="submit">
<i class="bi bi-funnel"></i> <?= h(tr('تصفية', 'Filter')) ?>
</button>
</div>
</div>
</form>
</section>
<section>
<?php if ($dbError): ?>
<div class="alert alert-warning"><?= h($dbError) ?></div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0 text-center">
<thead class="table-light">
<tr>
<th width="40">
<input class="form-check-input" type="checkbox" id="selectAllCheckbox" onclick="toggleAllCheckboxes(this)">
</th>
<th>SKU</th>
<th width="70"><?= h(tr('صورة', 'Pic')) ?></th>
<th class="text-end pe-4"><?= h(tr('الصنف', 'Product')) ?></th>
<th><?= h(tr('السعر', 'Price')) ?></th>
<th><?= h(tr('التكلفة', 'Cost')) ?></th>
<th><?= h(tr('افتتاحي', 'Opening')) ?></th>
<th><?= h(tr('مباع', 'Sold')) ?></th>
<th><?= h(tr('متاح', 'Available')) ?></th>
<th><?= h(tr('التنبيه', 'Signal')) ?></th>
<th class="text-end pe-4"><?= h(tr('إجراءات', 'Actions')) ?></th>
</tr>
</thead>
<tbody class="border-top-0">
<?php if(empty($stockRows)): ?>
<tr><td colspan="11" class="text-center text-muted py-5"><i class="bi bi-inbox fs-1 d-block text-black-50 mb-2"></i> <?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
<?php endif; ?>
<?php foreach ($stockRows as $row): ?>
<tr style="transition: all 0.2s ease;">
<td>
<input class="form-check-input item-checkbox" type="checkbox" value="<?= h($row['sku']) ?>">
</td>
<td class="text-secondary"><?= h($row['sku']) ?></td>
<td>
<?php if (!empty($row['image_url'])): ?>
<img src="<?= h($row['image_url']) ?>" alt="pic" class="img-thumbnail p-0 shadow-sm" style="width: 45px; height: 45px; object-fit: cover; border-radius: 10px;">
<?php else: ?>
<div class="bg-light text-muted d-flex align-items-center justify-content-center shadow-sm mx-auto" style="width: 45px; height: 45px; border-radius: 10px; font-size: 1rem;">
<i class="bi bi-box-seam"></i>
</div>
<?php endif; ?>
</td>
<td class="fw-bold text-end text-dark pe-4"><?= h($row['name']) ?></td>
<td><span class="badge bg-light text-dark border px-2 py-1"><?= h(currency($row['price'])) ?></span></td>
<td><span class="badge bg-light text-secondary border px-2 py-1"><?= h(currency($row['cost_price'] ?? 0)) ?></span></td>
<td class="text-secondary"><?= h((string) $row['base_stock']) ?></td>
<td class="text-secondary"><?= h((string) $row['sold']) ?></td>
<td class="fw-bold text-primary fs-6"><?= h((string) $row['available']) ?></td>
<td>
<?php if ($row['available'] <= 12): ?>
<span class="badge bg-warning bg-opacity-25 text-dark px-3 py-2 rounded-pill"><i class="bi bi-exclamation-triangle me-1"></i> <?= h(tr('منخفض', 'Low')) ?></span>
<?php else: ?>
<span class="badge bg-success bg-opacity-10 text-success px-3 py-2 rounded-pill border border-success border-opacity-25"><i class="bi bi-check2-circle me-1"></i> <?= h(tr('مستقر', 'Stable')) ?></span>
<?php endif; ?>
</td>
<td class="text-end pe-3">
<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'] ?? '') ?>', '<?= h($row['in_catalog'] ?? 0) ?>')" data-bs-toggle="tooltip" title="<?= h(tr('تعديل', 'Edit')) ?>">
<i class="bi bi-pencil"></i>
</button>
<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>
</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>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<nav class="mt-4">
<ul class="pagination justify-content-center mb-0">
<?php for($i=1; $i<=$totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="<?= h(url_for('stock.php', ['p' => $i, 'q' => $search, 'category' => $catFilter, 'supplier' => $supFilter])) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
</ul>
</nav>
<?php endif; ?>
<?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('ملاحظة: يدعم النظام الآن ملفات Excel (XLSX) بالإضافة إلى CSV.', 'Note: The system now supports Excel (XLSX) files in addition to CSV.')) ?>
</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, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" 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">
<div class="modal-content">
<form onsubmit="handleItemSubmit(event)" id="itemForm">
<div class="modal-header bg-primary text-white ">
<h5 class="modal-title" id="itemModalLabel"><?= h(tr('إضافة / تعديل صنف', 'Add / Edit Item')) ?></h5>
<button type="button" class="btn-close btn-close-white " data-bs-dismiss="modal" aria-label="Close" ></button>
</div>
<div class="modal-body">
<input type="hidden" id="item_original_sku">
<input type="hidden" id="item_existing_image_url">
<div class="row">
<div class="col-12 mb-3 text-center">
<label class="form-label d-block text-start"><?= h(tr('صورة الصنف', 'Item Picture')) ?></label>
<input type="file" class="form-control" id="item_picture" accept="image/*">
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('رمز الصنف (SKU)', 'SKU')) ?></label>
<div class="input-group">
<input type="text" class="form-control" id="item_sku" required maxlength="15">
<button type="button" class="btn btn-outline-secondary" onclick="suggestSKU()" title="<?= h(tr('اقتراح رمز', 'Suggest SKU')) ?>">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('اسم الصنف', 'Product Name')) ?></label>
<input type="text" class="form-control" id="item_name" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('السعر', 'Price')) ?></label>
<input type="number" step="0.001" class="form-control" id="item_price" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('الظهور في المتجر (الكتالوج)', 'Visible in Catalog')) ?></label>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" id="item_in_catalog" style="transform: scale(1.3); margin-left: -1.5em; margin-right: 0.5em;">
<label class="form-check-label" for="item_in_catalog" style="margin-right: 2.5em;"><?= h(tr('عرض أونلاين', 'Show Online')) ?></label>
</div>
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('التكلفة', 'Cost Price')) ?></label>
<input type="number" step="0.001" class="form-control" id="item_cost_price" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('الرصيد الافتتاحي', 'Opening Stock')) ?></label>
<input type="number" class="form-control" id="item_base_stock" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('الضريبة (VAT %)', 'VAT %')) ?></label>
<input type="number" step="0.001" class="form-control" id="item_vat" value="<?= h(get_setting('vat_percentage', 5)) ?>" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('التصنيف', 'Category')) ?></label>
<select class="form-select" id="item_category">
<option value=""><?= h(tr('-- اختر التصنيف --', '-- Select Category --')) ?></option>
<?php foreach($categories as $cat): ?>
<option value="<?= h($cat['id']) ?>"><?= h(current_lang() === 'ar' ? $cat['name_ar'] : $cat['name_en']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('المورد', 'Supplier')) ?></label>
<select class="form-select" id="item_supplier">
<option value=""><?= h(tr('-- اختر المورد --', '-- Select Supplier --')) ?></option>
<?php foreach($suppliers as $sup): ?>
<option value="<?= h($sup['id']) ?>"><?= h($sup['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-12 mb-3">
<label class="form-label"><?= h(tr('الوحدة', 'Unit')) ?></label>
<select class="form-select" id="item_unit">
<option value=""><?= h(tr('-- اختر الوحدة --', '-- Select Unit --')) ?></option>
<?php foreach($units as $unit): ?>
<option value="<?= h($unit['id']) ?>"><?= h(current_lang() === 'ar' ? $unit['name_ar'] : $unit['name_en']) ?></option>
<?php endforeach; ?>
</select>
</div>
</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-primary" id="saveItemBtn">
<i class="bi bi-save me-1"></i> <?= h(tr('حفظ التغييرات', 'Save Changes')) ?>
</button>
</div>
</form>
</div>
</div>
</div>
<script>
const existingSkus = <?= json_encode(array_values(array_unique(array_map('strval', array_column($allStock, 'sku'))))) ?>;
function suggestSKU() {
let newSku;
do {
newSku = Math.floor(10000000 + Math.random() * 90000000).toString().substring(0, 8);
} while (existingSkus.includes(newSku));
document.getElementById('item_sku').value = newSku;
}
let itemModalObj = null;
document.addEventListener('DOMContentLoaded', function () {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
itemModalObj = new bootstrap.Modal(document.getElementById('itemModal'));
const searchInput = document.getElementById('searchInput');
const clearSearchBtn = document.getElementById('clearSearchBtn');
let searchTimeout;
if (searchInput) {
if (searchInput.value.length > 0) {
searchInput.focus();
const valLen = searchInput.value.length;
searchInput.setSelectionRange(valLen, valLen);
}
searchInput.addEventListener('input', function() {
const val = this.value;
if (clearSearchBtn) {
clearSearchBtn.style.display = val.length > 0 ? 'block' : 'none';
}
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const trimmed = val.trim();
if (trimmed.length >= 2 || trimmed.length === 0) {
this.closest('form').submit();
}
}, 600);
});
}
if (clearSearchBtn) {
clearSearchBtn.addEventListener('click', function() {
if (searchInput) {
searchInput.value = '';
searchInput.closest('form').submit();
}
});
}
});
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 = '', in_catalog = 0) {
document.getElementById('item_original_sku').value = sku;
document.getElementById('item_existing_image_url').value = image_url;
document.getElementById('item_sku').value = sku;
document.getElementById('item_name').value = name;
document.getElementById('item_price').value = price;
document.getElementById('item_cost_price').value = cost_price;
document.getElementById('item_base_stock').value = base_stock;
document.getElementById('item_vat').value = vat;
document.getElementById('item_category').value = category_id;
document.getElementById('item_supplier').value = supplier_id;
document.getElementById('item_unit').value = unit_id;
document.getElementById('item_in_catalog').checked = (parseInt(in_catalog) === 1);
// Remove old image preview if any
const oldPreview = document.getElementById('image_preview');
if (oldPreview) oldPreview.remove();
if (image_url) {
const preview = document.createElement('img');
preview.id = 'image_preview';
preview.src = image_url;
preview.style.maxHeight = '100px';
preview.className = 'mt-2 rounded shadow-sm border';
document.getElementById('item_picture').parentElement.appendChild(preview);
}
document.getElementById('item_picture').value = '';
itemModalObj.show();
}
async function handleItemSubmit(e) {
e.preventDefault();
const btn = document.getElementById('saveItemBtn');
const originalText = btn.innerHTML;
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>';
btn.disabled = true;
const formData = new FormData();
formData.append('action', 'save');
formData.append('original_sku', document.getElementById('item_original_sku').value);
formData.append('sku', document.getElementById('item_sku').value);
formData.append('name', document.getElementById('item_name').value);
formData.append('price', document.getElementById('item_price').value);
formData.append('cost_price', document.getElementById('item_cost_price').value);
formData.append('base_stock', document.getElementById('item_base_stock').value);
formData.append('vat', document.getElementById('item_vat').value);
formData.append('category_id', document.getElementById('item_category').value);
formData.append('supplier_id', document.getElementById('item_supplier').value);
formData.append('unit_id', document.getElementById('item_unit').value);
formData.append('in_catalog', document.getElementById('item_in_catalog').checked ? '1' : '0');
formData.append('existing_image_url', document.getElementById('item_existing_image_url').value);
const picInput = document.getElementById('item_picture');
if (picInput.files.length > 0) {
formData.append('picture', picInput.files[0]);
}
try {
const res = await fetch('stock.php', { method: 'POST', body: formData });
const json = await res.json();
if (json.success) {
itemModalObj.hide();
Swal.fire({
title: '<?= h(tr('تم الحفظ بنجاح', 'Successfully saved')) ?>',
icon: 'success',
showConfirmButton: false,
timer: 1500
}).then(() => location.reload());
} else {
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', json.error || '<?= h(tr('فشل الحفظ', 'Failed to save')) ?>', 'error');
}
} catch(err) {
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', '<?= h(tr('حدث خطأ في الاتصال', 'Network error')) ?>', 'error');
} finally {
btn.innerHTML = originalText;
btn.disabled = false;
}
}
function deleteItem(sku) {
Swal.fire({
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
text: '<?= h(tr('لن تتمكن من التراجع عن هذا!', "You won't be able to revert this!")) ?>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
confirmButtonText: '<?= h(tr('نعم، احذف', 'Yes, delete it!')) ?>',
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
}).then(async (result) => {
if (result.isConfirmed) {
try {
const formData = new FormData();
formData.append('action', 'delete');
formData.append('sku', sku);
const res = await fetch('stock.php', { method: 'POST', body: formData });
const json = await res.json();
if (json.success) {
Swal.fire({
title: '<?= h(tr('محذوف!', 'Deleted!')) ?>',
icon: 'success',
showConfirmButton: false,
timer: 1500
}).then(() => location.reload());
} else {
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', json.error || '<?= h(tr('فشل الحذف', 'Failed to delete')) ?>', 'error');
}
} catch(err) {
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', '<?= h(tr('حدث خطأ في الاتصال', 'Network error')) ?>', 'error');
}
}
});
}
function toggleAllCheckboxes(source) {
const checkboxes = document.querySelectorAll(".item-checkbox");
for (let i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = source.checked;
}
}
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")) ?>', 'warning');
return;
}
const form = document.createElement('form');
form.method = 'POST';
form.action = 'print_labels.php';
checkboxes.forEach((cb) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'skus[]';
input.value = cb.value;
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"; ?>