Autosave: 20260420-113152

This commit is contained in:
Flatlogic Bot 2026-04-20 11:31:42 +00:00
parent 5f9eadae19
commit 017bae675e
10 changed files with 296 additions and 17 deletions

156
debts.php Normal file
View File

@ -0,0 +1,156 @@
<?php
require_once 'includes/app.php';
$user = require_auth();
$activeNav = 'debts';
$pageTitle = tr('الديون والفواتير الآجلة', 'Debts & Unpaid Bills');
require_once 'includes/header.php';
$pdo = db();
// Handle Mark as Paid
if (isset($_GET['mark_paid'])) {
$id = (int)$_GET['mark_paid'];
try {
$pdo->prepare("UPDATE sales_orders SET payment_status = 'paid', status = 'completed' WHERE id = ?")->execute([$id]);
set_flash('success', tr('تم استلام المبلغ بنجاح.', 'Payment received successfully.'));
} catch (Throwable $e) {
set_flash('danger', tr('خطأ أثناء التحديث.', 'Error updating.'));
}
redirect_to('debts.php');
}
// Fetch all unpaid sales
$sqlUnpaid = "SELECT s.*, c.name as c_name, c.phone as c_phone
FROM sales_orders s
LEFT JOIN customers c ON s.customer_id = c.id
WHERE s.payment_status = 'unpaid'
ORDER BY s.sale_date DESC";
$stmtUnpaid = $pdo->query($sqlUnpaid);
$unpaidSales = $stmtUnpaid->fetchAll(PDO::FETCH_ASSOC);
// Aggregate by customer
$debtsByCustomer = [];
foreach ($unpaidSales as $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'] ?: '',
'total' => 0.0,
'count' => 0
];
}
$debtsByCustomer[$cId]['total'] += (float)$sale['total_amount'];
$debtsByCustomer[$cId]['count'] += 1;
}
// Sort by highest debt
uasort($debtsByCustomer, fn($a, $b) => $b['total'] <=> $a['total']);
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0 text-gray-800"><?= h($pageTitle) ?></h1>
</div>
<div class="row">
<!-- Debts by Customer -->
<div class="col-lg-4 mb-4">
<div class="card shadow-sm border-0 h-100">
<div class="card-header bg-white border-bottom-0 pt-4 pb-0">
<h6 class="m-0 font-weight-bold text-primary"><i class="bi bi-people"></i> <?= h(tr('الديون حسب العميل', 'Debts by Customer')) ?></h6>
</div>
<div class="card-body">
<?php if (empty($debtsByCustomer)): ?>
<div class="text-center text-muted py-4"><?= h(tr('لا توجد ديون مسجلة.', 'No debts recorded.')) ?></div>
<?php else: ?>
<ul class="list-group list-group-flush">
<?php foreach ($debtsByCustomer as $debt): ?>
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
<div>
<strong><?= h($debt['name']) ?></strong>
<?php if ($debt['phone']): ?>
<div class="small text-muted"><?= h($debt['phone']) ?></div>
<?php endif; ?>
<div class="small text-muted"><?= h($debt['count']) ?> <?= h(tr('فواتير', 'bills')) ?></div>
</div>
<span class="badge bg-danger rounded-pill fs-6"><?= h(currency($debt['total'])) ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</div>
</div>
<!-- Unpaid Invoices -->
<div class="col-lg-8 mb-4">
<div class="card shadow-sm border-0 h-100">
<div class="card-header bg-white border-bottom-0 pt-4 pb-0">
<h6 class="m-0 font-weight-bold text-primary"><i class="bi bi-receipt"></i> <?= h(tr('الفواتير غير المدفوعة', 'Unpaid Bills')) ?></h6>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th><?= h(tr('رقم الفاتورة', 'Receipt No')) ?></th>
<th><?= h(tr('العميل', 'Customer')) ?></th>
<th><?= h(tr('التاريخ', 'Date')) ?></th>
<th><?= h(tr('المبلغ', 'Amount')) ?></th>
<th><?= h(tr('الإجراء', 'Action')) ?></th>
</tr>
</thead>
<tbody>
<?php if (empty($unpaidSales)): ?>
<tr>
<td colspan="5" class="text-center py-4 text-muted"><?= h(tr('لا توجد فواتير غير مدفوعة.', 'No unpaid bills.')) ?></td>
</tr>
<?php else: ?>
<?php foreach ($unpaidSales as $sale): ?>
<tr>
<td>
<a href="<?= h(url_for('sale.php', ['id' => $sale['id']])) ?>" class="fw-bold text-decoration-none">
<?= h($sale['receipt_no']) ?>
</a>
</td>
<td>
<?= h($sale['c_name'] ?: $sale['customer_name'] ?: '-') ?>
</td>
<td><?= h(date('Y-m-d', strtotime((string)$sale['sale_date']))) ?></td>
<td class="fw-bold text-danger"><?= h(currency((float)$sale['total_amount'])) ?></td>
<td>
<button class="btn btn-sm btn-outline-success rounded-pill px-3" onclick="markPaid(<?= $sale['id'] ?>)">
<i class="bi bi-cash-coin"></i> <?= h(tr('استلام', 'Receive')) ?>
</button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script>
function markPaid(id) {
Swal.fire({
title: "<?= h(tr('تأكيد استلام المبلغ؟', 'Confirm payment receipt?')) ?>",
text: "<?= h(tr('سيتم تحويل هذه الفاتورة إلى مدفوعة.', 'This bill will be marked as paid.')) ?>",
icon: "question",
showCancelButton: true,
confirmButtonColor: "#198754",
confirmButtonText: "<?= h(tr('نعم، تم الاستلام', 'Yes, Received')) ?>",
cancelButtonText: "<?= h(tr('إلغاء', 'Cancel')) ?>"
}).then((result) => {
if (result.isConfirmed) {
window.location.href = "debts.php?mark_paid=" + id;
}
});
}
</script>
<?php require_once 'includes/footer.php'; ?>

