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);
$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=? " . ($image_url ? ", image_url=?" : "") . " WHERE sku=?";
$params = [$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id];
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, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $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';
?>
= h(tr('إدارة الأصناف وجرد المخزون.', 'Manage items and inventory.')) ?>= h(tr('قائمة الأصناف والمخزون', 'Items & Stock List')) ?>