1056 lines
49 KiB
PHP
1056 lines
49 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, notes 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', 'Notes']);
|
|
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, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
$stmtUpdate = $pdo->prepare("UPDATE items SET name=?, price=?, cost_price=?, base_stock=?, vat=?, category_id=?, supplier_id=?, unit_id=?, notes=? 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;
|
|
$notes = isset($row[9]) ? trim((string)$row[9]) : null;
|
|
$notes = $notes === '' ? null : $notes;
|
|
$stmtCheck->execute([$sku]);
|
|
if ($stmtCheck->fetchColumn()) {
|
|
$stmtUpdate->execute([$name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $notes, $sku]);
|
|
$updated++;
|
|
} else {
|
|
$stmtInsert->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $notes]);
|
|
$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;
|
|
$notes = trim((string)($_POST['notes'] ?? ''));
|
|
$notes = $notes !== '' ? $notes : 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=?, notes=?, in_catalog=? " . ($image_url ? ", image_url=?" : "") . " WHERE sku=?";
|
|
$params = [$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $notes, $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, notes, in_catalog, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
$stmt->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $notes, $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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Sorting logic
|
|
$sortField = $_GET['sort'] ?? '';
|
|
$sortOrder = strtolower($_GET['order'] ?? 'asc') === 'desc' ? 'desc' : 'asc';
|
|
|
|
if ($sortField && !empty($filteredStock)) {
|
|
usort($filteredStock, function($a, $b) use ($sortField, $sortOrder) {
|
|
$valA = $a[$sortField] ?? '';
|
|
$valB = $b[$sortField] ?? '';
|
|
|
|
if (in_array($sortField, ['price', 'cost_price', 'base_stock', 'sold', 'available'])) {
|
|
$valA = (float)$valA;
|
|
$valB = (float)$valB;
|
|
} else {
|
|
$valA = (string)$valA;
|
|
$valB = (string)$valB;
|
|
}
|
|
|
|
if ($valA == $valB) return 0;
|
|
$cmp = ($valA < $valB) ? -1 : 1;
|
|
return $sortOrder === 'asc' ? $cmp : -$cmp;
|
|
});
|
|
}
|
|
|
|
# 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);
|
|
|
|
|
|
function sortable_header($field, $label, $currentSortField, $currentSortOrder, $search, $catFilter, $supFilter) {
|
|
$nextOrder = ($currentSortField === $field && $currentSortOrder === 'asc') ? 'desc' : 'asc';
|
|
$icon = '';
|
|
if ($currentSortField === $field) {
|
|
$icon = $currentSortOrder === 'asc' ? '<i class="bi bi-sort-alpha-down"></i>' : '<i class="bi bi-sort-alpha-down-alt"></i>';
|
|
if (in_array($field, ['price', 'cost_price', 'base_stock', 'sold', 'available'])) {
|
|
$icon = $currentSortOrder === 'asc' ? '<i class="bi bi-sort-numeric-down"></i>' : '<i class="bi bi-sort-numeric-down-alt"></i>';
|
|
}
|
|
} else {
|
|
$icon = '<i class="bi bi-arrow-down-up text-black-50 small"></i>';
|
|
}
|
|
$url = url_for('stock.php', [
|
|
'q' => $search,
|
|
'category' => $catFilter,
|
|
'supplier' => $supFilter,
|
|
'sort' => $field,
|
|
'order' => $nextOrder
|
|
]);
|
|
return '<a href="' . h($url) . '" class="text-dark text-decoration-none d-inline-flex align-items-center gap-1">' . h($label) . ' ' . $icon . '</a>';
|
|
}
|
|
|
|
require __DIR__ . '/includes/header.php';
|
|
?>
|
|
|
|
<style>
|
|
#itemModal .modal-dialog {
|
|
max-width: min(1140px, calc(100vw - 2rem));
|
|
}
|
|
|
|
#itemModal .modal-content {
|
|
border: 0;
|
|
border-radius: 24px;
|
|
overflow: hidden;
|
|
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.18);
|
|
}
|
|
|
|
#itemModal .modal-header {
|
|
padding: 1rem 1.25rem;
|
|
background: linear-gradient(135deg, #0d6efd, #2f80ff) !important;
|
|
}
|
|
|
|
#itemModal .modal-body {
|
|
padding: 1.25rem;
|
|
background: linear-gradient(180deg, #f8fbff 0%, #f5f7fb 100%);
|
|
}
|
|
|
|
#itemModal .modal-footer {
|
|
padding: 1rem 1.25rem;
|
|
background: #fff;
|
|
border-top: 1px solid rgba(13, 110, 253, 0.08);
|
|
}
|
|
|
|
.item-form-panel {
|
|
height: 100%;
|
|
padding: 1rem 1rem 0.75rem;
|
|
border: 1px solid rgba(13, 110, 253, 0.1);
|
|
border-radius: 20px;
|
|
background: rgba(255, 255, 255, 0.96);
|
|
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
|
|
}
|
|
|
|
.item-form-panel__eyebrow {
|
|
display: inline-block;
|
|
margin-bottom: 0.35rem;
|
|
color: #6c757d;
|
|
font-size: 0.72rem;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.item-form-panel__title {
|
|
margin-bottom: 1rem;
|
|
color: #1f2937;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.item-form-panel .form-label {
|
|
margin-bottom: 0.4rem;
|
|
color: #475467;
|
|
font-size: 0.86rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.item-form-panel .form-control,
|
|
.item-form-panel .form-select {
|
|
min-height: 46px;
|
|
border-color: #dbe4f0;
|
|
border-radius: 14px;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.item-form-panel textarea.form-control {
|
|
min-height: 148px;
|
|
resize: vertical;
|
|
}
|
|
|
|
.item-form-panel .form-control:focus,
|
|
.item-form-panel .form-select:focus {
|
|
border-color: rgba(13, 110, 253, 0.45);
|
|
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.12);
|
|
}
|
|
|
|
.item-form-panel .input-group > .form-control {
|
|
border-start-end-radius: 0;
|
|
border-end-end-radius: 0;
|
|
}
|
|
|
|
.item-form-panel .input-group > .btn {
|
|
border-start-start-radius: 0;
|
|
border-end-start-radius: 0;
|
|
border-color: #dbe4f0;
|
|
}
|
|
|
|
.item-form-note {
|
|
display: block;
|
|
margin-top: 0.45rem;
|
|
color: #6c757d;
|
|
font-size: 0.78rem;
|
|
}
|
|
|
|
.item-switch-wrap {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
padding: 0.9rem 1rem;
|
|
border: 1px solid rgba(13, 110, 253, 0.12);
|
|
border-radius: 16px;
|
|
background: linear-gradient(180deg, #fbfdff 0%, #f3f8ff 100%);
|
|
}
|
|
|
|
.item-switch-wrap .form-check {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.item-switch-wrap .form-check-input {
|
|
float: none;
|
|
margin: 0;
|
|
transform: scale(1.15);
|
|
}
|
|
|
|
.item-image-dropzone {
|
|
padding: 1rem;
|
|
border: 1px dashed rgba(13, 110, 253, 0.35);
|
|
border-radius: 18px;
|
|
background: linear-gradient(180deg, #fbfdff 0%, #f0f7ff 100%);
|
|
}
|
|
|
|
.item-image-preview {
|
|
min-height: 190px;
|
|
margin-top: 0.85rem;
|
|
padding: 1rem;
|
|
border: 1px solid rgba(13, 110, 253, 0.12);
|
|
border-radius: 16px;
|
|
background: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.item-image-preview img {
|
|
max-width: 100%;
|
|
max-height: 160px;
|
|
object-fit: contain;
|
|
border-radius: 14px;
|
|
}
|
|
|
|
.item-image-placeholder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
text-align: center;
|
|
color: #98a2b3;
|
|
font-size: 0.92rem;
|
|
}
|
|
|
|
@media (max-width: 991.98px) {
|
|
#itemModal .modal-dialog {
|
|
max-width: calc(100vw - 1rem);
|
|
}
|
|
|
|
#itemModal .modal-body,
|
|
#itemModal .modal-footer {
|
|
padding: 1rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<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><?= sortable_header('sku', 'SKU', $sortField, $sortOrder, $search, $catFilter, $supFilter) ?></th>
|
|
<th width="70"><?= h(tr('صورة', 'Pic')) ?></th>
|
|
<th class="text-end pe-4"><?= sortable_header('name', tr('الصنف', 'Product'), $sortField, $sortOrder, $search, $catFilter, $supFilter) ?></th>
|
|
<th><?= sortable_header('price', tr('السعر', 'Price'), $sortField, $sortOrder, $search, $catFilter, $supFilter) ?></th>
|
|
<th><?= sortable_header('cost_price', tr('التكلفة', 'Cost'), $sortField, $sortOrder, $search, $catFilter, $supFilter) ?></th>
|
|
<th><?= sortable_header('base_stock', tr('افتتاحي', 'Opening'), $sortField, $sortOrder, $search, $catFilter, $supFilter) ?></th>
|
|
<th><?= sortable_header('sold', tr('مباع', 'Sold'), $sortField, $sortOrder, $search, $catFilter, $supFilter) ?></th>
|
|
<th><?= sortable_header('available', tr('متاح', 'Available'), $sortField, $sortOrder, $search, $catFilter, $supFilter) ?></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) ?>', <?= h(json_encode((string) ($row['notes'] ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) ?>)" 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 type="button" onclick="printLabelDates('<?= h(addslashes($row['sku'])) ?>')" class="btn btn-sm btn-outline-info rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" data-bs-toggle="tooltip" title="<?= h(tr('طباعة ملصق مع التواريخ', 'Print Label with Dates')) ?>">
|
|
<i class="bi bi-calendar-event"></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, 'sort' => $sortField, 'order' => $sortOrder])) ?>"><?= $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 modal-xl modal-dialog-centered modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<form onsubmit="handleItemSubmit(event)" id="itemForm">
|
|
<div class="modal-header bg-primary text-white">
|
|
<div>
|
|
<h5 class="modal-title mb-1" id="itemModalLabel"><?= h(tr('إضافة / تعديل صنف', 'Add / Edit Item')) ?></h5>
|
|
<div class="small text-white-50"><?= h(tr('نموذج أسرع ومنظم بثلاثة أعمدة لإدخال بيانات الصنف.', 'A faster 3-column layout for product details.')) ?></div>
|
|
</div>
|
|
<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 g-3">
|
|
<div class="col-12 col-lg-4">
|
|
<div class="item-form-panel">
|
|
<span class="item-form-panel__eyebrow"><?= h(tr('الأساسيات', 'Basics')) ?></span>
|
|
<h6 class="item-form-panel__title"><?= h(tr('بيانات الصنف', 'Item Identity')) ?></h6>
|
|
|
|
<div class="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>
|
|
<small class="item-form-note"><?= h(tr('يمكنك توليد رمز سريع تلقائياً.', 'Generate a quick SKU automatically.')) ?></small>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label"><?= h(tr('اسم الصنف', 'Product Name')) ?></label>
|
|
<input type="text" class="form-control" id="item_name" required>
|
|
</div>
|
|
|
|
<div class="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 class="mb-0">
|
|
<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>
|
|
</div>
|
|
|
|
<div class="col-12 col-lg-4">
|
|
<div class="item-form-panel">
|
|
<span class="item-form-panel__eyebrow"><?= h(tr('البيع والمخزون', 'Pricing & Stock')) ?></span>
|
|
<h6 class="item-form-panel__title"><?= h(tr('التسعير والمخزون', 'Pricing & Inventory')) ?></h6>
|
|
|
|
<div class="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="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="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="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="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="mb-0">
|
|
<label class="form-label"><?= h(tr('الظهور في المتجر (الكتالوج)', 'Visible in Catalog')) ?></label>
|
|
<div class="item-switch-wrap">
|
|
<div>
|
|
<div class="fw-semibold text-dark"><?= h(tr('عرض أونلاين', 'Show Online')) ?></div>
|
|
<small class="text-muted"><?= h(tr('إظهار الصنف في الكتالوج والمتجر.', 'Display this item in the catalog/store.')) ?></small>
|
|
</div>
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="item_in_catalog">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-lg-4">
|
|
<div class="item-form-panel">
|
|
<span class="item-form-panel__eyebrow"><?= h(tr('الصورة والملاحظات', 'Media & Notes')) ?></span>
|
|
<h6 class="item-form-panel__title"><?= h(tr('الصورة والملاحظات الداخلية', 'Image & Internal Notes')) ?></h6>
|
|
|
|
<div class="item-image-dropzone mb-3">
|
|
<label class="form-label d-block"><?= h(tr('صورة الصنف', 'Item Picture')) ?></label>
|
|
<input type="file" class="form-control" id="item_picture" accept="image/*">
|
|
<div class="item-image-preview" id="itemPicturePreview">
|
|
<div class="item-image-placeholder">
|
|
<i class="bi bi-card-image fs-2"></i>
|
|
<span><?= h(tr('لا توجد صورة محددة بعد', 'No image selected yet')) ?></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-0">
|
|
<label class="form-label"><?= h(tr('ملاحظات المنتج', 'Product Notes')) ?></label>
|
|
<textarea class="form-control" id="item_notes" rows="6" maxlength="1000" placeholder="<?= h(tr('ملاحظات داخلية عن المنتج...', 'Internal notes about this product...')) ?>"></textarea>
|
|
<small class="item-form-note"><?= h(tr('هذه الملاحظات داخلية وتساعد الفريق عند التعديل أو الجرد.', 'These notes are internal and help during edits or stock counting.')) ?></small>
|
|
</div>
|
|
</div>
|
|
</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 pictureInput = document.getElementById('item_picture');
|
|
if (pictureInput) {
|
|
pictureInput.addEventListener('change', function () {
|
|
const file = this.files && this.files[0] ? this.files[0] : null;
|
|
if (!file) {
|
|
renderItemImagePreview(document.getElementById('item_existing_image_url').value || '');
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function (event) {
|
|
renderItemImagePreview((event.target && event.target.result) ? event.target.result : '');
|
|
};
|
|
reader.readAsDataURL(file);
|
|
});
|
|
}
|
|
|
|
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 renderItemImagePreview(imageUrl = '') {
|
|
const preview = document.getElementById('itemPicturePreview');
|
|
if (!preview) return;
|
|
|
|
preview.innerHTML = '';
|
|
|
|
if (imageUrl) {
|
|
const img = document.createElement('img');
|
|
img.src = imageUrl;
|
|
img.alt = '<?= h(tr('معاينة صورة الصنف', 'Item image preview')) ?>';
|
|
preview.appendChild(img);
|
|
return;
|
|
}
|
|
|
|
const placeholder = document.createElement('div');
|
|
placeholder.className = 'item-image-placeholder';
|
|
placeholder.innerHTML = '<i class="bi bi-card-image fs-2"></i><span><?= h(tr('لا توجد صورة محددة بعد', 'No image selected yet')) ?></span>';
|
|
preview.appendChild(placeholder);
|
|
}
|
|
|
|
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, notes = '') {
|
|
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_notes').value = notes || '';
|
|
document.getElementById('item_in_catalog').checked = (parseInt(in_catalog) === 1);
|
|
document.getElementById('item_picture').value = '';
|
|
|
|
renderItemImagePreview(image_url || '');
|
|
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('notes', document.getElementById('item_notes').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);
|
|
}
|
|
|
|
function printLabelDates(sku) {
|
|
window.location.href = 'print_label_dates.php?sku=' + encodeURIComponent(sku);
|
|
}
|
|
</script>
|
|
|
|
<?php require __DIR__ . "/includes/footer.php"; ?>
|