View File

@ -30,8 +30,10 @@ try {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$branchCode = trim((string) ($_POST['branch_code'] ?? ''));
$customerId = isset($_POST['customer_id']) && $_POST['customer_id'] !== '' ? (int)$_POST['customer_id'] : null;
$customerName = trim((string) ($_POST['customer_name'] ?? ''));
$paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash'));
$paymentStatus = ($paymentMethod === 'pay_later') ? 'unpaid' : 'paid';
$saleStatus = trim((string) ($_POST['sale_status'] ?? 'completed'));
$notes = trim((string) ($_POST['notes'] ?? ''));
$cartJson = (string) ($_POST['cart_json'] ?? '[]');
@ -39,7 +41,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!in_array($branchCode, $allowedBranches, true)) {
$error = tr('اختر فرعاً صالحاً لهذه الصلاحية.', 'Choose a valid branch for this role.');
} elseif (!in_array($paymentMethod, ['cash', 'card', 'transfer'], true)) {
} elseif (!in_array($paymentMethod, ['cash', 'card', 'transfer', 'pay_later'], true)) {
$error = tr('اختر طريقة دفع صحيحة.', 'Choose a valid payment method.');
} elseif (!is_array($items) || $items === []) {
$error = tr('أضف صنفاً واحداً على الأقل إلى الفاتورة.', 'Add at least one item to the invoice.');
@ -82,8 +84,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$cashierName = current_lang() === 'ar' ? $user['name_ar'] : $user['name_en'];
$stmt = db()->prepare('UPDATE sales_orders SET
branch_code = :branch_code,
customer_id = :customer_id,
customer_name = :customer_name,
payment_method = :payment_method,
payment_status = :payment_status,
items_json = :items_json,
item_count = :item_count,
subtotal = :subtotal,
@ -94,8 +98,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
WHERE id = :id');
$stmt->execute([
':branch_code' => $branchCode,
':customer_id' => $customerId,
':customer_name' => $customerName !== '' ? $customerName : null,
':payment_method' => $paymentMethod,
':payment_status' => $paymentStatus,
':items_json' => json_encode($normalized, JSON_UNESCAPED_UNICODE),
':item_count' => $itemCount,
':subtotal' => $subtotal,
@ -355,6 +361,7 @@ require __DIR__ . '/includes/header.php';
<div class="mb-3 position-relative">
<label class="form-label"><?= h(tr('العميل', 'Customer')) ?></label>
<div class="input-group">
<input type="hidden" id="formCustomerId" name="customer_id" value="<?= h($editSale['customer_id'] ?? '' ) ?>">
<input type="text" id="formCustomer" name="customer_name" class="form-control custom-input" style="border-right-width: 1px;" placeholder="<?= h(tr('بحث (اسم أو هاتف)', 'Search (Name or Phone)')) ?>" autocomplete="off" value="<?= h($editSale['customer_name'] ?? '' ) ?>">
<button class="btn btn-outline-primary px-3" style="border-radius: 0 8px 8px 0;" type="button" onclick="openNewCustomerModal()" title="<?= h(tr('إضافة عميل', 'Add Customer')) ?>">
<i class="bi bi-person-plus-fill"></i>
@ -375,6 +382,7 @@ require __DIR__ . '/includes/header.php';
<option value="cash" <?= $editSale['payment_method'] === 'cash' ? 'selected' : '' ?>><?= h(tr('نقداً', 'Cash')) ?></option>
<option value="card" <?= $editSale['payment_method'] === 'card' ? 'selected' : '' ?>><?= h(tr('بطاقة ائتمان', 'Credit Card')) ?></option>
<option value="transfer" <?= $editSale['payment_method'] === 'transfer' ? 'selected' : '' ?>><?= h(tr('تحويل بنكي', 'Bank Transfer')) ?></option>
<option value="pay_later" <?= $editSale['payment_method'] === 'pay_later' ? 'selected' : '' ?>><?= h(tr('آجل (Pay Later)', 'Pay Later')) ?></option>
</select>
</div>
<div class="mb-4">
@ -530,6 +538,7 @@ async function saveNewCustomer() {
if (data.success) {
customersData.push(data.customer);
custInput.value = data.customer.name + (data.customer.phone ? ' - ' + data.customer.phone : '');
document.getElementById('formCustomerId').value = data.customer.id;
newCustomerModalObj.hide();
const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 2000 });
Toast.fire({ icon: 'success', title: '<?= h(tr('تم إضافة العميل', 'Customer added')) ?>' });

View File

@ -20,6 +20,14 @@ try {
if ($stmt2->rowCount() === 0) {
$pdo->exec("ALTER TABLE branches ADD COLUMN avatar varchar(255) DEFAULT NULL");
}
$stmt3 = $pdo->query("SHOW COLUMNS FROM sales_orders LIKE 'customer_id'");
if ($stmt3->rowCount() === 0) {
$pdo->exec("ALTER TABLE sales_orders ADD COLUMN customer_id int(10) unsigned DEFAULT NULL");
}
$stmt4 = $pdo->query("SHOW COLUMNS FROM sales_orders LIKE 'payment_status'");
if ($stmt4->rowCount() === 0) {
$pdo->exec("ALTER TABLE sales_orders ADD COLUMN payment_status varchar(20) NOT NULL DEFAULT 'paid'");
}
@file_put_contents($flagFile, '1');
}
} catch (\Throwable $e) {}
@ -323,8 +331,10 @@ function ensure_sales_table(): void
cashier_username VARCHAR(60) NOT NULL,
cashier_name VARCHAR(120) NOT NULL,
role_name VARCHAR(40) NOT NULL,
customer_id INT(10) UNSIGNED DEFAULT NULL,
customer_name VARCHAR(120) DEFAULT NULL,
payment_method VARCHAR(30) NOT NULL,
payment_status VARCHAR(20) NOT NULL DEFAULT 'paid',
items_json LONGTEXT NOT NULL,
item_count INT UNSIGNED NOT NULL DEFAULT 0,
subtotal DECIMAL(10,2) NOT NULL DEFAULT 0,
@ -346,9 +356,9 @@ function create_sale(array $data): int
ensure_sales_table();
$stmt = db()->prepare('INSERT INTO sales_orders
(receipt_no, sale_mode, branch_code, cashier_username, cashier_name, role_name, customer_name, payment_method, items_json, item_count, subtotal, vat_amount, total_amount, status, notes, sale_date)
(receipt_no, sale_mode, branch_code, cashier_username, cashier_name, role_name, customer_id, customer_name, payment_method, payment_status, items_json, item_count, subtotal, vat_amount, total_amount, status, notes, sale_date)
VALUES
(:receipt_no, :sale_mode, :branch_code, :cashier_username, :cashier_name, :role_name, :customer_name, :payment_method, :items_json, :item_count, :subtotal, :vat_amount, :total_amount, :status, :notes, NOW())');
(:receipt_no, :sale_mode, :branch_code, :cashier_username, :cashier_name, :role_name, :customer_id, :customer_name, :payment_method, :payment_status, :items_json, :item_count, :subtotal, :vat_amount, :total_amount, :status, :notes, NOW())');
$stmt->bindValue(':receipt_no', $data['receipt_no']);
$stmt->bindValue(':sale_mode', $data['sale_mode']);
@ -356,8 +366,10 @@ function create_sale(array $data): int
$stmt->bindValue(':cashier_username', $data['cashier_username']);
$stmt->bindValue(':cashier_name', $data['cashier_name']);
$stmt->bindValue(':role_name', $data['role_name']);
$stmt->bindValue(':customer_id', $data['customer_id'] ?? null, PDO::PARAM_INT);
$stmt->bindValue(':customer_name', $data['customer_name']);
$stmt->bindValue(':payment_method', $data['payment_method']);
$stmt->bindValue(':payment_status', $data['payment_status'] ?? 'paid');
$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']);

View File

@ -96,13 +96,13 @@ $isPublic = !empty($forcePublic) || !isset($user) || !$user;
<?php if (has_permission('sales', 'show') || has_permission('normal_sale', 'show') || has_permission('pos', 'show')): ?>
<!-- المبيعات (Sales) - Now Collapsible -->
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['sales', 'normal', 'pos']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapseSales" role="button" aria-expanded="<?= in_array($activeNav, ['sales', 'normal', 'pos']) ? 'true' : 'false' ?>" aria-controls="collapseSales">
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['sales', 'unpaid', 'normal', 'pos']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapseSales" role="button" aria-expanded="<?= in_array($activeNav, ['sales', 'normal', 'pos']) ? 'true' : 'false' ?>" aria-controls="collapseSales">
<div class="d-flex justify-content-between align-items-center w-100">
<span><i class="bi bi-cart"></i> <?= h(tr('المبيعات', 'Sales')) ?></span>
<i class="bi bi-chevron-down toggle-icon" style="transition: transform 0.2s;"></i>
</div>
</a>
<div class="collapse <?= in_array($activeNav, ['sales', 'normal', 'pos']) ? 'show' : '' ?>" id="collapseSales">
<div class="collapse <?= in_array($activeNav, ['sales', 'unpaid', 'normal', 'pos']) ? 'show' : '' ?>" id="collapseSales">
<div class="list-group list-group-flush" style="background-color: rgba(0,0,0,0.15);">
<a class="list-group-item list-group-item-action <?= $activeNav === 'sales' ? 'active' : '' ?>" href="<?= h(url_for('sales.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
<i class="bi bi-dot"></i> <?= h(tr('قائمة الفواتير', 'Invoice list')) ?>
@ -168,6 +168,9 @@ $isPublic = !empty($forcePublic) || !isset($user) || !$user;
<a class="list-group-item list-group-item-action <?= $activeNav === 'customers' ? 'active' : '' ?>" href="<?= h(url_for('customers.php')) ?>">
<i class="bi bi-people-fill"></i> <?= h(tr('العملاء', 'Customers')) ?>
</a>
<a class="list-group-item list-group-item-action <?= $activeNav === 'debts' ? 'active' : '' ?>" href="<?= h(url_for('debts.php')) ?>">
<i class="bi bi-journal-text"></i> <?= h(tr('الديون والفواتير الآجلة', 'Debts & Unpaid')) ?>
</a>
<?php if (has_permission('reports', 'show')): ?>
<a class="list-group-item list-group-item-action <?= $activeNav === 'reports' ? 'active' : '' ?>" href="<?= h(url_for('reports.php')) ?>">
<i class="bi bi-bar-chart"></i> <?= h(tr('التقارير', 'Reports')) ?>

View File

@ -15,8 +15,10 @@ try {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$branchCode = trim((string) ($_POST['branch_code'] ?? ''));
$customerId = isset($_POST['customer_id']) && $_POST['customer_id'] !== '' ? (int)$_POST['customer_id'] : null;
$customerName = trim((string) ($_POST['customer_name'] ?? ''));
$paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash'));
$paymentStatus = ($paymentMethod === 'pay_later') ? 'unpaid' : 'paid';
$saleStatus = trim((string) ($_POST['sale_status'] ?? 'completed'));
$notes = trim((string) ($_POST['notes'] ?? ''));
$cartJson = (string) ($_POST['cart_json'] ?? '[]');
@ -24,7 +26,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!in_array($branchCode, $allowedBranches, true)) {
$error = tr('اختر فرعاً صالحاً لهذه الصلاحية.', 'Choose a valid branch for this role.');
} elseif (!in_array($paymentMethod, ['cash', 'card', 'transfer'], true)) {
} elseif (!in_array($paymentMethod, ['cash', 'card', 'transfer', 'pay_later'], true)) {
$error = tr('اختر طريقة دفع صحيحة.', 'Choose a valid payment method.');
} elseif (!is_array($items) || $items === []) {
$error = tr('أضف صنفاً واحداً على الأقل إلى الفاتورة.', 'Add at least one item to the invoice.');
@ -73,8 +75,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'cashier_username' => $user['username'],
'cashier_name' => $cashierName,
'role_name' => $user['role'],
'customer_id' => $customerId,
'customer_name' => $customerName !== '' ? $customerName : null,
'payment_method' => $paymentMethod,
'payment_status' => $paymentStatus,
'items' => $normalized,
'item_count' => $itemCount,
'subtotal' => $subtotal,
@ -336,6 +340,7 @@ require __DIR__ . '/header.php';
<div class="mb-3 position-relative">
<label class="form-label"><?= h(tr('العميل', 'Customer')) ?></label>
<div class="input-group">
<input type="hidden" id="formCustomerId" name="customer_id">
<input type="text" id="formCustomer" name="customer_name" class="form-control custom-input" style="border-right-width: 1px;" placeholder="<?= h(tr('بحث (اسم أو هاتف)', 'Search (Name or Phone)')) ?>" autocomplete="off">
<button class="btn btn-outline-primary px-3" style="border-radius: 0 8px 8px 0;" type="button" onclick="openNewCustomerModal()" title="<?= h(tr('إضافة عميل', 'Add Customer')) ?>">
<i class="bi bi-person-plus-fill"></i>
@ -356,6 +361,7 @@ require __DIR__ . '/header.php';
<option value="cash"><?= h(tr('نقداً', 'Cash')) ?></option>
<option value="card"><?= h(tr('بطاقة ائتمان', 'Credit Card')) ?></option>
<option value="transfer"><?= h(tr('تحويل بنكي', 'Bank Transfer')) ?></option>
<option value="pay_later"><?= h(tr('آجل (Pay Later)', 'Pay Later')) ?></option>
</select>
</div>
<div class="mb-4">
@ -436,7 +442,7 @@ custInput.addEventListener('input', function() {
const q = this.value.toLowerCase().trim();
custDropdown.innerHTML = '';
if (q.length < 2) {
custDropdown.classList.remove('show');
document.getElementById('formCustomerId').value = c.id; custDropdown.classList.remove('show');
return;
}
@ -452,19 +458,19 @@ custInput.addEventListener('input', function() {
div.innerHTML = `<strong>${c.name}</strong> ${c.phone ? '<small class="text-muted ms-2">'+c.phone+'</small>' : ''}`;
div.onclick = function() {
custInput.value = c.name + (c.phone ? ' - ' + c.phone : '');
custDropdown.classList.remove('show');
document.getElementById('formCustomerId').value = c.id; custDropdown.classList.remove('show');
};
custDropdown.appendChild(div);
});
custDropdown.classList.add('show');
} else {
custDropdown.classList.remove('show');
document.getElementById('formCustomerId').value = c.id; custDropdown.classList.remove('show');
}
});
document.addEventListener('click', function(e) {
if (!custInput.contains(e.target) && !custDropdown.contains(e.target)) {
custDropdown.classList.remove('show');
document.getElementById('formCustomerId').value = c.id; custDropdown.classList.remove('show');
}
});
@ -499,6 +505,7 @@ async function saveNewCustomer() {
if (data.success) {
customersData.push(data.customer);
custInput.value = data.customer.name + (data.customer.phone ? ' - ' + data.customer.phone : '');
document.getElementById('formCustomerId').value = data.customer.id;
newCustomerModalObj.hide();
const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 2000 });
Toast.fire({ icon: 'success', title: '<?= h(tr('تم إضافة العميل', 'Customer added')) ?>' });

76
patch.php Normal file
View File

@ -0,0 +1,76 @@
<?php
$c = file_get_contents('includes/app.php');
$c = str_replace("migrated_sales_cols_", "migrated_sales_cols_v2_", $c);
// add to auto migration
$s1 = <<<S1
$stmt2 = \pdo->query("SHOW COLUMNS FROM branches LIKE 'avatar'");
if (\$stmt2->rowCount() === 0) {
\$pdo->exec("ALTER TABLE branches ADD COLUMN avatar varchar(255) DEFAULT NULL");
}
@file_put_contents(\$flagFile, '1');
S1;
$r1 = <<<R1
$stmt2 = \pdo->query("SHOW COLUMNS FROM branches LIKE 'avatar'");
if (\$stmt2->rowCount() === 0) {
\$pdo->exec("ALTER TABLE branches ADD COLUMN avatar varchar(255) DEFAULT NULL");
}
\$stmt3 = \pdo->query("SHOW COLUMNS FROM sales_orders LIKE 'customer_id'");
if (\$stmt3->rowCount() === 0) {
\$pdo->exec("ALTER TABLE sales_orders ADD COLUMN customer_id int(10) unsigned DEFAULT NULL");
}
\$stmt4 = \pdo->query("SHOW COLUMNS FROM sales_orders LIKE 'payment_status'");
if (\$stmt4->rowCount() === 0) {
\$pdo->exec("ALTER TABLE sales_orders ADD COLUMN payment_status varchar(20) NOT NULL DEFAULT 'paid'");
}
@file_put_contents(\$flagFile, '1');
R1;
$c = str_replace($s1, $r1, $c);
// add to ensure_sales_table()
$s2 = <<<S2
customer_name VARCHAR(120) DEFAULT NULL,
payment_method VARCHAR(30) NOT NULL,
S2;
$r2 = <<<R2
customer_id INT(10) UNSIGNED DEFAULT NULL,
customer_name VARCHAR(120) DEFAULT NULL,
payment_method VARCHAR(30) NOT NULL,
payment_status VARCHAR(20) NOT NULL DEFAULT 'paid',
R2;
$c = str_replace($s2, $r2, $c);
// add to create_sale
$s3 = <<<S3
(receipt_no, sale_mode, branch_code, cashier_username, cashier_name, role_name, customer_name, payment_method, items_json, item_count, subtotal, vat_amount, total_amount, status, notes, sale_date)
VALUES
(:receipt_no, :sale_mode, :branch_code, :cashier_username, :cashier_name, :role_name, :customer_name, :payment_method, :items_json, :item_count, :subtotal, :vat_amount, :total_amount, :status, :notes, NOW())
);
S3;
$r3 = <<<R3
(receipt_no, sale_mode, branch_code, cashier_username, cashier_name, role_name, customer_id, customer_name, payment_method, payment_status, items_json, item_count, subtotal, vat_amount, total_amount, status, notes, sale_date)
VALUES
(:receipt_no, :sale_mode, :branch_code, :cashier_username, :cashier_name, :role_name, :customer_id, :customer_name, :payment_method, :payment_status, :items_json, :item_count, :subtotal, :vat_amount, :total_amount, :status, :notes, NOW())
);
R3;
$c = str_replace($s3, $r3, $c);
$s4 = <<<S4
$stmt->bindValue(':customer_name', $data['customer_name']);
$stmt->bindValue(':payment_method', $data['payment_method']);
S4;
$r4 = <<<R4
$stmt->bindValue(':customer_id', $data['customer_id'] ?? null, PDO::PARAM_INT);
$stmt->bindValue(':customer_name', $data['customer_name']);
$stmt->bindValue(':payment_method', $data['payment_method']);
$stmt->bindValue(':payment_status', $data['payment_status'] ?? 'paid');
R4;
$c = str_replace($s4, $r4, $c);
file_put_contents('includes/app.php', $c);
echo "Patched includes/app.php\n";

15
pos.php
View File

@ -19,15 +19,17 @@ try {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$branchCode = trim((string) ($_POST['branch_code'] ?? ''));
$customerId = isset($_POST['customer_id']) && $_POST['customer_id'] !== '' ? (int)$_POST['customer_id'] : null;
$customerName = trim((string) ($_POST['customer_name'] ?? ''));
$paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash'));
$paymentStatus = ($paymentMethod === 'pay_later') ? 'unpaid' : 'paid';
$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)) {
} elseif (!in_array($paymentMethod, ['cash', 'card', 'transfer', 'pay_later'], true)) {
$error = tr('اختر طريقة دفع صحيحة.', 'Choose a valid payment method.');
} elseif (!is_array($items) || $items === []) {
$error = tr('أضف صنفاً واحداً على الأقل إلى السلة.', 'Add at least one item to the cart.');
@ -71,8 +73,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'cashier_username' => $user['username'],
'cashier_name' => $cashierName,
'role_name' => $user['role'],
'customer_id' => $customerId,
'customer_name' => $customerName !== '' ? $customerName : null,
'payment_method' => $paymentMethod,
'payment_status' => $paymentStatus,
'items' => $normalized,
'item_count' => $itemCount,
'subtotal' => $subtotal,
@ -466,6 +470,7 @@ require __DIR__ . '/includes/header.php';
<!-- Hidden Form for Submission -->
<form method="POST" id="checkoutForm" style="display: none;">
<input type="hidden" name="branch_code" id="inputBranch">
<input type="hidden" name="customer_id" id="inputCustomerId">
<input type="hidden" name="customer_name" id="inputCustomer">
<input type="hidden" name="payment_method" id="inputPayment">
<input type="hidden" name="notes" id="inputNotes">
@ -493,6 +498,9 @@ require __DIR__ . '/includes/header.php';
<button class="btn btn-lg btn-outline-secondary rounded-pill fw-semibold" onclick="submitSale('transfer')">
<i class="bi bi-phone me-2"></i> <?= h(tr('تحويل بنكي', 'Transfer')) ?>
</button>
<button class="btn btn-lg btn-outline-warning rounded-pill fw-semibold" onclick="submitSale('pay_later')">
<i class="bi bi-clock-history me-2"></i> <?= h(tr('آجل', 'Pay Later')) ?>
</button>
</div>
</div>
</div>
@ -571,6 +579,7 @@ custInput.addEventListener('input', function() {
a.innerHTML = `<strong>${c.name}</strong> ${c.phone ? '<small class="text-muted ms-2">'+c.phone+'</small>' : ''}`;
a.onclick = function() {
custInput.value = c.name + (c.phone ? ' - ' + c.phone : '');
custInput.dataset.id = c.id;
custDropdown.classList.add('d-none');
};
custDropdown.appendChild(a);
@ -618,6 +627,7 @@ async function saveNewCustomer() {
if (data.success) {
customersData.push(data.customer);
custInput.value = data.customer.name + (data.customer.phone ? ' - ' + data.customer.phone : '');
custInput.dataset.id = data.customer.id;
newCustomerModalObj.hide();
const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 2000 });
Toast.fire({ icon: 'success', title: '<?= h(tr('تم إضافة العميل', 'Customer added')) ?>' });
@ -777,6 +787,7 @@ function renderCart() {
function clearCart() {
cart = {};
document.getElementById('posCustomer').value = '';
delete document.getElementById('posCustomer').dataset.id;
renderCart();
}
@ -795,6 +806,7 @@ function openPaymentModal() {
function submitSale(method) {
const branch = document.getElementById('posBranch').value || '<?= h($allowedBranches[0] ?? '') ?>';
const customer = document.getElementById('posCustomer').value;
const customerId = document.getElementById('posCustomer').dataset.id || '';
const itemsArr = Object.values(cart).map(item => ({
sku: item.sku,
@ -802,6 +814,7 @@ function submitSale(method) {
}));
document.getElementById('inputBranch').value = branch;
document.getElementById('inputCustomerId').value = customerId;
document.getElementById('inputCustomer').value = customer;
document.getElementById('inputPayment').value = method;
document.getElementById('inputCart').value = JSON.stringify(itemsArr);

View File

@ -206,12 +206,10 @@ $registerNo = 'REG-01';
<span><?= h(tr('الكاشير', 'Cashier')) ?>:</span>
<span><?= h($sale['cashier_name']) ?> (<?= h($registerNo) ?>)</span>
</div>
<?php if(!empty($sale['customer_name'])): ?>
<div class="sale-info">
<span><?= h(tr('العميل', 'Customer')) ?>:</span>
<span><?= h($sale['customer_name']) ?></span>
<span><?= h((string) ($sale['customer_name'] ?: tr('عميل نقدي', 'Walk-in Customer'))) ?></span>
</div>
<?php endif; ?>
<div class="divider"></div>

View File

@ -366,7 +366,7 @@ require __DIR__ . '/includes/header.php';
<div class="invoice-parties">
<div class="party-box">
<div class="party-title"><?= h(tr('فاتورة إلى', 'Invoice To')) ?></div>
<div class="party-title"><?= h(tr('العميل', 'Customer')) ?></div>
<div class="party-info">
<h4><?= h((string) ($sale['customer_name'] ?: tr('عميل نقدي', 'Walk-in Customer'))) ?></h4>
</div>

View File

@ -164,15 +164,20 @@ require __DIR__ . '/includes/header.php';
<td class="text-muted text-danger"><?= h(currency((float) $sale['vat_amount'])) ?></td>
<td class="fw-bold text-success"><?= h(currency((float) $sale['total_amount'])) ?></td>
<td>
<?php if (($sale['status'] ?? 'completed') === 'order'): ?>
<?php if (($sale['payment_status'] ?? 'paid') === 'unpaid'): ?>
<span class="badge bg-danger text-white px-3 py-2 rounded-pill"><i class="bi bi-clock-history"></i> <?= h(tr('آجل / غير مدفوع', 'Unpaid')) ?></span>
<?php elseif (($sale['status'] ?? 'completed') === 'order'): ?>
<span class="badge bg-warning text-dark px-3 py-2 rounded-pill"><i class="bi bi-clock"></i> <?= h(tr('طلب حجز', 'Order')) ?></span>
<?php else: ?>
<span class="badge bg-success px-3 py-2 rounded-pill"><i class="bi bi-check-circle"></i> <?= h(tr('مدفوع', 'Paid')) ?></span>
<?php endif; ?>
<?php if (($sale['payment_method'] ?? '') === 'pay_later'): ?>
<small class="d-block text-muted mt-1"><?= h(tr('دفع آجل', 'Pay Later')) ?></small>
<?php endif; ?>
</td>
<td><?= h(date('Y-m-d H:i', strtotime((string) $sale['sale_date']))) ?></td>
<td>
<?php if (($sale['status'] ?? 'completed') === 'order'): ?>
<?php if (($sale['status'] ?? 'completed') === 'order' || ($sale['payment_status'] ?? 'paid') === 'unpaid'): ?>
<button class="btn btn-sm btn-outline-success rounded-circle shadow-sm me-1" style="width: 34px; height: 34px; padding: 0;" onclick="markAsPaid(<?= $sale['id'] ?>)" title="<?= h(tr('تأكيد الدفع', 'Confirm Payment')) ?>">
<i class="bi bi-check-lg"></i>
</button>