update sales
This commit is contained in:
parent
c8919d7836
commit
0700bb66f7
@ -9,6 +9,9 @@ if ($editSaleId > 0) {
|
||||
$stmt->execute([':id' => $editSaleId]);
|
||||
$editSale = $stmt->fetch();
|
||||
}
|
||||
if ($editSale) {
|
||||
$editSale['items'] = json_decode((string) ($editSale['items_json'] ?? '[]'), true) ?: [];
|
||||
}
|
||||
if (!$editSale) {
|
||||
die(tr('الفاتورة غير موجودة.', 'Invoice not found.'));
|
||||
}
|
||||
@ -96,42 +99,62 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
if ($error === '') {
|
||||
$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,
|
||||
paid_amount = :paid_amount,
|
||||
due_amount = :due_amount,
|
||||
items_json = :items_json,
|
||||
item_count = :item_count,
|
||||
subtotal = :subtotal,
|
||||
vat_amount = :vat_amount,
|
||||
total_amount = :total_amount,
|
||||
status = :status,
|
||||
notes = :notes
|
||||
WHERE id = :id');
|
||||
$stmt->execute([
|
||||
':branch_code' => $branchCode,
|
||||
':customer_id' => $customerId,
|
||||
':customer_name' => $customerName !== '' ? $customerName : null,
|
||||
':payment_method' => $paymentMethod,
|
||||
':payment_status' => $paymentMeta['payment_status'],
|
||||
':paid_amount' => $paymentMeta['paid_amount'],
|
||||
':due_amount' => $paymentMeta['due_amount'],
|
||||
':items_json' => json_encode($normalized, JSON_UNESCAPED_UNICODE),
|
||||
':item_count' => $itemCount,
|
||||
':subtotal' => $subtotal,
|
||||
':vat_amount' => $totalVat,
|
||||
':total_amount' => $totalAmount,
|
||||
':status' => $saleStatus,
|
||||
':notes' => $notes !== '' ? $notes : null,
|
||||
':id' => $editSaleId,
|
||||
]);
|
||||
|
||||
set_flash('success', tr('تم تحديث الفاتورة بنجاح.', 'Invoice updated successfully.'));
|
||||
redirect_to('sale.php', ['id' => $editSaleId]);
|
||||
db()->beginTransaction();
|
||||
try {
|
||||
sync_order_stock_reservation(
|
||||
$editSale['items'] ?? [],
|
||||
(string) ($editSale['status'] ?? 'completed'),
|
||||
$normalized,
|
||||
$saleStatus
|
||||
);
|
||||
|
||||
$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,
|
||||
paid_amount = :paid_amount,
|
||||
due_amount = :due_amount,
|
||||
items_json = :items_json,
|
||||
item_count = :item_count,
|
||||
subtotal = :subtotal,
|
||||
vat_amount = :vat_amount,
|
||||
total_amount = :total_amount,
|
||||
status = :status,
|
||||
notes = :notes
|
||||
WHERE id = :id');
|
||||
$stmt->execute([
|
||||
':branch_code' => $branchCode,
|
||||
':customer_id' => $customerId,
|
||||
':customer_name' => $customerName !== '' ? $customerName : null,
|
||||
':payment_method' => $paymentMethod,
|
||||
':payment_status' => $paymentMeta['payment_status'],
|
||||
':paid_amount' => $paymentMeta['paid_amount'],
|
||||
':due_amount' => $paymentMeta['due_amount'],
|
||||
':items_json' => json_encode($normalized, JSON_UNESCAPED_UNICODE),
|
||||
':item_count' => $itemCount,
|
||||
':subtotal' => $subtotal,
|
||||
':vat_amount' => $totalVat,
|
||||
':total_amount' => $totalAmount,
|
||||
':status' => $saleStatus,
|
||||
':notes' => $notes !== '' ? $notes : null,
|
||||
':id' => $editSaleId,
|
||||
]);
|
||||
|
||||
db()->commit();
|
||||
} catch (Throwable $e) {
|
||||
if (db()->inTransaction()) {
|
||||
db()->rollBack();
|
||||
}
|
||||
$error = tr('تعذر تحديث الفاتورة.', 'Could not update the invoice.');
|
||||
}
|
||||
|
||||
if ($error === '') {
|
||||
set_flash('success', tr('تم تحديث الفاتورة بنجاح.', 'Invoice updated successfully.'));
|
||||
redirect_to('sale.php', ['id' => $editSaleId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
180
includes/app.php
180
includes/app.php
@ -60,6 +60,35 @@ try {
|
||||
('wablas_daily_auto_last_date', '')");
|
||||
@file_put_contents($flagFileV5, '1');
|
||||
}
|
||||
|
||||
$flagFileV6 = sys_get_temp_dir() . '/.schema_migrated_v6_' . md5(__DIR__);
|
||||
if (!file_exists($flagFileV6)) {
|
||||
$pdo = db();
|
||||
$orderItemsStmt = $pdo->query("SELECT items_json FROM sales_orders WHERE status = 'order'");
|
||||
$reservedBySku = [];
|
||||
foreach ($orderItemsStmt->fetchAll(PDO::FETCH_ASSOC) as $orderRow) {
|
||||
$orderItems = json_decode((string) ($orderRow['items_json'] ?? '[]'), true) ?: [];
|
||||
foreach ($orderItems as $item) {
|
||||
$sku = (string) ($item['sku'] ?? '');
|
||||
$qty = (int) ($item['qty'] ?? 0);
|
||||
if ($sku === '' || $qty <= 0) {
|
||||
continue;
|
||||
}
|
||||
$reservedBySku[$sku] = ($reservedBySku[$sku] ?? 0) + $qty;
|
||||
}
|
||||
}
|
||||
|
||||
if ($reservedBySku !== []) {
|
||||
$adjustStmt = $pdo->prepare("UPDATE items SET base_stock = base_stock - :qty WHERE sku = :sku");
|
||||
foreach ($reservedBySku as $sku => $qty) {
|
||||
$adjustStmt->bindValue(':qty', $qty, PDO::PARAM_INT);
|
||||
$adjustStmt->bindValue(':sku', $sku);
|
||||
$adjustStmt->execute();
|
||||
}
|
||||
}
|
||||
|
||||
@file_put_contents($flagFileV6, '1');
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
|
||||
@ -349,7 +378,7 @@ function require_auth(): array
|
||||
return $user;
|
||||
}
|
||||
|
||||
function get_app_modules(): array { return ["pos" => ["name_ar" => "نقاط البيع", "name_en" => "POS", "actions" => ["show", "add"]], "normal_sale" => ["name_ar" => "بيع عادي", "name_en" => "Normal Sale", "actions" => ["show", "add"]], "sales" => ["name_ar" => "المبيعات", "name_en" => "Sales", "actions" => ["show", "edit", "del"]], "purchases" => ["name_ar" => "المشتريات", "name_en" => "Purchases", "actions" => ["show", "add", "edit", "del"]], "stock" => ["name_ar" => "المخزون", "name_en" => "Stock", "actions" => ["show", "add", "edit", "del"]], "reports" => ["name_ar" => "التقارير", "name_en" => "Reports", "actions" => ["show"]], "customers" => ["name_ar" => "العملاء", "name_en" => "Customers", "actions" => ["show", "add", "edit", "del"]], "suppliers" => ["name_ar" => "الموردين", "name_en" => "Suppliers", "actions" => ["show", "add", "edit", "del"]], "categories" => ["name_ar" => "التصنيفات", "name_en" => "Categories", "actions" => ["show", "add", "edit", "del"]], "units" => ["name_ar" => "الوحدات", "name_en" => "Units", "actions" => ["show", "add", "edit", "del"]], "users" => ["name_ar" => "المستخدمين", "name_en" => "Users", "actions" => ["show", "add", "edit", "del"]], "settings" => ["name_ar" => "الإعدادات", "name_en" => "Settings", "actions" => ["show", "edit"]], "expense_categories" => ["name_ar" => "تصنيفات المصروفات", "name_en" => "Expense Categories", "actions" => ["show", "add", "edit", "del"]], "expenses" => ["name_ar" => "المصروفات", "name_en" => "Expenses", "actions" => ["show", "add", "edit", "del"]]]; } function has_permission(string $m, string $a = "show"): bool { $u = current_user(); if (!$u) return false; if ($u["role"] === "owner") return true; $p = !empty($u["permissions"]) ? (is_array($u["permissions"]) ? $u["permissions"] : json_decode($u["permissions"], true)) : []; return !empty($p[$m][$a]); } function require_permission(string $m, string $a = "show"): array { $u = require_auth(); if (!has_permission($m, $a)) { set_flash("warning", tr("ليس لديك صلاحية.", "You do not have permission.")); redirect_to("index.php"); } return $u; }
|
||||
function get_app_modules(): array { return ["pos" => ["name_ar" => "نقاط البيع", "name_en" => "POS", "actions" => ["show", "add"]], "normal_sale" => ["name_ar" => "فاتورة", "name_en" => "Invoice", "actions" => ["show", "add"]], "sales" => ["name_ar" => "المبيعات", "name_en" => "Sales", "actions" => ["show", "edit", "del"]], "purchases" => ["name_ar" => "المشتريات", "name_en" => "Purchases", "actions" => ["show", "add", "edit", "del"]], "stock" => ["name_ar" => "المخزون", "name_en" => "Stock", "actions" => ["show", "add", "edit", "del"]], "reports" => ["name_ar" => "التقارير", "name_en" => "Reports", "actions" => ["show"]], "customers" => ["name_ar" => "العملاء", "name_en" => "Customers", "actions" => ["show", "add", "edit", "del"]], "suppliers" => ["name_ar" => "الموردين", "name_en" => "Suppliers", "actions" => ["show", "add", "edit", "del"]], "categories" => ["name_ar" => "التصنيفات", "name_en" => "Categories", "actions" => ["show", "add", "edit", "del"]], "units" => ["name_ar" => "الوحدات", "name_en" => "Units", "actions" => ["show", "add", "edit", "del"]], "users" => ["name_ar" => "المستخدمين", "name_en" => "Users", "actions" => ["show", "add", "edit", "del"]], "settings" => ["name_ar" => "الإعدادات", "name_en" => "Settings", "actions" => ["show", "edit"]], "expense_categories" => ["name_ar" => "تصنيفات المصروفات", "name_en" => "Expense Categories", "actions" => ["show", "add", "edit", "del"]], "expenses" => ["name_ar" => "المصروفات", "name_en" => "Expenses", "actions" => ["show", "add", "edit", "del"]]]; } function has_permission(string $m, string $a = "show"): bool { $u = current_user(); if (!$u) return false; if ($u["role"] === "owner") return true; $p = !empty($u["permissions"]) ? (is_array($u["permissions"]) ? $u["permissions"] : json_decode($u["permissions"], true)) : []; return !empty($p[$m][$a]); } function require_permission(string $m, string $a = "show"): array { $u = require_auth(); if (!has_permission($m, $a)) { set_flash("warning", tr("ليس لديك صلاحية.", "You do not have permission.")); redirect_to("index.php"); } return $u; }
|
||||
function require_roles(array $roles): array
|
||||
{
|
||||
$user = require_auth();
|
||||
@ -455,7 +484,7 @@ function currency(float $amount): string
|
||||
|
||||
function sale_mode_label(string $mode): string
|
||||
{
|
||||
return $mode === 'normal' ? tr('بيع عادي', 'Normal Sale') : tr('بيع نقاط البيع', 'POS Sale');
|
||||
return $mode === 'normal' ? tr('فاتورة', 'Invoice') : tr('بيع نقاط البيع', 'POS Sale');
|
||||
}
|
||||
|
||||
function round_money(float $amount): float
|
||||
@ -556,19 +585,32 @@ function apply_sale_payment(int $saleId, float $paymentAmount, bool $completeOrd
|
||||
$newPaidAmount = round_money($summary['paid_amount'] + $appliedAmount);
|
||||
$newDueAmount = max(0.0, round_money((float) $sale['total_amount'] - $newPaidAmount));
|
||||
$newPaymentStatus = $newDueAmount <= 0.0005 ? 'paid' : 'partial';
|
||||
$newSaleStatus = (string) ($sale['status'] ?? 'completed');
|
||||
$oldSaleStatus = (string) ($sale['status'] ?? 'completed');
|
||||
$newSaleStatus = $oldSaleStatus;
|
||||
if ($completeOrderWhenPaid && $newDueAmount <= 0.0005 && $newSaleStatus === 'order') {
|
||||
$newSaleStatus = 'completed';
|
||||
}
|
||||
|
||||
$stmt = db()->prepare('UPDATE sales_orders SET paid_amount = :paid_amount, due_amount = :due_amount, payment_status = :payment_status, status = :status WHERE id = :id');
|
||||
$stmt->execute([
|
||||
':paid_amount' => $newPaidAmount,
|
||||
':due_amount' => $newDueAmount,
|
||||
':payment_status' => $newPaymentStatus,
|
||||
':status' => $newSaleStatus,
|
||||
':id' => $saleId,
|
||||
]);
|
||||
db()->beginTransaction();
|
||||
try {
|
||||
sync_order_stock_reservation($sale['items'] ?? [], $oldSaleStatus, $sale['items'] ?? [], $newSaleStatus);
|
||||
|
||||
$stmt = db()->prepare('UPDATE sales_orders SET paid_amount = :paid_amount, due_amount = :due_amount, payment_status = :payment_status, status = :status WHERE id = :id');
|
||||
$stmt->execute([
|
||||
':paid_amount' => $newPaidAmount,
|
||||
':due_amount' => $newDueAmount,
|
||||
':payment_status' => $newPaymentStatus,
|
||||
':status' => $newSaleStatus,
|
||||
':id' => $saleId,
|
||||
]);
|
||||
|
||||
db()->commit();
|
||||
} catch (Throwable $e) {
|
||||
if (db()->inTransaction()) {
|
||||
db()->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return [
|
||||
'applied_amount' => $appliedAmount,
|
||||
@ -1191,33 +1233,93 @@ 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_id, customer_name, payment_method, payment_status, items_json, item_count, subtotal, vat_amount, total_amount, paid_amount, due_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, :paid_amount, :due_amount, :status, :notes, NOW())');
|
||||
db()->beginTransaction();
|
||||
try {
|
||||
$stmt = db()->prepare('INSERT INTO sales_orders
|
||||
(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, paid_amount, due_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, :paid_amount, :due_amount, :status, :notes, NOW())');
|
||||
|
||||
$stmt->bindValue(':receipt_no', $data['receipt_no']);
|
||||
$stmt->bindValue(':sale_mode', $data['sale_mode']);
|
||||
$stmt->bindValue(':branch_code', $data['branch_code']);
|
||||
$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']);
|
||||
$stmt->bindValue(':vat_amount', $data['vat_amount'] ?? 0.0);
|
||||
$stmt->bindValue(':total_amount', $data['total_amount']);
|
||||
$stmt->bindValue(':paid_amount', $data['paid_amount'] ?? $data['total_amount']);
|
||||
$stmt->bindValue(':due_amount', $data['due_amount'] ?? 0.0);
|
||||
$stmt->bindValue(':status', $data['status'] ?? 'completed');
|
||||
$stmt->bindValue(':notes', $data['notes']);
|
||||
$stmt->execute();
|
||||
$stmt->bindValue(':receipt_no', $data['receipt_no']);
|
||||
$stmt->bindValue(':sale_mode', $data['sale_mode']);
|
||||
$stmt->bindValue(':branch_code', $data['branch_code']);
|
||||
$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']);
|
||||
$stmt->bindValue(':vat_amount', $data['vat_amount'] ?? 0.0);
|
||||
$stmt->bindValue(':total_amount', $data['total_amount']);
|
||||
$stmt->bindValue(':paid_amount', $data['paid_amount'] ?? $data['total_amount']);
|
||||
$stmt->bindValue(':due_amount', $data['due_amount'] ?? 0.0);
|
||||
$stmt->bindValue(':status', $data['status'] ?? 'completed');
|
||||
$stmt->bindValue(':notes', $data['notes']);
|
||||
$stmt->execute();
|
||||
|
||||
return (int) db()->lastInsertId();
|
||||
$saleId = (int) db()->lastInsertId();
|
||||
sync_order_stock_reservation([], 'completed', $data['items'] ?? [], (string) ($data['status'] ?? 'completed'));
|
||||
|
||||
db()->commit();
|
||||
return $saleId;
|
||||
} catch (Throwable $e) {
|
||||
if (db()->inTransaction()) {
|
||||
db()->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function sale_item_quantities(array $items): array
|
||||
{
|
||||
$quantities = [];
|
||||
foreach ($items as $item) {
|
||||
$sku = (string) ($item['sku'] ?? '');
|
||||
$qty = (int) ($item['qty'] ?? 0);
|
||||
if ($sku === '' || $qty <= 0) {
|
||||
continue;
|
||||
}
|
||||
$quantities[$sku] = ($quantities[$sku] ?? 0) + $qty;
|
||||
}
|
||||
|
||||
return $quantities;
|
||||
}
|
||||
|
||||
function adjust_item_base_stock(array $stockDeltaBySku): void
|
||||
{
|
||||
if ($stockDeltaBySku === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = db()->prepare('UPDATE items SET base_stock = base_stock + :stock_delta WHERE sku = :sku');
|
||||
foreach ($stockDeltaBySku as $sku => $delta) {
|
||||
if ($sku === '' || $delta === 0) {
|
||||
continue;
|
||||
}
|
||||
$stmt->bindValue(':stock_delta', $delta, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':sku', (string) $sku);
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
||||
|
||||
function sync_order_stock_reservation(array $oldItems, string $oldStatus, array $newItems, string $newStatus): void
|
||||
{
|
||||
$previousReserved = $oldStatus === 'order' ? sale_item_quantities($oldItems) : [];
|
||||
$nextReserved = $newStatus === 'order' ? sale_item_quantities($newItems) : [];
|
||||
|
||||
$stockDeltaBySku = [];
|
||||
foreach ($previousReserved as $sku => $qty) {
|
||||
$stockDeltaBySku[$sku] = ($stockDeltaBySku[$sku] ?? 0) + $qty;
|
||||
}
|
||||
foreach ($nextReserved as $sku => $qty) {
|
||||
$stockDeltaBySku[$sku] = ($stockDeltaBySku[$sku] ?? 0) - $qty;
|
||||
}
|
||||
|
||||
adjust_item_base_stock($stockDeltaBySku);
|
||||
}
|
||||
|
||||
function base_sales_query_filters(array &$params, ?string $mode = null, ?string $branch = null): string
|
||||
@ -1386,6 +1488,10 @@ function stock_snapshot(): array
|
||||
$catalog = catalog();
|
||||
$sold = [];
|
||||
foreach (fetch_all_sales_for_scope() as $sale) {
|
||||
if ((string) ($sale['status'] ?? 'completed') === 'order') {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($sale['items'] as $item) {
|
||||
$sku = (string) ($item['sku'] ?? '');
|
||||
$sold[$sku] = ($sold[$sku] ?? 0) + (int) ($item['qty'] ?? 0);
|
||||
@ -1420,7 +1526,7 @@ function module_cards(): array
|
||||
{
|
||||
return [
|
||||
['title_ar' => 'نقاط البيع', 'title_en' => 'POS Sale', 'path' => 'pos.php', 'desc_ar' => 'إتمام البيع السريع مع تحديث السجل.', 'desc_en' => 'Fast checkout with instant sales logging.'],
|
||||
['title_ar' => 'بيع عادي', 'title_en' => 'Normal Sale', 'path' => 'normal_sale.php', 'desc_ar' => 'فاتورة يدوية مع العميل والملاحظات.', 'desc_en' => 'Manual invoice flow with customer details and notes.'],
|
||||
['title_ar' => 'فاتورة', 'title_en' => 'Invoice', 'path' => 'normal_sale.php', 'desc_ar' => 'فاتورة يدوية مع العميل والملاحظات.', 'desc_en' => 'Manual invoice flow with customer details and notes.'],
|
||||
['title_ar' => 'المبيعات', 'title_en' => 'Sales Ledger', 'path' => 'sales.php', 'desc_ar' => 'قائمة الفواتير مع التفاصيل والفرز.', 'desc_en' => 'Invoice list with filters and detail views.'],
|
||||
['title_ar' => 'المخزون', 'title_en' => 'Stock', 'path' => 'stock.php', 'desc_ar' => 'قراءة فورية للمخزون الحالي والتنبيهات.', 'desc_en' => 'Live stock snapshot and low-stock indicators.'],
|
||||
['title_ar' => 'المشتريات', 'title_en' => 'Purchases', 'path' => 'purchases.php', 'desc_ar' => 'واجهة مبدئية لاستلام الموردين بين الفروع.', 'desc_en' => 'Starter receiving board for suppliers and branches.'],
|
||||
|
||||
@ -96,17 +96,23 @@ $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', 'unpaid', 'normal', 'pos']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapseSales" role="button" aria-expanded="<?= in_array($activeNav, ['sales', 'unpaid', 'normal', 'pos']) ? 'true' : 'false' ?>" aria-controls="collapseSales">
|
||||
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['sales', 'sales_orders', 'unpaid', 'normal', 'pos', 'online_orders']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapseSales" role="button" aria-expanded="<?= in_array($activeNav, ['sales', 'sales_orders', 'unpaid', 'normal', 'pos', 'online_orders']) ? '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', 'unpaid', 'normal', 'pos']) ? 'show' : '' ?>" id="collapseSales" data-bs-parent="#sidebar-navigation">
|
||||
<div class="collapse <?= in_array($activeNav, ['sales', 'sales_orders', 'unpaid', 'normal', 'pos', 'online_orders']) ? 'show' : '' ?>" id="collapseSales" data-bs-parent="#sidebar-navigation">
|
||||
<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')) ?>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action <?= $activeNav === 'sales_orders' ? 'active' : '' ?>" href="<?= h(url_for('sales.php', ['status' => 'order'])) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
|
||||
<i class="bi bi-dot"></i> <?= h(tr('الطلبات', 'Orders')) ?>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action <?= $activeNav === 'online_orders' ? 'active' : '' ?>" href="<?= h(url_for('online_orders.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
|
||||
<i class="bi bi-dot"></i> <?= h(tr('طلبات المتجر', 'Store Orders')) ?>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action <?= $activeNav === 'normal' ? 'active' : '' ?>" href="<?= h(url_for('normal_sale.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
|
||||
<i class="bi bi-dot"></i> <?= h(tr('فاتورة جديدة', 'New invoice')) ?>
|
||||
</a>
|
||||
@ -162,9 +168,6 @@ $isPublic = !empty($forcePublic) || !isset($user) || !$user;
|
||||
<a class="list-group-item list-group-item-action <?= $activeNav === 'suppliers' ? 'active' : '' ?>" href="<?= h(url_for('suppliers.php')) ?>">
|
||||
<i class="bi bi-truck"></i> <?= h(tr('الموردون', 'Suppliers')) ?>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action <?= $activeNav === 'online_orders' ? 'active' : '' ?>" href="<?= h(url_for('online_orders.php')) ?>">
|
||||
<i class="bi bi-cart-check"></i> <?= h(tr('طلبات المتجر', 'Online Orders')) ?>
|
||||
</a>
|
||||
<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>
|
||||
|
||||
@ -90,7 +90,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<div class="card text-white bg-warning h-100 shadow-sm border-0" style="border-radius: 12px; background: linear-gradient(135deg, #ffc107 0%, #b38600 100%);">
|
||||
<div class="card-body d-flex flex-column justify-content-between">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<h6 class="text-white-50 text-uppercase mb-0 fw-bold"><?= h(tr('بيع عادي', 'Normal sale')) ?></h6>
|
||||
<h6 class="text-white-50 text-uppercase mb-0 fw-bold"><?= h(tr('فاتورة', 'Invoice')) ?></h6>
|
||||
<div class="p-2 bg-white bg-opacity-25 rounded"><i class="bi bi-basket fs-5 text-white"></i></div>
|
||||
</div>
|
||||
<h2 class="display-6 fw-bold mb-0"><?= h((string) $metrics['normal_count']) ?></h2>
|
||||
@ -212,7 +212,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
<a href="<?= h(url_for('normal_sale.php')) ?>" class="btn btn-outline-primary btn-lg d-flex align-items-center justify-content-between" style="border-radius: 10px;">
|
||||
<span><i class="bi bi-receipt me-2"></i> <?= h(tr('بيع عادي', 'Normal Sale')) ?></span>
|
||||
<span><i class="bi bi-receipt me-2"></i> <?= h(tr('فاتورة', 'Invoice')) ?></span>
|
||||
<i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-light btn-lg d-flex align-items-center justify-content-between border" data-bs-toggle="modal" data-bs-target="#quickNoteModal" style="border-radius: 10px;">
|
||||
|
||||
17
sales.php
17
sales.php
@ -1,13 +1,13 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$user = require_permission('sales', 'show');
|
||||
$pageTitle = tr('المبيعات', 'Sales Ledger');
|
||||
$activeNav = 'sales';
|
||||
|
||||
$mode = isset($_GET['mode']) && in_array($_GET['mode'], ['pos', 'normal'], true) ? $_GET['mode'] : null;
|
||||
$branch = isset($_GET['branch']) && array_key_exists($_GET['branch'], branches()) ? $_GET['branch'] : null;
|
||||
$search = $_GET['q'] ?? '';
|
||||
$statusFilter = $_GET['status'] ?? '';
|
||||
$activeNav = $statusFilter === 'order' ? 'sales_orders' : 'sales';
|
||||
$pageTitle = $statusFilter === 'order' ? tr('الطلبات', 'Orders') : tr('المبيعات', 'Sales Ledger');
|
||||
|
||||
if (isset($_GET['mark_paid']) && is_numeric($_GET['mark_paid'])) {
|
||||
try {
|
||||
@ -68,10 +68,13 @@ try {
|
||||
if (in_array($statusFilter, ['paid', 'partial', 'unpaid'], true)) {
|
||||
$where .= ' AND payment_status = :payment_status ';
|
||||
$params[':payment_status'] = $statusFilter;
|
||||
$where .= " AND COALESCE(status, 'completed') <> 'order' ";
|
||||
} elseif ($statusFilter === 'order') {
|
||||
$where .= " AND status = 'order' ";
|
||||
} elseif ($statusFilter === 'completed') {
|
||||
$where .= " AND status = 'completed' ";
|
||||
$where .= " AND COALESCE(status, 'completed') = 'completed' ";
|
||||
} else {
|
||||
$where .= " AND COALESCE(status, 'completed') <> 'order' ";
|
||||
}
|
||||
|
||||
// Pagination counts
|
||||
@ -104,13 +107,13 @@ require __DIR__ . '/includes/header.php';
|
||||
<section class="surface-card mb-4">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-3 mb-3">
|
||||
<div>
|
||||
<h3 class="h5 mb-1"><i class="bi bi-journal-text me-2"></i><?= h(tr('سجل الفواتير', 'Invoice ledger')) ?></h3>
|
||||
<div class="small text-muted"><?= h(tr('ابحث بصرياً في أحدث المبيعات مع صلاحيات حسب الدور والفرع.', 'Scan the latest sales with role and branch scoping.')) ?></div>
|
||||
<h3 class="h5 mb-1"><i class="bi bi-journal-text me-2"></i><?= h($statusFilter === 'order' ? tr('قائمة الطلبات', 'Orders list') : tr('سجل الفواتير', 'Invoice ledger')) ?></h3>
|
||||
<div class="small text-muted"><?= h($statusFilter === 'order' ? tr('هذه القائمة تعرض طلبات البيع العادي المحفوظة بحالة طلب.', 'This list shows normal sales saved with order status.') : tr('ابحث بصرياً في أحدث المبيعات مع صلاحيات حسب الدور والفرع.', 'Scan the latest sales with role and branch scoping.')) ?></div>
|
||||
</div>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<a class="btn btn-sm <?= $mode === null ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('sales.php', ['q' => $search, 'status' => $statusFilter])) ?>"><?= h(tr('الكل', 'All')) ?></a>
|
||||
<a class="btn btn-sm <?= $mode === 'pos' ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('sales.php', ['mode' => 'pos', 'q' => $search, 'status' => $statusFilter])) ?>">POS</a>
|
||||
<a class="btn btn-sm <?= $mode === 'normal' ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('sales.php', ['mode' => 'normal', 'q' => $search, 'status' => $statusFilter])) ?>"><?= h(tr('بيع عادي', 'Normal')) ?></a>
|
||||
<a class="btn btn-sm <?= $mode === 'normal' ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('sales.php', ['mode' => 'normal', 'q' => $search, 'status' => $statusFilter])) ?>"><?= h(tr('فاتورة', 'Invoice')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -140,7 +143,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<p><?= h(tr('جرّب إنشاء فاتورة جديدة من صفحة البيع.', 'Try creating a new invoice from the sale page.')) ?></p>
|
||||
<div class="d-flex gap-2 justify-content-center flex-wrap">
|
||||
<a class="btn btn-dark" href="<?= h(url_for('pos.php')) ?>">POS</a>
|
||||
<a class="btn btn-outline-secondary" href="<?= h(url_for('normal_sale.php')) ?>"><?= h(tr('بيع عادي', 'Normal Sale')) ?></a>
|
||||
<a class="btn btn-outline-secondary" href="<?= h(url_for('normal_sale.php')) ?>"><?= h(tr('فاتورة', 'Invoice')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user