diff --git a/api/suppliers.php b/api/suppliers.php new file mode 100644 index 0000000..d337041 --- /dev/null +++ b/api/suppliers.php @@ -0,0 +1,26 @@ + false, 'error' => tr('الاسم مطلوب', 'Name is required')]); + exit; + } + + try { + $pdo = db(); + $stmt = $pdo->prepare('INSERT INTO suppliers (name, phone) VALUES (?, ?)'); + $stmt->execute([$name, $phone]); + $id = $pdo->lastInsertId(); + + echo json_encode(['success' => true, 'supplier' => ['id' => $id, 'name' => $name, 'phone' => $phone]]); + } catch (Throwable $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + } + exit; +} \ No newline at end of file diff --git a/includes/app.php b/includes/app.php index 1bc51e7..6a430e3 100644 --- a/includes/app.php +++ b/includes/app.php @@ -230,6 +230,7 @@ function catalog(): array "name_ar" => $item["name"], "name_en" => $item["name"], "price" => (float)$item["price"], + "cost_price" => (float)($item["cost_price"] ?? 0), "base_stock" => (int)$item["base_stock"], "vat" => (float)$item["vat"], "category_id" => $item["category_id"], @@ -488,6 +489,7 @@ function stock_snapshot(): array 'sold' => $used, 'available' => max(0, $base - $used), 'price' => $item['price'], + 'cost_price' => $item['cost_price'] ?? 0, 'category_id' => $item['category_id'], 'supplier_id' => $item['supplier_id'], 'image_url' => $item['image_url'], @@ -525,3 +527,47 @@ function receipt_code(): string { return 'AR-' . date('ymd-His') . '-' . random_int(100, 999); } + +function create_purchase(array $data): int +{ + db()->beginTransaction(); + try { + $stmt = db()->prepare("INSERT INTO purchase_orders + (reference_no, branch_code, user_username, user_name, role_name, supplier_name, items_json, item_count, subtotal, total_amount, status, notes, purchase_date) + VALUES + (:reference_no, :branch_code, :user_username, :user_name, :role_name, :supplier_name, :items_json, :item_count, :subtotal, :total_amount, :status, :notes, NOW())"); + + $stmt->bindValue(":reference_no", $data["reference_no"]); + $stmt->bindValue(":branch_code", $data["branch_code"]); + $stmt->bindValue(":user_username", $data["user_username"]); + $stmt->bindValue(":user_name", $data["user_name"]); + $stmt->bindValue(":role_name", $data["role_name"]); + $stmt->bindValue(":supplier_name", $data["supplier_name"]); + $stmt->bindValue(":items_json", json_encode($data["items"], JSON_UNESCAPED_UNICODE)); + $stmt->bindValue(":item_count", $data["item_count"], PDO::PARAM_INT); + $stmt->bindValue(":subtotal", $data["subtotal"]); + $stmt->bindValue(":total_amount", $data["total_amount"]); + $stmt->bindValue(":status", $data["status"] ?? "completed"); + $stmt->bindValue(":notes", $data["notes"]); + $stmt->execute(); + + $purchaseId = (int) db()->lastInsertId(); + + // Update stock + foreach ($data["items"] as $item) { + $qty = (int) $item["qty"]; + $sku = $item["sku"]; + $updateStmt = db()->prepare("UPDATE items SET base_stock = base_stock + :qty, cost_price = :price WHERE sku = :sku"); + $updateStmt->bindValue(":qty", $qty, PDO::PARAM_INT); + $updateStmt->bindValue(":price", $item["price"]); + $updateStmt->bindValue(":sku", $sku); + $updateStmt->execute(); + } + + db()->commit(); + return $purchaseId; + } catch (Throwable $e) { + db()->rollBack(); + throw $e; + } +} diff --git a/includes/header.php b/includes/header.php index 5f1277c..86cb7ae 100644 --- a/includes/header.php +++ b/includes/header.php @@ -70,16 +70,8 @@ $isPublic = !isset($user) || !$user; - - - - - - - - - - + +
@@ -99,10 +91,44 @@ $isPublic = !isset($user) || !$user;
- - - + + + +
+ + +
+
+
+ + + + + + + + + +
+
+ + +
+ + +
+
+
+
+ + + + + + +
+
diff --git a/includes/purchase_form.php b/includes/purchase_form.php new file mode 100644 index 0000000..ff3f41d --- /dev/null +++ b/includes/purchase_form.php @@ -0,0 +1,664 @@ +query('SELECT id, name, phone FROM suppliers ORDER BY name ASC')->fetchAll(); +} catch (Throwable $e) { + $customers = []; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $branchCode = trim((string) ($_POST['branch_code'] ?? '')); + $supplierName = trim((string) ($_POST['supplier_name'] ?? '')); + $paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash')); + $purchaseStatus = trim((string) ($_POST['purchase_status'] ?? 'completed')); + $notes = trim((string) ($_POST['notes'] ?? '')); + $cartJson = (string) ($_POST['cart_json'] ?? '[]'); + $items = json_decode($cartJson, true); + + if (!in_array($branchCode, $allowedBranches, true)) { + $error = tr('اختر فرعاً صالحاً لهذه الصلاحية.', 'Choose a valid branch for this role.'); + } elseif (!in_array($paymentMethod, ['cash', 'card', 'transfer'], true)) { + $error = tr('اختر طريقة دفع صحيحة.', 'Choose a valid payment method.'); + } elseif (!is_array($items) || $items === []) { + $error = tr('أضف صنفاً واحداً على الأقل إلى الفاتورة.', 'Add at least one item to the invoice.'); + } else { + $normalized = []; + $subtotal = 0.0; + $itemCount = 0; + foreach ($items as $item) { + $sku = (string) ($item['sku'] ?? ''); + $qty = (int) ($item['qty'] ?? 0); + if (!isset($catalog[$sku]) || $qty < 1) { + continue; + } + $product = $catalog[$sku]; + $price = isset($item['price']) ? (float)$item['price'] : (float)($product['cost_price'] ?? $product['price']); + $lineTotal = $price * $qty; + $normalized[] = [ + 'sku' => $sku, + 'name_ar' => $product['name_ar'], + 'name_en' => $product['name_en'], + 'qty' => $qty, + 'price' => $price, + 'line_total' => $lineTotal, + ]; + $subtotal += $lineTotal; + $itemCount += $qty; + } + + if ($normalized === []) { + $error = tr('الفاتورة غير صالحة بعد التحقق من الأصناف.', 'The invoice is invalid after product validation.'); + } else { + $cashierName = current_lang() === 'ar' ? $user['name_ar'] : $user['name_en']; + $purchaseId = create_purchase([ + 'reference_no' => receipt_code(), + 'branch_code' => $branchCode, + 'user_username' => $user['username'], + 'user_name' => $cashierName, + 'role_name' => $user['role'], + 'supplier_name' => $supplierName !== '' ? $supplierName : null, + 'items' => $normalized, + 'item_count' => $itemCount, + 'subtotal' => $subtotal, + 'total_amount' => $subtotal, + 'status' => $purchaseStatus, + 'notes' => $notes !== '' ? $notes : null, + ]); + + set_flash('success', tr('تم حفظ فاتورة المشتريات بنجاح. وتم تحديث المخزون.', 'Purchase invoice saved successfully and stock updated.')); + redirect_to('purchases.php'); + } + } +} + +require __DIR__ . '/header.php'; +?> + + + +
+
+

+ + + +
+ + +
+ + +
+ + +
+
+ +
+
+
+ +
+
+
+ + +
+ + +
+
+ + +
+ + + + + + + + + + + + + + + +
+ + +
+
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+ + +
+
+ +
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+ + 0.000 +
+
+ + +
+
+ + 0.000 +
+
+ + +
+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/login.php b/login.php index 52d571e..81fadc9 100644 --- a/login.php +++ b/login.php @@ -120,12 +120,6 @@ $accounts = demo_users();

- -
-
3
-
3
-
2
-
© @@ -145,7 +139,7 @@ $accounts = demo_users();

@@ -169,14 +163,14 @@ $accounts = demo_users();
- +
- +
@@ -221,7 +215,7 @@ $accounts = demo_users();

- +
+ + + + + + + + + +HTML; + +$newContent = str_replace($search, $replace, $content); +file_put_contents('includes/header.php', $newContent); +echo "Done"; diff --git a/purchases.php b/purchases.php index 28e630a..ded70e5 100644 --- a/purchases.php +++ b/purchases.php @@ -4,45 +4,53 @@ $user = require_roles(['owner', 'manager']); $pageTitle = tr('المشتريات', 'Purchases'); $activeNav = 'purchases'; -$allPurchases = purchase_pipeline(); - // Search logic $search = $_GET['q'] ?? ''; -$filteredPurchases = []; -if ($search) { - $lowerSearch = strtolower($search); - foreach ($allPurchases as $key => $row) { - if ( - str_contains(strtolower((string)$row['supplier']), $lowerSearch) || - str_contains(strtolower((string)$row['reference']), $lowerSearch) - ) { - $filteredPurchases[$key] = $row; - } - } -} else { - $filteredPurchases = $allPurchases; -} - -// Pagination logic $page = max(1, (int)($_GET['p'] ?? 1)); $limit = 10; -$total = count($filteredPurchases); -$totalPages = max(1, ceil($total / $limit)); $offset = ($page - 1) * $limit; -$purchaseRows = array_slice($filteredPurchases, $offset, $limit, true); + +$params = []; +$where = ' WHERE 1=1 '; + +if ($search) { + $where .= ' AND (supplier_name LIKE :search OR reference_no LIKE :search) '; + $params[':search'] = "%$search%"; +} + +// Pagination counts +$countSql = 'SELECT COUNT(*) FROM purchase_orders' . $where; +$countStmt = db()->prepare($countSql); +foreach ($params as $key => $value) { + $countStmt->bindValue($key, $value); +} +$countStmt->execute(); +$total = $countStmt->fetchColumn(); +$totalPages = max(1, ceil($total / $limit)); + +// Fetch Data +$sql = 'SELECT * FROM purchase_orders' . $where . ' ORDER BY purchase_date DESC LIMIT :limit OFFSET :offset'; +$stmt = db()->prepare($sql); +foreach ($params as $key => $value) { + $stmt->bindValue($key, $value); +} +$stmt->bindValue(':limit', $limit, PDO::PARAM_INT); +$stmt->bindValue(':offset', $offset, PDO::PARAM_INT); +$stmt->execute(); +$purchaseRows = $stmt->fetchAll(); require __DIR__ . '/includes/header.php'; ?>
-

-

+

+

- + + +
@@ -62,22 +70,30 @@ require __DIR__ . '/includes/header.php'; + - + - + - - - - - + + + + + + + + + + + + + +
+
+ +
-
-
- - -
-
-
+ +
+ +
+ + +
+
+ +
+ + +
+ +
-
+ +
+ + +
+ +
-
+ +
-
-
- - -
-
- - -
-
- - + +
+ + +
+ +
+ + +
+ +
+ + +