From 8df558e09d36b196ae73261e713ab00b22fb1298 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 4 May 2026 04:58:07 +0000 Subject: [PATCH] Autosave: 20260504-045808 --- debts.php | 221 +++++++++++++++++++++++++++++++++-------- includes/sale_form.php | 48 ++++++++- 2 files changed, 225 insertions(+), 44 deletions(-) diff --git a/debts.php b/debts.php index f28f5ed..f9d3a3b 100644 --- a/debts.php +++ b/debts.php @@ -8,6 +8,11 @@ $pdo = db(); $debtsLoadError = ''; $unpaidSales = []; $debtsByCustomer = []; +$customerNameFilter = trim((string) ($_GET['customer_name'] ?? '')); +$phoneFilter = trim((string) ($_GET['phone'] ?? '')); +$dateFrom = trim((string) ($_GET['date_from'] ?? '')); +$dateTo = trim((string) ($_GET['date_to'] ?? '')); +$hasFilters = $customerNameFilter !== '' || $phoneFilter !== '' || $dateFrom !== '' || $dateTo !== ''; // Handle legacy mark-as-paid shortcut if (isset($_GET['mark_paid'])) { @@ -53,6 +58,7 @@ try { 'COALESCE(s.due_amount, s.total_amount) AS due_amount', isset($salesColumns['customer_id']) ? 's.customer_id' : 'NULL AS customer_id', isset($salesColumns['customer_name']) ? 's.customer_name' : 'NULL AS customer_name', + isset($salesColumns['status']) ? "COALESCE(s.status, 'completed') AS status" : "'completed' AS status", 'NULL AS c_name', 'NULL AS c_phone', ]; @@ -61,28 +67,95 @@ try { if ($hasCustomersTable && isset($salesColumns['customer_id']) && isset($customerColumns['id'])) { $joinSql = ' LEFT JOIN customers c ON s.customer_id = c.id '; if (isset($customerColumns['name'])) { - $selectParts[9] = 'c.name AS c_name'; + $selectParts[10] = 'c.name AS c_name'; } if (isset($customerColumns['phone'])) { - $selectParts[10] = 'c.phone AS c_phone'; + $selectParts[11] = 'c.phone AS c_phone'; } } + $params = []; + $whereParts = []; if (isset($salesColumns['payment_status'])) { - $whereSql = " WHERE s.payment_status IN ('unpaid', 'partial')"; + $whereParts[] = "s.payment_status IN ('unpaid', 'partial')"; } elseif (isset($salesColumns['payment_method'])) { - $whereSql = " WHERE s.payment_method = 'pay_later'"; + $whereParts[] = "s.payment_method = 'pay_later'"; } else { - $whereSql = ' WHERE 1 = 0'; + $whereParts[] = '1 = 0'; } + if ($customerNameFilter !== '') { + $nameConditions = []; + if ($hasCustomersTable && isset($customerColumns['name'])) { + $nameConditions[] = 'c.name LIKE :customer_name_customer'; + $params[':customer_name_customer'] = '%' . $customerNameFilter . '%'; + } + if (isset($salesColumns['customer_name'])) { + $nameConditions[] = 's.customer_name LIKE :customer_name_sale'; + $params[':customer_name_sale'] = '%' . $customerNameFilter . '%'; + } + if ($nameConditions !== []) { + $whereParts[] = '(' . implode(' OR ', $nameConditions) . ')'; + } + } + + if ($phoneFilter !== '') { + $rawDigits = phone_digits($phoneFilter); + $normalizedPhone = normalize_oman_phone($phoneFilter); + $phoneVariants = []; + if ($rawDigits !== '') { + $phoneVariants[] = $rawDigits; + } + if ($normalizedPhone !== '') { + $phoneVariants[] = $normalizedPhone; + $phoneVariants[] = '0' . $normalizedPhone; + $phoneVariants[] = '968' . $normalizedPhone; + $phoneVariants[] = '00968' . $normalizedPhone; + } + $phoneVariants = array_values(array_unique(array_filter($phoneVariants, static fn($value) => $value !== ''))); + + $phoneColumns = []; + if ($hasCustomersTable && isset($customerColumns['phone'])) { + $phoneColumns[] = "REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(c.phone, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '')"; + } + if (isset($salesColumns['customer_name'])) { + $phoneColumns[] = "REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(s.customer_name, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '')"; + } + + if ($phoneColumns !== [] && $phoneVariants !== []) { + $phoneConditions = []; + foreach ($phoneColumns as $columnIndex => $columnExpression) { + foreach ($phoneVariants as $variantIndex => $variant) { + $paramKey = ':phone_' . $columnIndex . '_' . $variantIndex; + $phoneConditions[] = $columnExpression . ' LIKE ' . $paramKey; + $params[$paramKey] = '%' . $variant . '%'; + } + } + $whereParts[] = '(' . implode(' OR ', $phoneConditions) . ')'; + } + } + + if ($dateFrom !== '') { + $whereParts[] = 'DATE(s.sale_date) >= :date_from'; + $params[':date_from'] = $dateFrom; + } + if ($dateTo !== '') { + $whereParts[] = 'DATE(s.sale_date) <= :date_to'; + $params[':date_to'] = $dateTo; + } + + $whereSql = ' WHERE ' . implode(' AND ', $whereParts); $sqlUnpaid = 'SELECT ' . implode(', ', $selectParts) . ' FROM sales_orders s' . $joinSql . $whereSql . ' ORDER BY s.sale_date DESC'; - $stmtUnpaid = $pdo->query($sqlUnpaid); - $unpaidSales = $stmtUnpaid ? $stmtUnpaid->fetchAll(PDO::FETCH_ASSOC) : []; + $stmtUnpaid = $pdo->prepare($sqlUnpaid); + foreach ($params as $key => $value) { + $stmtUnpaid->bindValue($key, $value); + } + $stmtUnpaid->execute(); + $unpaidSales = $stmtUnpaid->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { $debtsLoadError = tr( 'تعذر تحميل صفحة الديون بسبب اختلاف في هيكل قاعدة البيانات. احفظ التعديلات ثم أنشئ نسخة جديدة وأعد التحديث.', @@ -90,16 +163,48 @@ try { ); } +$extractCustomerContact = static function (array $sale): array { + $sourceName = trim((string) ($sale['customer_name'] ?? '')); + $displayName = trim((string) ($sale['c_name'] ?? '')); + if ($displayName === '') { + $displayName = $sourceName; + } + + $displayPhone = trim((string) ($sale['c_phone'] ?? '')); + if ($displayPhone === '' && str_contains($sourceName, ' - ')) { + $parts = explode(' - ', $sourceName); + $lastPart = trim((string) end($parts)); + if (preg_match('/^[0-9+\s]+$/', $lastPart)) { + $displayPhone = $lastPart; + array_pop($parts); + if ($displayName === '' || $displayName === $sourceName) { + $displayName = trim(implode(' - ', $parts)); + } + } + } + + $displayPhone = phone_display($displayPhone); + if ($displayName === '') { + $displayName = tr('عميل غير معروف', 'Unknown Customer'); + } + + return [ + 'name' => $displayName, + 'phone' => $displayPhone, + ]; +}; + // Aggregate by customer foreach ($unpaidSales as $sale) { + $customerContact = $extractCustomerContact($sale); $cId = $sale['customer_id'] ?? 'unknown'; if (!isset($debtsByCustomer[$cId])) { $debtsByCustomer[$cId] = [ - 'name' => $sale['c_name'] ?: $sale['customer_name'] ?: tr('عميل غير معروف', 'Unknown Customer'), - 'phone' => $sale['c_phone'] ?: '', + 'name' => $customerContact['name'], + 'phone' => $customerContact['phone'], 'total' => 0.0, 'open_invoices' => 0, - 'partial_invoices' => 0 + 'partial_invoices' => 0, ]; } $saleSummary = sale_payment_summary($sale); @@ -126,6 +231,60 @@ require_once 'includes/header.php'; +
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + +
+ +
+
+ +
+
+ +
+
+
+
@@ -135,7 +294,7 @@ require_once 'includes/header.php';
-
+
    @@ -143,7 +302,7 @@ require_once 'includes/header.php';
    -
    +
    0): ?> @@ -184,42 +343,26 @@ require_once 'includes/header.php'; - + - + - 8) { - $displayPhone = substr($displayPhone, 3); - } - } - ?> - - - - - - + + + + + + diff --git a/includes/sale_form.php b/includes/sale_form.php index a9563f5..cb4210d 100644 --- a/includes/sale_form.php +++ b/includes/sale_form.php @@ -21,6 +21,15 @@ $selectedDeliveryStatus = trim((string) ($_POST['delivery_status'] ?? ($isEidOrd $deliveryDateInput = trim((string) ($_POST['delivery_date'] ?? '')); $saleStatusInput = trim((string) ($_POST['sale_status'] ?? ($isEidOrder ? 'order' : 'completed'))); $notesInput = trim((string) ($_POST['notes'] ?? '')); +$notesLabel = $isEidOrder + ? tr('ملاحظة داخلية للفاتورة', 'Internal invoice notice') + : tr('ملاحظات (اختياري)', 'Notes (Optional)'); +$notesPlaceholder = $isEidOrder + ? tr('مثال: بدون مكسرات، تغليف خاص، اتصال قبل التسليم...', 'Example: no nuts, special packing, call before delivery...') + : tr('أي ملاحظات إضافية...', 'Any additional notes...'); +$notesHelper = $isEidOrder + ? tr('هذه الملاحظة داخلية وتبقى في النظام فقط، ولن تظهر في الإيصال المطبوع.', 'This note is internal, stays in the system only, and will not appear on the printed receipt.') + : tr('سيتم حفظ هذه الملاحظات مع الفاتورة داخل النظام.', 'These notes will be saved with the invoice inside the system.'); try { $customers = db()->query('SELECT id, name, phone FROM customers ORDER BY name ASC')->fetchAll(); @@ -340,6 +349,32 @@ require __DIR__ . '/header.php'; color: #212529; margin-bottom: 0; } +.inline-notice-box { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px dashed #d9e3f0; +} +.inline-notice-box .form-label { + display: flex; + align-items: center; + gap: 0.45rem; + margin-bottom: 0.5rem; +} +.inline-notice-box .notice-icon { + width: 1.9rem; + height: 1.9rem; + border-radius: 999px; + display: inline-flex; + align-items: center; + justify-content: center; + color: #92400e; + background: rgba(245, 158, 11, 0.14); + flex-shrink: 0; +} +.inline-notice-box textarea { + background: #fff; + resize: vertical; +} .line-input { min-width: 0; } @@ -496,11 +531,6 @@ require __DIR__ . '/header.php';
-
- - -
-
@@ -515,6 +545,14 @@ require __DIR__ . '/header.php'; 0.000
+
+ + +
+