diff --git a/db/migrations/20260217_purchase_returns.sql b/db/migrations/20260217_purchase_returns.sql new file mode 100644 index 0000000..2a6b9c9 --- /dev/null +++ b/db/migrations/20260217_purchase_returns.sql @@ -0,0 +1,23 @@ +-- Migration: Add Purchase Returns tables +CREATE TABLE IF NOT EXISTS purchase_returns ( + id INT AUTO_INCREMENT PRIMARY KEY, + invoice_id INT NOT NULL, + supplier_id INT NULL, + return_date DATE NOT NULL, + total_amount DECIMAL(15, 3) NOT NULL DEFAULT 0, + notes TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE CASCADE, + FOREIGN KEY (supplier_id) REFERENCES customers(id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS purchase_return_items ( + id INT AUTO_INCREMENT PRIMARY KEY, + return_id INT NOT NULL, + item_id INT NOT NULL, + quantity DECIMAL(15, 2) NOT NULL, + unit_price DECIMAL(15, 3) NOT NULL, + total_price DECIMAL(15, 3) NOT NULL, + FOREIGN KEY (return_id) REFERENCES purchase_returns(id) ON DELETE CASCADE, + FOREIGN KEY (item_id) REFERENCES stock_items(id) ON DELETE CASCADE +); diff --git a/index.php b/index.php index 9a68a77..d6f2da0 100644 --- a/index.php +++ b/index.php @@ -115,6 +115,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action'])) { echo json_encode($stmt->fetchAll()); exit; } + if ($_GET['action'] === 'get_return_details') { + header('Content-Type: application/json'); + $return_id = (int)$_GET['return_id']; + $type = $_GET['type'] ?? 'sale'; // 'sale' or 'purchase' + + $table = ($type === 'purchase') ? 'purchase_returns' : 'sales_returns'; + $item_table = ($type === 'purchase') ? 'purchase_return_items' : 'sales_return_items'; + + $stmt = db()->prepare("SELECT r.*, c.name as party_name, i.invoice_date as original_invoice_date + FROM $table r + LEFT JOIN customers c ON " . ($type === 'purchase' ? 'r.supplier_id' : 'r.customer_id') . " = c.id + LEFT JOIN invoices i ON r.invoice_id = i.id + WHERE r.id = ?"); + $stmt->execute([$return_id]); + $return = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($return) { + $stmt = db()->prepare("SELECT ri.*, si.name_en, si.name_ar, si.sku + FROM $item_table ri + JOIN stock_items si ON ri.item_id = si.id + WHERE ri.return_id = ?"); + $stmt->execute([$return_id]); + $return['items'] = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + echo json_encode($return); + exit; + } } if ($_SERVER['REQUEST_METHOD'] === 'POST') { @@ -1123,6 +1151,72 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } } + + if (isset($_POST['add_purchase_return'])) { + $invoice_id = (int)$_POST['invoice_id']; + $return_date = $_POST['return_date'] ?: date('Y-m-d'); + $notes = $_POST['notes'] ?? ''; + $item_ids = $_POST['item_ids'] ?? []; + $quantities = $_POST['quantities'] ?? []; + $prices = $_POST['prices'] ?? []; + + if ($invoice_id && !empty($item_ids)) { + $db = db(); + $db->beginTransaction(); + try { + // Get supplier ID from invoice + $stmt = $db->prepare("SELECT customer_id FROM invoices WHERE id = ?"); + $stmt->execute([$invoice_id]); + $supplier_id = $stmt->fetchColumn(); + + $total_return_amount = 0; + $items_data = []; + foreach ($item_ids as $index => $item_id) { + $qty = (float)$quantities[$index]; + $price = (float)$prices[$index]; + if ($qty <= 0) continue; + + $line_total = $qty * $price; + $total_return_amount += $line_total; + + $items_data[] = [ + 'id' => $item_id, + 'qty' => $qty, + 'price' => $price, + 'total' => $line_total + ]; + } + + if (empty($items_data)) throw new Exception("No items to return"); + + // Create Purchase Return record + $stmt = $db->prepare("INSERT INTO purchase_returns (invoice_id, supplier_id, return_date, total_amount, notes) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$invoice_id, $supplier_id, $return_date, $total_return_amount, $notes]); + $return_id = $db->lastInsertId(); + + foreach ($items_data as $item) { + $stmt = $db->prepare("INSERT INTO purchase_return_items (return_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$return_id, $item['id'], $item['qty'], $item['price'], $item['total']]); + + // Update stock (decrease) + $stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?"); + $stmt->execute([$item['qty'], $item['id']]); + } + + // Reduce debt to supplier + if ($supplier_id) { + $stmt = $db->prepare("UPDATE customers SET balance = balance + ? WHERE id = ?"); + $stmt->execute([$total_return_amount, $supplier_id]); + } + + $db->commit(); + $message = "Purchase Return #$return_id processed successfully!"; + } catch (Exception $e) { + if (isset($db)) $db->rollBack(); + $message = "Error: " . $e->getMessage(); + } + } + } } @@ -1350,6 +1444,27 @@ switch ($page) { $data['sales_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices WHERE type = 'sale' ORDER BY id DESC")->fetchAll(); break; + case 'purchase_returns': + $where = ["1=1"]; + $params = []; + if (!empty($_GET['search'])) { + $where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.invoice_id LIKE ?)"; + $params[] = "%{$_GET['search']}%"; + $params[] = "%{$_GET['search']}%"; + $params[] = "%{$_GET['search']}%"; + } + $whereSql = implode(" AND ", $where); + $stmt = db()->prepare("SELECT pr.*, c.name as supplier_name, i.total_with_vat as invoice_total + FROM purchase_returns pr + LEFT JOIN customers c ON pr.supplier_id = c.id + LEFT JOIN invoices i ON pr.invoice_id = i.id + WHERE $whereSql + ORDER BY pr.id DESC"); + $stmt->execute($params); + $data['returns'] = $stmt->fetchAll(); + $data['purchase_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices WHERE type = 'purchase' ORDER BY id DESC")->fetchAll(); + break; + case 'customer_statement': case 'supplier_statement': $type = ($page === 'customer_statement') ? 'customer' : 'supplier'; @@ -1533,6 +1648,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; Purchase Tax Invoices + + Purchase Returns + Quotations @@ -3322,7 +3440,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
| Return # | +Date | +Invoice # | +Supplier | +Total Amount | +Actions | +
|---|---|---|---|---|---|
| PRET-= str_pad((string)$ret['id'], 5, '0', STR_PAD_LEFT) ?> | += $ret['return_date'] ?> | +INV-= str_pad((string)$ret['invoice_id'], 5, '0', STR_PAD_LEFT) ?> | += htmlspecialchars($ret['supplier_name'] ?? 'Unknown') ?> | +OMR = number_format((float)$ret['total_amount'], 3) ?> | +
+
+
+
+ |
+
| No returns found | |||||