diff --git a/db/migrations/2026-05-01_items_notes.sql b/db/migrations/2026-05-01_items_notes.sql new file mode 100644 index 0000000..371f064 --- /dev/null +++ b/db/migrations/2026-05-01_items_notes.sql @@ -0,0 +1,14 @@ +-- Add notes field to products/items for internal product remarks. +-- Safe to run multiple times on existing databases. + +SET @db_name := DATABASE(); + +SET @sql := IF ( + EXISTS ( + SELECT 1 FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db_name AND TABLE_NAME = 'items' AND COLUMN_NAME = 'notes' + ), + 'SELECT 1', + "ALTER TABLE items ADD COLUMN notes text DEFAULT NULL AFTER image_url" +); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; diff --git a/db/migrations/2026-05-01_production_updates.sql b/db/migrations/2026-05-01_production_updates.sql new file mode 100644 index 0000000..d3177d3 --- /dev/null +++ b/db/migrations/2026-05-01_production_updates.sql @@ -0,0 +1,34 @@ +-- Production sync for 2026-05-01 changes. +-- Safe to import on an existing production database. +-- Includes all schema migrations introduced on 2026-05-01. +-- Current scope: +-- 1) items.notes for internal product remarks in stock management. + +SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS; +SET FOREIGN_KEY_CHECKS = 0; + +DROP PROCEDURE IF EXISTS apply_production_updates_20260501; +DELIMITER $$ +CREATE PROCEDURE apply_production_updates_20260501() +BEGIN + -- items.notes + IF NOT EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'items' + AND COLUMN_NAME = 'notes' + ) THEN + ALTER TABLE items + ADD COLUMN notes TEXT DEFAULT NULL AFTER image_url; + END IF; +END $$ +DELIMITER ; + +CALL apply_production_updates_20260501(); +DROP PROCEDURE IF EXISTS apply_production_updates_20260501; + +SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS; + +-- Optional verification after import: +-- SHOW COLUMNS FROM items; diff --git a/db/schema.sql b/db/schema.sql index 103c871..56d8a52 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -73,6 +73,7 @@ CREATE TABLE IF NOT EXISTS `items` ( `category_id` int(10) unsigned DEFAULT NULL, `supplier_id` int(10) unsigned DEFAULT NULL, `image_url` varchar(255) DEFAULT NULL, + `notes` text DEFAULT NULL, `created_at` datetime DEFAULT current_timestamp(), `unit_id` int(10) unsigned DEFAULT NULL, `in_catalog` tinyint(1) NOT NULL DEFAULT 0, diff --git a/edit_sale.php b/edit_sale.php index 2458591..3c382cc 100644 --- a/edit_sale.php +++ b/edit_sale.php @@ -24,7 +24,7 @@ $isEidSale = (($editSale['order_type'] ?? 'standard') === 'eid'); $activeNav = $isEidSale ? 'eid_orders' : 'sales'; $error = ''; $editPaymentSummary = sale_payment_summary($editSale); -$paymentAmountInput = (string) ($_POST['payment_amount'] ?? ($editPaymentSummary['paid_amount'] > 0 ? number_format((float) $editPaymentSummary['paid_amount'], 3, '.', '') : '')); +$paymentAmountInput = (string) ($_POST['payment_amount'] ?? number_format((float) $editPaymentSummary['paid_amount'], 3, '.', '')); $catalog = catalog(); $allowedBranches = get_user_branches($user); $deliveryOptions = eid_delivery_status_options(); @@ -45,6 +45,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $customerName = trim((string) ($_POST['customer_name'] ?? '')); $paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash')); $paymentAmountInput = trim((string) ($_POST['payment_amount'] ?? '')); + $paymentAmountForCalculation = $paymentAmountInput === '' ? '0' : $paymentAmountInput; $saleStatus = trim((string) ($_POST['sale_status'] ?? 'completed')); $saleStatusInput = $saleStatus; $notes = trim((string) ($_POST['notes'] ?? '')); @@ -106,7 +107,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($paymentAmountInput !== '' && !is_numeric($paymentAmountInput)) { $error = tr('أدخل مبلغاً مدفوعاً صحيحاً.', 'Enter a valid paid amount.'); } else { - $paymentMeta = sale_payment_breakdown($totalAmount, $paymentMethod, $paymentAmountInput); + $paymentMeta = sale_payment_breakdown($totalAmount, $paymentMethod, $paymentAmountForCalculation); if ($paymentMeta['due_amount'] > 0.0005 && !$customerId) { $error = tr('يجب اختيار عميل مسجل عند وجود مبلغ متبقٍ أو دفعة جزئية.', 'Select a registered customer when there is a remaining balance or partial payment.'); } @@ -186,7 +187,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } set_flash($flashType, $flashMessage); - redirect_to('sale.php', ['id' => $editSaleId]); + redirect_to($isEidSale ? 'eid_orders.php' : 'sales.php'); } } } @@ -482,7 +483,21 @@ require __DIR__ . '/includes/header.php';
-
+
+
+
+
+
0.000
+
+
+
+
+
+
0.000
+
+
+
+
@@ -802,15 +817,28 @@ function renderInvoice() { cartJson.value = JSON.stringify(cartData); } +function formatMoney(value) { + return value.toFixed(3) + currencySuffix; +} + function updatePaymentAmountHint() { const paymentAmountField = document.getElementById('payment_amount'); const paymentAmountHint = document.getElementById('paymentAmountHint'); + const displayPaidAmountLive = document.getElementById('displayPaidAmountLive'); + const displayDueAmountLive = document.getElementById('displayDueAmountLive'); if (!paymentAmountField || !paymentAmountHint) { return; } const entered = Math.max(0, parseFloat(paymentAmountField.value || '0') || 0); - const due = Math.max(0, currentInvoiceTotal - Math.min(entered, currentInvoiceTotal)); - paymentAmountHint.innerText = partialPaymentText + ' ' + due.toFixed(3) + currencySuffix; + const paid = Math.min(entered, currentInvoiceTotal); + const due = Math.max(0, currentInvoiceTotal - paid); + paymentAmountHint.innerText = partialPaymentText + ' ' + formatMoney(due); + if (displayPaidAmountLive) { + displayPaidAmountLive.innerText = formatMoney(paid); + } + if (displayDueAmountLive) { + displayDueAmountLive.innerText = formatMoney(due); + } } function syncPaymentAmount(force = false) { diff --git a/eid_orders.php b/eid_orders.php index 5eb8bac..78bac6b 100644 --- a/eid_orders.php +++ b/eid_orders.php @@ -481,7 +481,7 @@ require __DIR__ . '/includes/header.php';
-
+ +