Autosave: 20260423-020651
This commit is contained in:
parent
b2cb1b5a0b
commit
c5a18a8ed1
65
db/migrations/2026-04-22_eid_orders_foundation.sql
Normal file
65
db/migrations/2026-04-22_eid_orders_foundation.sql
Normal file
@ -0,0 +1,65 @@
|
||||
-- Step 1 foundation for Eid Orders (طلبات العيد)
|
||||
-- Adds Eid-specific tracking columns to the shared sales_orders table.
|
||||
|
||||
SET @db_name := DATABASE();
|
||||
|
||||
SET @sql := IF (
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name AND TABLE_NAME = 'sales_orders' AND COLUMN_NAME = 'order_type'
|
||||
),
|
||||
'SELECT 1',
|
||||
"ALTER TABLE sales_orders ADD COLUMN order_type varchar(30) NOT NULL DEFAULT 'standard' AFTER status"
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF (
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name AND TABLE_NAME = 'sales_orders' AND COLUMN_NAME = 'delivery_status'
|
||||
),
|
||||
'SELECT 1',
|
||||
"ALTER TABLE sales_orders ADD COLUMN delivery_status varchar(30) NOT NULL DEFAULT 'pending' AFTER order_type"
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF (
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name AND TABLE_NAME = 'sales_orders' AND COLUMN_NAME = 'delivery_date'
|
||||
),
|
||||
'SELECT 1',
|
||||
"ALTER TABLE sales_orders ADD COLUMN delivery_date date DEFAULT NULL AFTER delivery_status"
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF (
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name AND TABLE_NAME = 'sales_orders' AND INDEX_NAME = 'idx_order_type'
|
||||
),
|
||||
'SELECT 1',
|
||||
"ALTER TABLE sales_orders ADD INDEX idx_order_type (order_type)"
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF (
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db_name AND TABLE_NAME = 'sales_orders' AND INDEX_NAME = 'idx_delivery_date'
|
||||
),
|
||||
'SELECT 1',
|
||||
"ALTER TABLE sales_orders ADD INDEX idx_delivery_date (delivery_date)"
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
UPDATE sales_orders
|
||||
SET order_type = 'standard'
|
||||
WHERE order_type IS NULL OR order_type = '';
|
||||
|
||||
UPDATE sales_orders
|
||||
SET delivery_status = CASE
|
||||
WHEN COALESCE(status, 'completed') = 'completed' THEN 'delivered'
|
||||
ELSE 'pending'
|
||||
END
|
||||
WHERE delivery_status IS NULL OR delivery_status = '';
|
||||
@ -20,12 +20,18 @@ if ($user['role'] !== 'owner' && $editSale['branch_code'] !== $user['branch_code
|
||||
}
|
||||
|
||||
$pageTitle = tr('تعديل فاتورة', 'Edit Invoice') . ' #' . h($editSale['receipt_no']);
|
||||
$activeNav = 'sales';
|
||||
$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, '.', '') : ''));
|
||||
$catalog = catalog();
|
||||
$allowedBranches = get_user_branches($user);
|
||||
$deliveryOptions = eid_delivery_status_options();
|
||||
$deliveryStatusInput = trim((string) ($_POST['delivery_status'] ?? ($editSale['delivery_status'] ?? ($isEidSale ? 'pending' : ''))));
|
||||
$deliveryDateInput = trim((string) ($_POST['delivery_date'] ?? ($editSale['delivery_date'] ?? '')));
|
||||
$notesInput = trim((string) ($_POST['notes'] ?? ($editSale['notes'] ?? '')));
|
||||
$saleStatusInput = trim((string) ($_POST['sale_status'] ?? ($editSale['status'] ?? 'completed')));
|
||||
|
||||
try {
|
||||
$customers = db()->query('SELECT id, name, phone FROM customers ORDER BY name ASC')->fetchAll();
|
||||
@ -40,7 +46,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash'));
|
||||
$paymentAmountInput = trim((string) ($_POST['payment_amount'] ?? ''));
|
||||
$saleStatus = trim((string) ($_POST['sale_status'] ?? 'completed'));
|
||||
$saleStatusInput = $saleStatus;
|
||||
$notes = trim((string) ($_POST['notes'] ?? ''));
|
||||
$notesInput = $notes;
|
||||
$deliveryStatus = trim((string) ($_POST['delivery_status'] ?? ($editSale['delivery_status'] ?? ($isEidSale ? 'pending' : ''))));
|
||||
$deliveryStatusInput = $deliveryStatus;
|
||||
$deliveryDate = trim((string) ($_POST['delivery_date'] ?? ($editSale['delivery_date'] ?? '')));
|
||||
$deliveryDateInput = $deliveryDate;
|
||||
$cartJson = (string) ($_POST['cart_json'] ?? '[]');
|
||||
$items = json_decode($cartJson, true);
|
||||
|
||||
@ -48,6 +60,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$error = tr('اختر فرعاً صالحاً لهذه الصلاحية.', 'Choose a valid branch for this role.');
|
||||
} elseif (!in_array($paymentMethod, ['cash', 'card', 'transfer', 'pay_later'], true)) {
|
||||
$error = tr('اختر طريقة دفع صحيحة.', 'Choose a valid payment method.');
|
||||
} elseif ($isEidSale && !isset($deliveryOptions[$deliveryStatus])) {
|
||||
$error = tr('اختر حالة تجهيز صحيحة لطلب العيد.', 'Choose a valid prep status for the Eid order.');
|
||||
} elseif ($isEidSale && ($deliveryDate === '' || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $deliveryDate))) {
|
||||
$error = tr('حدد تاريخ تسليم صحيح لطلب العيد.', 'Choose a valid delivery date for the Eid order.');
|
||||
} elseif (!is_array($items) || $items === []) {
|
||||
$error = tr('أضف صنفاً واحداً على الأقل إلى الفاتورة.', 'Add at least one item to the invoice.');
|
||||
} else {
|
||||
@ -123,6 +139,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
vat_amount = :vat_amount,
|
||||
total_amount = :total_amount,
|
||||
status = :status,
|
||||
delivery_status = :delivery_status,
|
||||
delivery_date = :delivery_date,
|
||||
notes = :notes
|
||||
WHERE id = :id');
|
||||
$stmt->execute([
|
||||
@ -139,6 +157,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
':vat_amount' => $totalVat,
|
||||
':total_amount' => $totalAmount,
|
||||
':status' => $saleStatus,
|
||||
':delivery_status' => $isEidSale ? $deliveryStatus : ($editSale['delivery_status'] ?? 'pending'),
|
||||
':delivery_date' => $isEidSale && $deliveryDate !== '' ? $deliveryDate : ($isEidSale ? null : ($editSale['delivery_date'] ?? null)),
|
||||
':notes' => $notes !== '' ? $notes : null,
|
||||
':id' => $editSaleId,
|
||||
]);
|
||||
@ -326,8 +346,8 @@ require __DIR__ . '/includes/header.php';
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="fw-bold mb-0 text-dark"><?= h($pageTitle) ?></h3>
|
||||
<a href="sales.php" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-left"></i> <?= h(tr('عودة للمبيعات', 'Back to Sales')) ?>
|
||||
<a href="<?= h($isEidSale ? url_for('eid_orders.php') : url_for('sales.php')) ?>" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-left"></i> <?= h($isEidSale ? tr('عودة لطلبات العيد', 'Back to Eid Orders') : tr('عودة للمبيعات', 'Back to Sales')) ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -414,8 +434,13 @@ require __DIR__ . '/includes/header.php';
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('نوع العملية', 'Entry Type')) ?></label>
|
||||
<select class="form-select custom-input" name="sale_status">
|
||||
<option value="completed" <?= $editSale['status'] === 'completed' ? 'selected' : '' ?>><?= h(tr('فاتورة بيع (تم الدفع)', 'Sale Bill (Paid)')) ?></option>
|
||||
<option value="order" <?= $editSale['status'] === 'order' ? 'selected' : '' ?>><?= h(tr('طلب مسبق (دفع لاحق)', 'Order (Pay Later)')) ?></option>
|
||||
<?php if ($isEidSale): ?>
|
||||
<option value="order" <?= $saleStatusInput === 'order' ? 'selected' : '' ?>><?= h(tr('طلب عيد قيد التجهيز', 'Eid order in preparation')) ?></option>
|
||||
<option value="completed" <?= $saleStatusInput === 'completed' ? 'selected' : '' ?>><?= h(tr('تم التسليم / مكتمل', 'Delivered / Completed')) ?></option>
|
||||
<?php else: ?>
|
||||
<option value="completed" <?= $saleStatusInput === 'completed' ? 'selected' : '' ?>><?= h(tr('فاتورة بيع (تم الدفع)', 'Sale Bill (Paid)')) ?></option>
|
||||
<option value="order" <?= $saleStatusInput === 'order' ? 'selected' : '' ?>><?= h(tr('طلب مسبق (دفع لاحق)', 'Order (Pay Later)')) ?></option>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@ -427,6 +452,20 @@ require __DIR__ . '/includes/header.php';
|
||||
<option value="pay_later" <?= ($paymentMethod ?? $editSale['payment_method']) === 'pay_later' ? 'selected' : '' ?>><?= h(tr('آجل (Pay Later)', 'Pay Later')) ?></option>
|
||||
</select>
|
||||
</div>
|
||||
<?php if ($isEidSale): ?>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="delivery_date"><?= h(tr('تاريخ التسليم', 'Delivery date')) ?></label>
|
||||
<input type="date" class="form-control custom-input" id="delivery_date" name="delivery_date" value="<?= h($deliveryDateInput) ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="delivery_status"><?= h(tr('حالة التجهيز', 'Prep status')) ?></label>
|
||||
<select class="form-select custom-input" id="delivery_status" name="delivery_status">
|
||||
<?php foreach ($deliveryOptions as $value => $label): ?>
|
||||
<option value="<?= h($value) ?>" <?= $deliveryStatusInput === $value ? 'selected' : '' ?>><?= h($label) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="payment_amount"><?= h(tr('المبلغ المدفوع الآن', 'Paid Now')) ?></label>
|
||||
<input type="number" class="form-control custom-input" id="payment_amount" name="payment_amount" min="0" step="0.001" value="<?= h($paymentAmountInput) ?>" placeholder="0.000">
|
||||
@ -434,7 +473,7 @@ require __DIR__ . '/includes/header.php';
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label"><?= h(tr('ملاحظات (اختياري)', 'Notes (Optional)')) ?></label>
|
||||
<textarea class="form-control custom-input" name="notes" rows="2" placeholder="<?= h(tr('أي ملاحظات إضافية...', 'Any additional notes...')) ?>"><?= h($editSale['notes'] ?? '' ) ?></textarea>
|
||||
<textarea class="form-control custom-input" name="notes" rows="2" placeholder="<?= h(tr('أي ملاحظات إضافية...', 'Any additional notes...')) ?>"><?= h($notesInput) ?></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Summary -->
|
||||
@ -801,6 +840,9 @@ document.getElementById('smart-sale-form').addEventListener('submit', function(e
|
||||
if (Object.keys(invoiceItems).length === 0) {
|
||||
e.preventDefault();
|
||||
Swal.fire({icon: 'warning', text: '<?= h(tr('الرجاء إضافة أصناف للفاتورة أولاً.', 'Please add items to the invoice first.')) ?>'});
|
||||
} else if (<?= $isEidSale ? 'true' : 'false' ?> && (!document.getElementById('delivery_date') || !document.getElementById('delivery_date').value)) {
|
||||
e.preventDefault();
|
||||
Swal.fire({icon: 'warning', text: '<?= h(tr('حدد تاريخ التسليم لطلب العيد.', 'Please choose a delivery date for the Eid order.')) ?>'});
|
||||
} else if (paymentAmount > currentInvoiceTotal + 0.0005) {
|
||||
e.preventDefault();
|
||||
Swal.fire({icon: 'warning', text: '<?= h(tr('المبلغ المدفوع لا يمكن أن يتجاوز إجمالي الفاتورة.', 'Paid amount cannot exceed the invoice total.')) ?>'});
|
||||
|
||||
529
eid_orders.php
Normal file
529
eid_orders.php
Normal file
@ -0,0 +1,529 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$user = require_permission('sales', 'show');
|
||||
ensure_sales_table();
|
||||
|
||||
$activeNav = 'eid_orders';
|
||||
$pageTitle = tr('طلبات العيد', 'Eid Orders');
|
||||
$metaDescription = tr('متابعة طلبات العيد مع الفلاتر والمدى الزمني.', 'Track Eid orders with filters and date range.');
|
||||
|
||||
$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 = trim((string) ($_GET['q'] ?? ''));
|
||||
$paymentStatus = trim((string) ($_GET['payment_status'] ?? ''));
|
||||
$deliveryStatus = trim((string) ($_GET['delivery_status'] ?? ''));
|
||||
$dateFrom = trim((string) ($_GET['date_from'] ?? ''));
|
||||
$dateTo = trim((string) ($_GET['date_to'] ?? ''));
|
||||
$sort = trim((string) ($_GET['sort'] ?? 'delivery_date'));
|
||||
$dir = strtolower(trim((string) ($_GET['dir'] ?? 'asc')));
|
||||
$sortMap = [
|
||||
'receipt_no' => 'receipt_no',
|
||||
'customer' => 'customer_name',
|
||||
'branch' => 'branch_code',
|
||||
'delivery_date' => 'COALESCE(delivery_date, DATE(sale_date))',
|
||||
'delivery_status' => 'delivery_status',
|
||||
'item_count' => 'item_count',
|
||||
'total_amount' => 'total_amount',
|
||||
];
|
||||
if (!isset($sortMap[$sort])) {
|
||||
$sort = 'delivery_date';
|
||||
}
|
||||
if (!in_array($dir, ['asc', 'desc'], true)) {
|
||||
$dir = 'asc';
|
||||
}
|
||||
$page = max(1, (int) ($_GET['p'] ?? 1));
|
||||
$limit = 15;
|
||||
$offset = ($page - 1) * $limit;
|
||||
$allowedBranches = $user && $user['role'] !== 'owner' ? get_user_branches($user) : [];
|
||||
$deliveryOptions = eid_delivery_status_options();
|
||||
$dbError = null;
|
||||
$totalPages = 1;
|
||||
$orders = [];
|
||||
$summary = [
|
||||
'total_orders' => 0,
|
||||
'total_items' => 0,
|
||||
'total_amount' => 0,
|
||||
'prep_orders' => 0,
|
||||
];
|
||||
|
||||
try {
|
||||
$params = [':order_type' => 'eid'];
|
||||
$where = ' WHERE order_type = :order_type ';
|
||||
|
||||
if ($mode) {
|
||||
$where .= ' AND sale_mode = :sale_mode ';
|
||||
$params[':sale_mode'] = $mode;
|
||||
}
|
||||
|
||||
if ($branch) {
|
||||
$where .= ' AND branch_code = :branch_code ';
|
||||
$params[':branch_code'] = $branch;
|
||||
}
|
||||
|
||||
if ($user && $user['role'] !== 'owner') {
|
||||
if ($allowedBranches === []) {
|
||||
$where .= ' AND 1=0 ';
|
||||
} else {
|
||||
$namedParams = [];
|
||||
foreach ($allowedBranches as $i => $allowedBranch) {
|
||||
$key = ':v_branch_' . $i;
|
||||
$namedParams[] = $key;
|
||||
$params[$key] = $allowedBranch;
|
||||
}
|
||||
$where .= ' AND branch_code IN (' . implode(', ', $namedParams) . ') ';
|
||||
}
|
||||
}
|
||||
|
||||
if ($search !== '') {
|
||||
$where .= ' AND (receipt_no LIKE :search OR customer_name LIKE :search OR cashier_name LIKE :search OR notes LIKE :search) ';
|
||||
$params[':search'] = '%' . $search . '%';
|
||||
}
|
||||
|
||||
if (in_array($paymentStatus, ['paid', 'partial', 'unpaid'], true)) {
|
||||
$where .= ' AND payment_status = :payment_status ';
|
||||
$params[':payment_status'] = $paymentStatus;
|
||||
}
|
||||
|
||||
if (isset($deliveryOptions[$deliveryStatus])) {
|
||||
$where .= ' AND delivery_status = :delivery_status ';
|
||||
$params[':delivery_status'] = $deliveryStatus;
|
||||
}
|
||||
|
||||
if ($dateFrom !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateFrom)) {
|
||||
$where .= ' AND DATE(COALESCE(delivery_date, sale_date)) >= :date_from ';
|
||||
$params[':date_from'] = $dateFrom;
|
||||
} else {
|
||||
$dateFrom = '';
|
||||
}
|
||||
|
||||
if ($dateTo !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateTo)) {
|
||||
$where .= ' AND DATE(COALESCE(delivery_date, sale_date)) <= :date_to ';
|
||||
$params[':date_to'] = $dateTo;
|
||||
} else {
|
||||
$dateTo = '';
|
||||
}
|
||||
|
||||
$summarySql = "SELECT COUNT(*) AS total_orders, COALESCE(SUM(item_count), 0) AS total_items, COALESCE(SUM(total_amount), 0) AS total_amount, COALESCE(SUM(CASE WHEN delivery_status IN ('pending', 'preparing') THEN 1 ELSE 0 END), 0) AS prep_orders FROM sales_orders" . $where;
|
||||
$summaryStmt = db()->prepare($summarySql);
|
||||
foreach ($params as $key => $value) {
|
||||
$summaryStmt->bindValue($key, $value);
|
||||
}
|
||||
$summaryStmt->execute();
|
||||
$summary = $summaryStmt->fetch() ?: $summary;
|
||||
|
||||
$countSql = 'SELECT COUNT(*) FROM sales_orders' . $where;
|
||||
$countStmt = db()->prepare($countSql);
|
||||
foreach ($params as $key => $value) {
|
||||
$countStmt->bindValue($key, $value);
|
||||
}
|
||||
$countStmt->execute();
|
||||
$total = (int) $countStmt->fetchColumn();
|
||||
$totalPages = max(1, (int) ceil($total / $limit));
|
||||
|
||||
$primarySort = $sortMap[$sort] . ' ' . strtoupper($dir);
|
||||
$secondarySort = $sort === 'delivery_date'
|
||||
? ', sale_date DESC'
|
||||
: ', COALESCE(delivery_date, DATE(sale_date)) ASC, sale_date DESC';
|
||||
|
||||
$sql = 'SELECT * FROM sales_orders' . $where . ' ORDER BY ' . $primarySort . $secondarySort . ' LIMIT :limit OFFSET :offset';
|
||||
$stmt = db()->prepare($sql);
|
||||
foreach ($params as $key => $value) {
|
||||
$stmt->bindValue($key, $value);
|
||||
}
|
||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$orders = $stmt->fetchAll();
|
||||
|
||||
foreach ($orders as &$order) {
|
||||
$order['items'] = json_decode((string) ($order['items_json'] ?? '[]'), true) ?: [];
|
||||
|
||||
$itemPreview = [];
|
||||
foreach ($order['items'] as $item) {
|
||||
$name = trim((string) ($item['name'] ?? $item['name_ar'] ?? $item['name_en'] ?? $item['sku'] ?? ''));
|
||||
$qty = max(0, (int) ($item['qty'] ?? 0));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemPreview[] = $qty > 0 ? $name . ' ×' . $qty : $name;
|
||||
if (count($itemPreview) >= 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$remainingItems = max(0, count($order['items']) - count($itemPreview));
|
||||
$order['item_preview'] = $itemPreview;
|
||||
$order['item_preview_more'] = $remainingItems;
|
||||
}
|
||||
unset($order);
|
||||
} catch (Throwable $e) {
|
||||
$dbError = $e->getMessage();
|
||||
}
|
||||
|
||||
$queryState = static function (array $extra = []) use ($search, $branch, $mode, $paymentStatus, $deliveryStatus, $dateFrom, $dateTo, $sort, $dir): array {
|
||||
$params = [
|
||||
'q' => $search,
|
||||
'branch' => $branch,
|
||||
'mode' => $mode,
|
||||
'payment_status' => $paymentStatus,
|
||||
'delivery_status' => $deliveryStatus,
|
||||
'date_from' => $dateFrom,
|
||||
'date_to' => $dateTo,
|
||||
'sort' => $sort,
|
||||
'dir' => $dir,
|
||||
];
|
||||
|
||||
foreach ($extra as $key => $value) {
|
||||
$params[$key] = $value;
|
||||
}
|
||||
|
||||
return array_filter($params, static fn($value) => $value !== null && $value !== '');
|
||||
};
|
||||
|
||||
$sortUrl = static function (string $column) use ($sort, $dir, $queryState): string {
|
||||
$nextDir = $sort === $column && $dir === 'asc' ? 'desc' : 'asc';
|
||||
return url_for('eid_orders.php', $queryState(['sort' => $column, 'dir' => $nextDir, 'p' => 1]));
|
||||
};
|
||||
|
||||
$sortIcon = static function (string $column) use ($sort, $dir): string {
|
||||
if ($sort !== $column) {
|
||||
return 'bi-arrow-down-up text-muted';
|
||||
}
|
||||
|
||||
return $dir === 'asc' ? 'bi-sort-down-alt' : 'bi-sort-up';
|
||||
};
|
||||
|
||||
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>
|
||||
<h1 class="h4 mb-1"><i class="bi bi-stars me-2"></i><?= h(tr('طلبات العيد', 'Eid Orders')) ?></h1>
|
||||
</div>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<span class="badge text-bg-success-subtle border border-success-subtle text-success-emphasis px-3 py-2"><?= h(tr('المرحلة الحالية: إنشاء الطلبات', 'Current phase: order creation')) ?></span>
|
||||
<a class="btn btn-dark" href="<?= h(url_for('eid_sale.php')) ?>"><i class="bi bi-plus-circle me-1"></i><?= h(tr('طلب عيد جديد', 'New Eid Order')) ?></a>
|
||||
<a class="btn btn-outline-dark" href="<?= h(url_for('eid_print.php', $queryState())) ?>"><i class="bi bi-printer me-1"></i><?= h(tr('طباعة ملخص التجهيز', 'Print prep summary')) ?></a>
|
||||
<a class="btn btn-outline-secondary" href="<?= h(url_for('sales.php', ['status' => 'order'])) ?>"><i class="bi bi-journal-text me-1"></i><?= h(tr('عرض الطلبات العادية', 'View regular orders')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $hasAdvancedFilters = $mode !== null || $paymentStatus !== ''; ?>
|
||||
<style>
|
||||
.eid-filter-shell {
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
border-radius: 1rem;
|
||||
background: linear-gradient(180deg, rgba(248, 250, 252, 0.96), rgba(255, 255, 255, 0.98));
|
||||
padding: 1rem;
|
||||
}
|
||||
.eid-filter-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .75rem;
|
||||
align-items: end;
|
||||
}
|
||||
.eid-filter-item {
|
||||
flex: 1 1 180px;
|
||||
min-width: 0;
|
||||
}
|
||||
.eid-filter-item--search {
|
||||
flex: 2.2 1 320px;
|
||||
}
|
||||
.eid-filter-item--date {
|
||||
flex: 1.6 1 280px;
|
||||
}
|
||||
.eid-filter-actions {
|
||||
flex: 0 1 auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .5rem;
|
||||
align-items: end;
|
||||
}
|
||||
.eid-date-range {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: .5rem;
|
||||
}
|
||||
.eid-advanced-toggle summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.eid-advanced-toggle summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
.eid-advanced-panel {
|
||||
border-top: 1px dashed rgba(15, 23, 42, 0.12);
|
||||
margin-top: .85rem;
|
||||
padding-top: .85rem;
|
||||
}
|
||||
.eid-grid-card {
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1rem 2rem rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
.eid-grid-toolbar {
|
||||
background: linear-gradient(135deg, rgba(248, 250, 252, 0.96), rgba(241, 245, 249, 0.92));
|
||||
}
|
||||
.eid-table thead th {
|
||||
white-space: nowrap;
|
||||
font-size: .82rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .03em;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
.eid-sort-link {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .35rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.eid-sort-link:hover {
|
||||
color: var(--bs-dark);
|
||||
}
|
||||
.eid-order-id {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.eid-customer-cell,
|
||||
.eid-items-cell {
|
||||
min-width: 190px;
|
||||
}
|
||||
.eid-items-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .35rem;
|
||||
}
|
||||
.eid-items-list .badge {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@media (max-width: 767.98px) {
|
||||
.eid-filter-actions {
|
||||
width: 100%;
|
||||
}
|
||||
.eid-filter-actions .btn {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.eid-date-range {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<form class="eid-filter-shell mb-4" method="GET" action="eid_orders.php">
|
||||
<div class="d-flex justify-content-end align-items-center mb-3">
|
||||
<details class="eid-advanced-toggle" <?= $hasAdvancedFilters ? 'open' : '' ?>>
|
||||
<summary class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-sliders me-1"></i><?= h(tr('فلاتر إضافية', 'More filters')) ?>
|
||||
</summary>
|
||||
<div class="eid-advanced-panel">
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label mb-1" for="eid-mode"><?= h(tr('القناة', 'Channel')) ?></label>
|
||||
<select id="eid-mode" class="form-select" name="mode">
|
||||
<option value=""><?= h(tr('الكل', 'All')) ?></option>
|
||||
<option value="normal" <?= $mode === 'normal' ? 'selected' : '' ?>><?= h(tr('فاتورة', 'Invoice')) ?></option>
|
||||
<option value="pos" <?= $mode === 'pos' ? 'selected' : '' ?>>POS</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label mb-1" for="eid-payment-status"><?= h(tr('حالة الدفع', 'Payment')) ?></label>
|
||||
<select id="eid-payment-status" class="form-select" name="payment_status">
|
||||
<option value=""><?= h(tr('كل الحالات', 'All statuses')) ?></option>
|
||||
<option value="paid" <?= $paymentStatus === 'paid' ? 'selected' : '' ?>><?= h(tr('مدفوع', 'Paid')) ?></option>
|
||||
<option value="partial" <?= $paymentStatus === 'partial' ? 'selected' : '' ?>><?= h(tr('جزئي', 'Partial')) ?></option>
|
||||
<option value="unpaid" <?= $paymentStatus === 'unpaid' ? 'selected' : '' ?>><?= h(tr('غير مدفوع', 'Unpaid')) ?></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="eid-filter-row">
|
||||
<div class="eid-filter-item eid-filter-item--search">
|
||||
<label class="form-label mb-1" for="eid-search"><?= h(tr('بحث سريع', 'Quick search')) ?></label>
|
||||
<input id="eid-search" type="text" class="form-control" name="q" value="<?= h($search) ?>" placeholder="<?= h(tr('فاتورة، عميل، كاشير، أو ملاحظة', 'Invoice, customer, cashier, or note')) ?>">
|
||||
</div>
|
||||
<div class="eid-filter-item">
|
||||
<label class="form-label mb-1" for="eid-branch"><?= h(tr('الفرع', 'Branch')) ?></label>
|
||||
<select id="eid-branch" class="form-select" name="branch">
|
||||
<option value=""><?= h(tr('كل الفروع', 'All branches')) ?></option>
|
||||
<?php foreach (branches() as $branchCode => $branchLabel): ?>
|
||||
<?php if ($user['role'] !== 'owner' && !in_array($branchCode, $allowedBranches, true)) { continue; } ?>
|
||||
<option value="<?= h($branchCode) ?>" <?= $branch === $branchCode ? 'selected' : '' ?>><?= h($branchLabel) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="eid-filter-item">
|
||||
<label class="form-label mb-1" for="eid-delivery-status"><?= h(tr('التجهيز', 'Prep')) ?></label>
|
||||
<select id="eid-delivery-status" class="form-select" name="delivery_status">
|
||||
<option value=""><?= h(tr('كل الحالات', 'All statuses')) ?></option>
|
||||
<?php foreach ($deliveryOptions as $value => $label): ?>
|
||||
<option value="<?= h($value) ?>" <?= $deliveryStatus === $value ? 'selected' : '' ?>><?= h($label) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="eid-filter-item eid-filter-item--date">
|
||||
<label class="form-label mb-1"><?= h(tr('فترة التسليم', 'Delivery window')) ?></label>
|
||||
<div class="eid-date-range">
|
||||
<input id="eid-date-from" type="date" class="form-control" name="date_from" value="<?= h($dateFrom) ?>" aria-label="<?= h(tr('من تاريخ', 'From date')) ?>">
|
||||
<input id="eid-date-to" type="date" class="form-control" name="date_to" value="<?= h($dateTo) ?>" aria-label="<?= h(tr('إلى تاريخ', 'To date')) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="eid-filter-actions">
|
||||
<button type="submit" class="btn btn-dark"><i class="bi bi-funnel me-1"></i><?= h(tr('تطبيق', 'Apply')) ?></button>
|
||||
<a class="btn btn-outline-secondary" href="<?= h(url_for('eid_orders.php')) ?>"><?= h(tr('إعادة ضبط', 'Reset')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-md-6 col-xl-3">
|
||||
<div class="surface-card h-100 border">
|
||||
<div class="small text-muted mb-2"><?= h(tr('إجمالي الطلبات', 'Total orders')) ?></div>
|
||||
<div class="display-6 fw-bold"><?= (int) ($summary['total_orders'] ?? 0) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-xl-3">
|
||||
<div class="surface-card h-100 border">
|
||||
<div class="small text-muted mb-2"><?= h(tr('عدد القطع', 'Total items')) ?></div>
|
||||
<div class="display-6 fw-bold"><?= (int) ($summary['total_items'] ?? 0) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-xl-3">
|
||||
<div class="surface-card h-100 border">
|
||||
<div class="small text-muted mb-2"><?= h(tr('قيد التجهيز', 'Pending prep')) ?></div>
|
||||
<div class="display-6 fw-bold"><?= (int) ($summary['prep_orders'] ?? 0) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-xl-3">
|
||||
<div class="surface-card h-100 border">
|
||||
<div class="small text-muted mb-2"><?= h(tr('إجمالي القيمة', 'Total value')) ?></div>
|
||||
<div class="display-6 fw-bold"><?= h(number_format((float) ($summary['total_amount'] ?? 0), 3)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($dbError): ?>
|
||||
<div class="alert alert-danger"><?= h($dbError) ?></div>
|
||||
<?php elseif ($orders === []): ?>
|
||||
<div class="text-center py-5 text-muted">
|
||||
<div class="mb-3"><i class="bi bi-calendar-heart" style="font-size: 2rem;"></i></div>
|
||||
<h2 class="h5"><?= h(tr('لا توجد طلبات عيد حتى الآن', 'No Eid orders yet')) ?></h2>
|
||||
<p class="mb-0"><?= h(tr('لا توجد طلبات عيد حتى الآن. ابدأ بإنشاء أول طلب عيد ليظهر هنا مع الفلاتر والتواريخ.', 'There are no Eid orders yet. Create your first Eid order to see it here with filters and dates.')) ?></p>
|
||||
<a class="btn btn-dark mt-3" href="<?= h(url_for('eid_sale.php')) ?>"><i class="bi bi-plus-circle me-1"></i><?= h(tr('إنشاء طلب عيد', 'Create Eid Order')) ?></a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="eid-grid-card bg-white mb-3">
|
||||
<div class="eid-grid-toolbar px-3 px-lg-4 py-3 border-bottom d-flex justify-content-end">
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">
|
||||
<?= h((int) ($summary['total_orders'] ?? 0)) ?> <?= h(tr('طلب', 'orders')) ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0 eid-table">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>
|
||||
<a class="eid-sort-link" href="<?= h($sortUrl('receipt_no')) ?>">
|
||||
<?= h(tr('الفاتورة', 'Invoice')) ?>
|
||||
<i class="bi <?= h($sortIcon('receipt_no')) ?>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a class="eid-sort-link" href="<?= h($sortUrl('customer')) ?>">
|
||||
<?= h(tr('العميل', 'Customer')) ?>
|
||||
<i class="bi <?= h($sortIcon('customer')) ?>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a class="eid-sort-link" href="<?= h($sortUrl('delivery_date')) ?>">
|
||||
<?= h(tr('تسليم', 'Delivery')) ?>
|
||||
<i class="bi <?= h($sortIcon('delivery_date')) ?>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a class="eid-sort-link" href="<?= h($sortUrl('delivery_status')) ?>">
|
||||
<?= h(tr('حالة التجهيز', 'Prep status')) ?>
|
||||
<i class="bi <?= h($sortIcon('delivery_status')) ?>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a class="eid-sort-link" href="<?= h($sortUrl('branch')) ?>">
|
||||
<?= h(tr('الفرع', 'Branch')) ?>
|
||||
<i class="bi <?= h($sortIcon('branch')) ?>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a class="eid-sort-link" href="<?= h($sortUrl('item_count')) ?>">
|
||||
<?= h(tr('الأصناف', 'Items')) ?>
|
||||
<i class="bi <?= h($sortIcon('item_count')) ?>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th class="text-end">
|
||||
<a class="eid-sort-link justify-content-end" href="<?= h($sortUrl('total_amount')) ?>">
|
||||
<?= h(tr('الإجمالي', 'Total')) ?>
|
||||
<i class="bi <?= h($sortIcon('total_amount')) ?>"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th class="text-end"><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($orders as $order): ?>
|
||||
<?php $effectiveDelivery = $order['delivery_date'] ?: date('Y-m-d', strtotime((string) $order['sale_date'])); ?>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-semibold eid-order-id"><?= h($order['receipt_no']) ?></div>
|
||||
</td>
|
||||
<td class="eid-customer-cell">
|
||||
<div class="fw-semibold"><?= h((string) ($order['customer_name'] ?: tr('عميل نقدي', 'Walk-in customer'))) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-semibold"><?= h(date('Y-m-d', strtotime((string) $effectiveDelivery))) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge <?= h(eid_delivery_status_badge_class((string) ($order['delivery_status'] ?? 'pending'))) ?> px-3 py-2"><?= h(eid_delivery_status_label((string) ($order['delivery_status'] ?? 'pending'))) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill text-bg-light border"><?= h(branch_label((string) $order['branch_code'])) ?></span>
|
||||
</td>
|
||||
<td class="eid-items-cell">
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2 fw-semibold">
|
||||
<?= (int) ($order['item_count'] ?? 0) ?> <?= h(tr('صنف', 'items')) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end fw-semibold"><?= h(number_format((float) ($order['total_amount'] ?? 0), 3)) ?></td>
|
||||
<td>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a class="btn btn-sm btn-light border text-primary" href="<?= h(url_for('sale.php', ['id' => $order['id']])) ?>" title="<?= h(tr('تفاصيل', 'Detail')) ?>"><i class="bi bi-eye"></i></a>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="<?= h(url_for('edit_sale.php', ['id' => $order['id']])) ?>" title="<?= h(tr('تعديل', 'Edit')) ?>"><i class="bi bi-pencil"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<nav class="mt-4" aria-label="<?= h(tr('صفحات طلبات العيد', 'Eid order pages')) ?>">
|
||||
<ul class="pagination justify-content-center mb-0">
|
||||
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
|
||||
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
|
||||
<a class="page-link" href="<?= h(url_for('eid_orders.php', $queryState(['p' => $i]))) ?>"><?= $i ?></a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php require __DIR__ . '/includes/footer.php'; ?>
|
||||
394
eid_print.php
Normal file
394
eid_print.php
Normal file
@ -0,0 +1,394 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$user = require_permission('sales', 'show');
|
||||
ensure_sales_table();
|
||||
|
||||
$activeNav = 'eid_orders';
|
||||
$pageTitle = tr('طباعة تجهيزات العيد', 'Print Eid Prep Summary');
|
||||
$metaDescription = tr('ملخص قابل للطباعة لتجهيزات طلبات العيد حسب التاريخ والفرع والحالة.', 'Printable Eid preparation summary by date, branch, and status.');
|
||||
$metaRobots = 'noindex, nofollow';
|
||||
|
||||
$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 = trim((string) ($_GET['q'] ?? ''));
|
||||
$paymentStatus = trim((string) ($_GET['payment_status'] ?? ''));
|
||||
$deliveryStatus = trim((string) ($_GET['delivery_status'] ?? ''));
|
||||
$dateFrom = trim((string) ($_GET['date_from'] ?? ''));
|
||||
$dateTo = trim((string) ($_GET['date_to'] ?? ''));
|
||||
$allowedBranches = $user && $user['role'] !== 'owner' ? get_user_branches($user) : [];
|
||||
$deliveryOptions = eid_delivery_status_options();
|
||||
$dbError = null;
|
||||
$orders = [];
|
||||
$itemRows = [];
|
||||
$summary = [
|
||||
'total_orders' => 0,
|
||||
'unique_items' => 0,
|
||||
'total_quantity' => 0,
|
||||
'total_amount' => 0,
|
||||
];
|
||||
|
||||
try {
|
||||
$params = [':order_type' => 'eid'];
|
||||
$where = ' WHERE order_type = :order_type ';
|
||||
|
||||
if ($mode) {
|
||||
$where .= ' AND sale_mode = :sale_mode ';
|
||||
$params[':sale_mode'] = $mode;
|
||||
}
|
||||
|
||||
if ($branch) {
|
||||
$where .= ' AND branch_code = :branch_code ';
|
||||
$params[':branch_code'] = $branch;
|
||||
}
|
||||
|
||||
if ($user && $user['role'] !== 'owner') {
|
||||
if ($allowedBranches === []) {
|
||||
$where .= ' AND 1=0 ';
|
||||
} else {
|
||||
$namedParams = [];
|
||||
foreach ($allowedBranches as $i => $allowedBranch) {
|
||||
$key = ':v_branch_' . $i;
|
||||
$namedParams[] = $key;
|
||||
$params[$key] = $allowedBranch;
|
||||
}
|
||||
$where .= ' AND branch_code IN (' . implode(', ', $namedParams) . ') ';
|
||||
}
|
||||
}
|
||||
|
||||
if ($search !== '') {
|
||||
$where .= ' AND (receipt_no LIKE :search OR customer_name LIKE :search OR cashier_name LIKE :search OR notes LIKE :search) ';
|
||||
$params[':search'] = '%' . $search . '%';
|
||||
}
|
||||
|
||||
if (in_array($paymentStatus, ['paid', 'partial', 'unpaid'], true)) {
|
||||
$where .= ' AND payment_status = :payment_status ';
|
||||
$params[':payment_status'] = $paymentStatus;
|
||||
}
|
||||
|
||||
if (isset($deliveryOptions[$deliveryStatus])) {
|
||||
$where .= ' AND delivery_status = :delivery_status ';
|
||||
$params[':delivery_status'] = $deliveryStatus;
|
||||
}
|
||||
|
||||
if ($dateFrom !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateFrom)) {
|
||||
$where .= ' AND DATE(COALESCE(delivery_date, sale_date)) >= :date_from ';
|
||||
$params[':date_from'] = $dateFrom;
|
||||
} else {
|
||||
$dateFrom = '';
|
||||
}
|
||||
|
||||
if ($dateTo !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateTo)) {
|
||||
$where .= ' AND DATE(COALESCE(delivery_date, sale_date)) <= :date_to ';
|
||||
$params[':date_to'] = $dateTo;
|
||||
} else {
|
||||
$dateTo = '';
|
||||
}
|
||||
|
||||
$sql = 'SELECT * FROM sales_orders' . $where . ' ORDER BY COALESCE(delivery_date, DATE(sale_date)) ASC, sale_date ASC';
|
||||
$stmt = db()->prepare($sql);
|
||||
foreach ($params as $key => $value) {
|
||||
$stmt->bindValue($key, $value);
|
||||
}
|
||||
$stmt->execute();
|
||||
$orders = $stmt->fetchAll();
|
||||
|
||||
$itemIndex = [];
|
||||
foreach ($orders as &$order) {
|
||||
$decodedItems = json_decode((string) ($order['items_json'] ?? '[]'), true);
|
||||
$items = is_array($decodedItems) ? $decodedItems : [];
|
||||
$order['items'] = $items;
|
||||
|
||||
foreach ($items as $item) {
|
||||
$sku = trim((string) ($item['sku'] ?? ''));
|
||||
$name = trim((string) ($item['name_ar'] ?? ''));
|
||||
if ($name === '') {
|
||||
$name = trim((string) ($item['name_en'] ?? ''));
|
||||
}
|
||||
if ($name === '') {
|
||||
$name = $sku !== '' ? $sku : tr('صنف بدون اسم', 'Unnamed item');
|
||||
}
|
||||
|
||||
$key = $sku !== '' ? $sku : md5($name);
|
||||
$qty = max(0, (float) ($item['qty'] ?? 0));
|
||||
|
||||
if (!isset($itemIndex[$key])) {
|
||||
$itemIndex[$key] = [
|
||||
'sku' => $sku,
|
||||
'name' => $name,
|
||||
'qty' => 0.0,
|
||||
'order_count' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$itemIndex[$key]['qty'] += $qty;
|
||||
$itemIndex[$key]['order_count']++;
|
||||
}
|
||||
}
|
||||
unset($order);
|
||||
|
||||
usort($orders, static function (array $a, array $b): int {
|
||||
$aDate = (string) ($a['delivery_date'] ?: substr((string) ($a['sale_date'] ?? ''), 0, 10));
|
||||
$bDate = (string) ($b['delivery_date'] ?: substr((string) ($b['sale_date'] ?? ''), 0, 10));
|
||||
return [$aDate, (string) ($a['receipt_no'] ?? '')] <=> [$bDate, (string) ($b['receipt_no'] ?? '')];
|
||||
});
|
||||
|
||||
$itemRows = array_values($itemIndex);
|
||||
usort($itemRows, static function (array $a, array $b): int {
|
||||
if ($a['qty'] === $b['qty']) {
|
||||
return strcasecmp((string) $a['name'], (string) $b['name']);
|
||||
}
|
||||
return $b['qty'] <=> $a['qty'];
|
||||
});
|
||||
|
||||
$summary['total_orders'] = count($orders);
|
||||
$summary['unique_items'] = count($itemRows);
|
||||
$summary['total_quantity'] = array_sum(array_map(static fn(array $row): float => (float) $row['qty'], $itemRows));
|
||||
$summary['total_amount'] = array_sum(array_map(static fn(array $row): float => (float) ($row['total_amount'] ?? 0), $orders));
|
||||
} catch (Throwable $e) {
|
||||
$dbError = $e->getMessage();
|
||||
}
|
||||
|
||||
$filterParams = array_filter([
|
||||
'q' => $search,
|
||||
'branch' => $branch,
|
||||
'mode' => $mode,
|
||||
'payment_status' => $paymentStatus,
|
||||
'delivery_status' => $deliveryStatus,
|
||||
'date_from' => $dateFrom,
|
||||
'date_to' => $dateTo,
|
||||
], static fn($value) => $value !== null && $value !== '');
|
||||
|
||||
$generatedAt = date('Y-m-d H:i');
|
||||
require __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
<style>
|
||||
.eid-print-shell { max-width: 1180px; margin: 0 auto; }
|
||||
.eid-print-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e8ecf4;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 12px 40px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
.eid-print-card .table th { white-space: nowrap; }
|
||||
.eid-kpi {
|
||||
border: 1px solid #e9eef8;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
||||
padding: 1rem 1.1rem;
|
||||
height: 100%;
|
||||
}
|
||||
.eid-kpi-label { color: #6b7280; font-size: 0.92rem; margin-bottom: 0.35rem; }
|
||||
.eid-kpi-value { font-size: 2rem; font-weight: 700; color: #111827; }
|
||||
.eid-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
border: 1px solid #dbe3ef;
|
||||
border-radius: 999px;
|
||||
padding: 0.4rem 0.8rem;
|
||||
background: #f8fafc;
|
||||
color: #334155;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
@media print {
|
||||
body { background: #fff !important; }
|
||||
.main-sidebar, .main-header, .footer-section, .d-print-none, .alert-dismissible .btn-close { display: none !important; }
|
||||
.main-content { margin: 0 !important; padding: 0 !important; }
|
||||
.eid-print-card { box-shadow: none; border: none; }
|
||||
.table th { background: #f3f4f6 !important; -webkit-print-color-adjust: exact; color: #111 !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="eid-print-shell py-4">
|
||||
<section class="eid-print-card p-4 p-lg-5 mb-4">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap mb-4 d-print-none">
|
||||
<div>
|
||||
<h1 class="h3 mb-1"><i class="bi bi-printer me-2"></i><?= h(tr('ملخص تجهيزات طلبات العيد', 'Eid Order Prep Summary')) ?></h1>
|
||||
<p class="text-muted mb-0"><?= h(tr('تقرير قابل للطباعة يوضح الأصناف المطلوبة وعدد الطلبات التي تحتوي كل صنف.', 'Printable report showing required items and how many orders contain each item.')) ?></p>
|
||||
</div>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<a class="btn btn-outline-secondary" href="<?= h(url_for('eid_orders.php', $filterParams)) ?>"><i class="bi bi-arrow-<?= current_lang() === 'ar' ? 'right' : 'left' ?> me-1"></i><?= h(tr('رجوع لطلبات العيد', 'Back to Eid Orders')) ?></a>
|
||||
<button type="button" class="btn btn-dark" onclick="window.print()"><i class="bi bi-printer me-1"></i><?= h(tr('طباعة', 'Print')) ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="row g-3 align-items-end mb-4 d-print-none" method="GET" action="eid_print.php">
|
||||
<div class="col-12 col-md-3">
|
||||
<label class="form-label" for="print-search"><?= h(tr('بحث', 'Search')) ?></label>
|
||||
<input id="print-search" type="text" class="form-control" name="q" value="<?= h($search) ?>" placeholder="<?= h(tr('رقم الفاتورة أو العميل أو ملاحظة', 'Invoice, customer, or note')) ?>">
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<label class="form-label" for="print-branch"><?= h(tr('الفرع', 'Branch')) ?></label>
|
||||
<select id="print-branch" class="form-select" name="branch">
|
||||
<option value=""><?= h(tr('كل الفروع', 'All branches')) ?></option>
|
||||
<?php foreach (branches() as $branchCode => $branchLabel): ?>
|
||||
<?php if ($user['role'] !== 'owner' && !in_array($branchCode, $allowedBranches, true)) { continue; } ?>
|
||||
<option value="<?= h($branchCode) ?>" <?= $branch === $branchCode ? 'selected' : '' ?>><?= h($branchLabel) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<label class="form-label" for="print-mode"><?= h(tr('القناة', 'Channel')) ?></label>
|
||||
<select id="print-mode" class="form-select" name="mode">
|
||||
<option value=""><?= h(tr('الكل', 'All')) ?></option>
|
||||
<option value="normal" <?= $mode === 'normal' ? 'selected' : '' ?>><?= h(tr('فاتورة', 'Invoice')) ?></option>
|
||||
<option value="pos" <?= $mode === 'pos' ? 'selected' : '' ?>>POS</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<label class="form-label" for="print-payment-status"><?= h(tr('حالة الدفع', 'Payment')) ?></label>
|
||||
<select id="print-payment-status" class="form-select" name="payment_status">
|
||||
<option value=""><?= h(tr('كل الحالات', 'All statuses')) ?></option>
|
||||
<option value="paid" <?= $paymentStatus === 'paid' ? 'selected' : '' ?>><?= h(tr('مدفوع', 'Paid')) ?></option>
|
||||
<option value="partial" <?= $paymentStatus === 'partial' ? 'selected' : '' ?>><?= h(tr('جزئي', 'Partial')) ?></option>
|
||||
<option value="unpaid" <?= $paymentStatus === 'unpaid' ? 'selected' : '' ?>><?= h(tr('غير مدفوع', 'Unpaid')) ?></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<label class="form-label" for="print-delivery-status"><?= h(tr('حالة التجهيز', 'Prep status')) ?></label>
|
||||
<select id="print-delivery-status" class="form-select" name="delivery_status">
|
||||
<option value=""><?= h(tr('كل الحالات', 'All statuses')) ?></option>
|
||||
<?php foreach ($deliveryOptions as $value => $label): ?>
|
||||
<option value="<?= h($value) ?>" <?= $deliveryStatus === $value ? 'selected' : '' ?>><?= h($label) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<label class="form-label" for="print-date-from"><?= h(tr('من تاريخ', 'From date')) ?></label>
|
||||
<input id="print-date-from" type="date" class="form-control" name="date_from" value="<?= h($dateFrom) ?>">
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<label class="form-label" for="print-date-to"><?= h(tr('إلى تاريخ', 'To date')) ?></label>
|
||||
<input id="print-date-to" type="date" class="form-control" name="date_to" value="<?= h($dateTo) ?>">
|
||||
</div>
|
||||
<div class="col-12 col-md-3 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-dark flex-fill"><i class="bi bi-funnel me-1"></i><?= h(tr('تحديث الملخص', 'Update summary')) ?></button>
|
||||
<a class="btn btn-outline-secondary" href="<?= h(url_for('eid_print.php')) ?>"><?= h(tr('إعادة ضبط', 'Reset')) ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="d-none d-print-block mb-4">
|
||||
<div class="text-center mb-3">
|
||||
<h2 class="h4 mb-1"><?= h(tr('ملخص تجهيزات طلبات العيد', 'Eid Order Prep Summary')) ?></h2>
|
||||
<div class="text-muted"><?= h(tr('تاريخ الطباعة', 'Printed at')) ?>: <?= h($generatedAt) ?></div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 justify-content-center">
|
||||
<span class="eid-chip"><?= h(tr('عدد الطلبات', 'Orders')) ?>: <?= (int) $summary['total_orders'] ?></span>
|
||||
<span class="eid-chip"><?= h(tr('الأصناف المميزة', 'Unique items')) ?>: <?= (int) $summary['unique_items'] ?></span>
|
||||
<span class="eid-chip"><?= h(tr('إجمالي الكمية', 'Total quantity')) ?>: <?= h(number_format((float) $summary['total_quantity'], 0)) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($dbError): ?>
|
||||
<div class="alert alert-danger"><?= h($dbError) ?></div>
|
||||
<?php else: ?>
|
||||
<div class="d-flex flex-wrap gap-2 mb-4">
|
||||
<span class="eid-chip"><i class="bi bi-calendar-range"></i><?= h($dateFrom !== '' || $dateTo !== '' ? (($dateFrom !== '' ? $dateFrom : '…') . ' → ' . ($dateTo !== '' ? $dateTo : '…')) : tr('كل التواريخ', 'All dates')) ?></span>
|
||||
<span class="eid-chip"><i class="bi bi-shop"></i><?= h($branch ? (branches()[$branch] ?? $branch) : tr('كل الفروع', 'All branches')) ?></span>
|
||||
<span class="eid-chip"><i class="bi bi-stars"></i><?= h($deliveryStatus !== '' && isset($deliveryOptions[$deliveryStatus]) ? $deliveryOptions[$deliveryStatus] : tr('كل حالات التجهيز', 'All prep statuses')) ?></span>
|
||||
<span class="eid-chip"><i class="bi bi-credit-card"></i><?= h($paymentStatus !== '' ? match ($paymentStatus) { 'paid' => tr('مدفوع', 'Paid'), 'partial' => tr('جزئي', 'Partial'), 'unpaid' => tr('غير مدفوع', 'Unpaid'), default => $paymentStatus } : tr('كل حالات الدفع', 'All payment statuses')) ?></span>
|
||||
</div>
|
||||
|
||||
<section class="row g-3 mb-4">
|
||||
<div class="col-12 col-md-6 col-xl-3"><div class="eid-kpi"><div class="eid-kpi-label"><?= h(tr('إجمالي الطلبات', 'Total orders')) ?></div><div class="eid-kpi-value"><?= (int) $summary['total_orders'] ?></div></div></div>
|
||||
<div class="col-12 col-md-6 col-xl-3"><div class="eid-kpi"><div class="eid-kpi-label"><?= h(tr('الأصناف المميزة', 'Unique items')) ?></div><div class="eid-kpi-value"><?= (int) $summary['unique_items'] ?></div></div></div>
|
||||
<div class="col-12 col-md-6 col-xl-3"><div class="eid-kpi"><div class="eid-kpi-label"><?= h(tr('إجمالي الكمية', 'Total quantity')) ?></div><div class="eid-kpi-value"><?= h(number_format((float) $summary['total_quantity'], 0)) ?></div></div></div>
|
||||
<div class="col-12 col-md-6 col-xl-3"><div class="eid-kpi"><div class="eid-kpi-label"><?= h(tr('إجمالي القيمة', 'Total value')) ?></div><div class="eid-kpi-value"><?= h(number_format((float) $summary['total_amount'], 3)) ?></div></div></div>
|
||||
</section>
|
||||
|
||||
<section class="mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h2 class="h5 mb-1"><?= h(tr('ملخص الأصناف', 'Item Summary')) ?></h2>
|
||||
<div class="text-muted small"><?= h(tr('عدد الطلبات لكل صنف مع الكمية الإجمالية المطلوبة للتجهيز.', 'Order count per item with total quantity required for preparation.')) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($itemRows === []): ?>
|
||||
<div class="alert alert-secondary mb-0"><?= h(tr('لا توجد بيانات ضمن هذه الفلاتر.', 'No data found for the selected filters.')) ?></div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th><?= h(tr('الصنف', 'Item')) ?></th>
|
||||
<th><?= h(tr('SKU', 'SKU')) ?></th>
|
||||
<th class="text-center"><?= h(tr('عدد الطلبات', 'Orders count')) ?></th>
|
||||
<th class="text-end"><?= h(tr('إجمالي الكمية', 'Total quantity')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($itemRows as $row): ?>
|
||||
<tr>
|
||||
<td class="fw-semibold"><?= h($row['name']) ?></td>
|
||||
<td class="text-muted small"><?= h($row['sku'] !== '' ? $row['sku'] : '—') ?></td>
|
||||
<td class="text-center fw-semibold"><?= (int) $row['order_count'] ?></td>
|
||||
<td class="text-end fw-semibold"><?= h(rtrim(rtrim(number_format((float) $row['qty'], 3, '.', ''), '0'), '.')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h2 class="h5 mb-1"><?= h(tr('الطلبات المشمولة', 'Included Orders')) ?></h2>
|
||||
<div class="text-muted small"><?= h(tr('قائمة الطلبات الداخلة في هذا الملخص مع الأصناف داخل كل طلب.', 'Orders included in this summary with the items inside each order.')) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($orders === []): ?>
|
||||
<div class="alert alert-secondary mb-0"><?= h(tr('لا توجد طلبات عيد ضمن هذا النطاق.', 'There are no Eid orders in this range.')) ?></div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th><?= h(tr('الفاتورة', 'Invoice')) ?></th>
|
||||
<th><?= h(tr('العميل', 'Customer')) ?></th>
|
||||
<th><?= h(tr('الفرع', 'Branch')) ?></th>
|
||||
<th><?= h(tr('تاريخ الاستلام', 'Delivery date')) ?></th>
|
||||
<th><?= h(tr('حالة التجهيز', 'Prep status')) ?></th>
|
||||
<th><?= h(tr('الأصناف', 'Items')) ?></th>
|
||||
<th class="text-end"><?= h(tr('الإجمالي', 'Total')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($orders as $order): ?>
|
||||
<tr>
|
||||
<td class="fw-semibold"><?= h($order['receipt_no'] ?? ('#' . (int) $order['id'])) ?></td>
|
||||
<td><?= h($order['customer_name'] ?: tr('عميل نقدي', 'Walk-in customer')) ?></td>
|
||||
<td><?= h(branches()[$order['branch_code']] ?? $order['branch_code']) ?></td>
|
||||
<td><?= h($order['delivery_date'] ?: substr((string) ($order['sale_date'] ?? ''), 0, 10)) ?></td>
|
||||
<td><span class="badge rounded-pill text-bg-light border"><?= h(eid_delivery_status_label((string) ($order['delivery_status'] ?? 'pending'))) ?></span></td>
|
||||
<td>
|
||||
<div class="small">
|
||||
<?php foreach (($order['items'] ?? []) as $item): ?>
|
||||
<?php
|
||||
$itemName = trim((string) ($item['name_ar'] ?? ''));
|
||||
if ($itemName === '') {
|
||||
$itemName = trim((string) ($item['name_en'] ?? ''));
|
||||
}
|
||||
if ($itemName === '') {
|
||||
$itemName = trim((string) ($item['sku'] ?? ''));
|
||||
}
|
||||
?>
|
||||
<div><?= h($itemName) ?> <span class="text-muted">× <?= h(rtrim(rtrim(number_format((float) ($item['qty'] ?? 0), 3, '.', ''), '0'), '.')) ?></span></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end fw-semibold"><?= h(number_format((float) ($order['total_amount'] ?? 0), 3)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
<?php require __DIR__ . '/includes/footer.php'; ?>
|
||||
9
eid_sale.php
Normal file
9
eid_sale.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$saleMode = 'normal';
|
||||
$orderType = 'eid';
|
||||
$pageTitle = tr('إنشاء طلب عيد', 'Create Eid Order');
|
||||
$backUrl = url_for('eid_orders.php');
|
||||
$backLabel = tr('عودة لطلبات العيد', 'Back to Eid Orders');
|
||||
$saveLabel = tr('حفظ طلب العيد', 'Save Eid Order');
|
||||
require_once __DIR__ . '/includes/sale_form.php';
|
||||
@ -702,6 +702,42 @@ function payment_status_badge_class(string $status): string
|
||||
};
|
||||
}
|
||||
|
||||
function eid_delivery_status_options(): array
|
||||
{
|
||||
return [
|
||||
'pending' => tr('بانتظار التجهيز', 'Pending Prep'),
|
||||
'preparing' => tr('قيد التجهيز', 'Preparing'),
|
||||
'ready' => tr('جاهز', 'Ready'),
|
||||
'delivered' => tr('تم التسليم', 'Delivered'),
|
||||
'cancelled' => tr('ملغي', 'Cancelled'),
|
||||
];
|
||||
}
|
||||
|
||||
function eid_delivery_status_label(string $status): string
|
||||
{
|
||||
$options = eid_delivery_status_options();
|
||||
return $options[$status] ?? $status;
|
||||
}
|
||||
|
||||
function eid_delivery_status_badge_class(string $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
'preparing' => 'bg-info text-dark',
|
||||
'ready' => 'bg-primary text-white',
|
||||
'delivered' => 'bg-success text-white',
|
||||
'cancelled' => 'bg-danger text-white',
|
||||
default => 'bg-warning text-dark',
|
||||
};
|
||||
}
|
||||
|
||||
function sale_order_type_label(string $type): string
|
||||
{
|
||||
return match ($type) {
|
||||
'eid' => tr('طلبات العيد', 'Eid Orders'),
|
||||
default => tr('بيع عادي', 'Standard Sale'),
|
||||
};
|
||||
}
|
||||
|
||||
function online_payment_method_label(string $method): string
|
||||
{
|
||||
return match ($method) {
|
||||
@ -1673,15 +1709,47 @@ function ensure_sales_table(): void
|
||||
paid_amount DECIMAL(10,3) NOT NULL DEFAULT 0.000,
|
||||
due_amount DECIMAL(10,3) NOT NULL DEFAULT 0.000,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'completed',
|
||||
order_type VARCHAR(30) NOT NULL DEFAULT 'standard',
|
||||
delivery_status VARCHAR(30) NOT NULL DEFAULT 'pending',
|
||||
delivery_date DATE DEFAULT NULL,
|
||||
notes TEXT DEFAULT NULL,
|
||||
sale_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_sale_mode (sale_mode),
|
||||
INDEX idx_branch_code (branch_code),
|
||||
INDEX idx_sale_date (sale_date)
|
||||
INDEX idx_sale_date (sale_date),
|
||||
INDEX idx_order_type (order_type),
|
||||
INDEX idx_delivery_date (delivery_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
|
||||
|
||||
db()->exec($sql);
|
||||
$pdo = db();
|
||||
$pdo->exec($sql);
|
||||
|
||||
$requiredColumns = [
|
||||
'order_type' => "ALTER TABLE sales_orders ADD COLUMN order_type varchar(30) NOT NULL DEFAULT 'standard' AFTER status",
|
||||
'delivery_status' => "ALTER TABLE sales_orders ADD COLUMN delivery_status varchar(30) NOT NULL DEFAULT 'pending' AFTER order_type",
|
||||
'delivery_date' => "ALTER TABLE sales_orders ADD COLUMN delivery_date date DEFAULT NULL AFTER delivery_status",
|
||||
];
|
||||
foreach ($requiredColumns as $column => $columnSql) {
|
||||
$exists = $pdo->query("SHOW COLUMNS FROM sales_orders LIKE " . $pdo->quote($column))->fetchColumn();
|
||||
if (!$exists) {
|
||||
$pdo->exec($columnSql);
|
||||
}
|
||||
}
|
||||
|
||||
$requiredIndexes = [
|
||||
'idx_order_type' => 'ALTER TABLE sales_orders ADD INDEX idx_order_type (order_type)',
|
||||
'idx_delivery_date' => 'ALTER TABLE sales_orders ADD INDEX idx_delivery_date (delivery_date)',
|
||||
];
|
||||
foreach ($requiredIndexes as $indexName => $indexSql) {
|
||||
$hasIndex = $pdo->query("SHOW INDEX FROM sales_orders WHERE Key_name = " . $pdo->quote($indexName))->fetchColumn();
|
||||
if (!$hasIndex) {
|
||||
$pdo->exec($indexSql);
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->exec("UPDATE sales_orders SET order_type = 'standard' WHERE order_type IS NULL OR order_type = ''");
|
||||
$pdo->exec("UPDATE sales_orders SET delivery_status = CASE WHEN COALESCE(status, 'completed') = 'completed' THEN 'delivered' ELSE 'pending' END WHERE delivery_status IS NULL OR delivery_status = ''");
|
||||
}
|
||||
|
||||
function create_sale(array $data): int
|
||||
@ -1695,10 +1763,26 @@ function create_sale(array $data): int
|
||||
? trim((string) $data['receipt_no'])
|
||||
: next_receipt_code($pdo);
|
||||
|
||||
$orderType = trim((string) ($data['order_type'] ?? 'standard'));
|
||||
if (!in_array($orderType, ['standard', 'eid'], true)) {
|
||||
$orderType = 'standard';
|
||||
}
|
||||
|
||||
$defaultDeliveryStatus = (($data['status'] ?? 'completed') === 'completed') ? 'delivered' : 'pending';
|
||||
$deliveryStatus = trim((string) ($data['delivery_status'] ?? $defaultDeliveryStatus));
|
||||
if (!array_key_exists($deliveryStatus, eid_delivery_status_options())) {
|
||||
$deliveryStatus = $defaultDeliveryStatus;
|
||||
}
|
||||
|
||||
$deliveryDate = isset($data['delivery_date']) ? trim((string) $data['delivery_date']) : '';
|
||||
if ($deliveryDate !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $deliveryDate)) {
|
||||
$deliveryDate = '';
|
||||
}
|
||||
|
||||
$stmt = $pdo->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)
|
||||
(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, order_type, delivery_status, delivery_date, 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())');
|
||||
(: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, :order_type, :delivery_status, :delivery_date, :notes, NOW())');
|
||||
|
||||
$stmt->bindValue(':receipt_no', $receiptNo);
|
||||
$stmt->bindValue(':sale_mode', $data['sale_mode']);
|
||||
@ -1719,6 +1803,9 @@ function create_sale(array $data): int
|
||||
$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(':order_type', $orderType);
|
||||
$stmt->bindValue(':delivery_status', $deliveryStatus);
|
||||
$stmt->bindValue(':delivery_date', $deliveryDate !== '' ? $deliveryDate : null, $deliveryDate !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||
$stmt->bindValue(':notes', $data['notes']);
|
||||
$stmt->execute();
|
||||
|
||||
@ -2010,6 +2097,7 @@ function module_cards(): array
|
||||
['title_ar' => 'نقاط البيع', 'title_en' => 'POS Sale', 'path' => 'pos.php', 'desc_ar' => 'إتمام البيع السريع مع تحديث السجل.', 'desc_en' => 'Fast checkout with instant sales logging.'],
|
||||
['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' => 'Eid Orders', 'path' => 'eid_orders.php', 'desc_ar' => 'قائمة مخصصة لطلبات العيد مع فلاتر التاريخ والتجهيز.', 'desc_en' => 'Dedicated Eid orders list with prep and date filters.'],
|
||||
['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.'],
|
||||
['title_ar' => 'التقارير', 'title_en' => 'Reports', 'path' => 'reports.php', 'desc_ar' => 'مبيعات اليوم، الفروع، وأفضل الأصناف.', 'desc_en' => 'Daily sales, branch totals, and best sellers.'],
|
||||
|
||||
@ -29,6 +29,9 @@ $isPublic = !empty($forcePublic) || !isset($user) || !$user;
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<meta property="og:image" content="<?= h($projectImageUrl) ?>" />
|
||||
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($metaRobots)): ?>
|
||||
<meta name="robots" content="<?= h($metaRobots) ?>" />
|
||||
<?php endif; ?>
|
||||
<meta name="theme-color" content="#343a40" />
|
||||
|
||||
@ -96,13 +99,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', '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">
|
||||
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['sales', 'sales_orders', 'eid_orders', 'unpaid', 'normal', 'pos', 'online_orders']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapseSales" role="button" aria-expanded="<?= in_array($activeNav, ['sales', 'sales_orders', 'eid_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', 'sales_orders', 'unpaid', 'normal', 'pos', 'online_orders']) ? 'show' : '' ?>" id="collapseSales" data-bs-parent="#sidebar-navigation">
|
||||
<div class="collapse <?= in_array($activeNav, ['sales', 'sales_orders', 'eid_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')) ?>
|
||||
@ -110,6 +113,12 @@ $isPublic = !empty($forcePublic) || !isset($user) || !$user;
|
||||
<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 === 'eid_orders' ? 'active' : '' ?>" href="<?= h(url_for('eid_orders.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
|
||||
<i class="bi bi-dot"></i> <?= h(tr('طلبات العيد', 'Eid Orders')) ?>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action <?= $activeNav === 'eid_orders' ? 'active' : '' ?>" href="<?= h(url_for('eid_sale.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
|
||||
<i class="bi bi-dot"></i> <?= h(tr('طلب عيد جديد', 'New Eid Order')) ?>
|
||||
</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>
|
||||
|
||||
@ -1,12 +1,24 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/app.php';
|
||||
$user = require_roles(['owner', 'manager', 'cashier']);
|
||||
$pageTitle = $saleMode === 'normal' ? tr('إنشاء فاتورة ضريبية', 'Create Tax Invoice') : tr('نقاط البيع', 'POS Sale');
|
||||
$activeNav = $saleMode === 'normal' ? 'normal' : 'pos';
|
||||
$orderType = isset($orderType) && $orderType === 'eid' ? 'eid' : 'standard';
|
||||
$isEidOrder = $orderType === 'eid';
|
||||
$pageTitle = $pageTitle ?? ($isEidOrder
|
||||
? tr('إنشاء طلب عيد', 'Create Eid Order')
|
||||
: ($saleMode === 'normal' ? tr('إنشاء فاتورة ضريبية', 'Create Tax Invoice') : tr('نقاط البيع', 'POS Sale')));
|
||||
$activeNav = $activeNav ?? ($isEidOrder ? 'eid_orders' : ($saleMode === 'normal' ? 'normal' : 'pos'));
|
||||
$backUrl = $backUrl ?? ($isEidOrder ? url_for('eid_orders.php') : url_for('sales.php'));
|
||||
$backLabel = $backLabel ?? ($isEidOrder ? tr('عودة لطلبات العيد', 'Back to Eid Orders') : tr('عودة للمبيعات', 'Back to Sales'));
|
||||
$saveLabel = $saveLabel ?? ($isEidOrder ? tr('حفظ طلب العيد', 'Save Eid Order') : tr('حفظ الفاتورة', 'Save Invoice'));
|
||||
$error = '';
|
||||
$paymentAmountInput = (string) ($_POST['payment_amount'] ?? '');
|
||||
$catalog = catalog();
|
||||
$allowedBranches = get_user_branches($user);
|
||||
$deliveryOptions = eid_delivery_status_options();
|
||||
$selectedDeliveryStatus = trim((string) ($_POST['delivery_status'] ?? ($isEidOrder ? 'pending' : '')));
|
||||
$deliveryDateInput = trim((string) ($_POST['delivery_date'] ?? ''));
|
||||
$saleStatusInput = trim((string) ($_POST['sale_status'] ?? ($isEidOrder ? 'order' : 'completed')));
|
||||
$notesInput = trim((string) ($_POST['notes'] ?? ''));
|
||||
|
||||
try {
|
||||
$customers = db()->query('SELECT id, name, phone FROM customers ORDER BY name ASC')->fetchAll();
|
||||
@ -20,8 +32,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$customerName = trim((string) ($_POST['customer_name'] ?? ''));
|
||||
$paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash'));
|
||||
$paymentAmountInput = trim((string) ($_POST['payment_amount'] ?? ''));
|
||||
$saleStatus = trim((string) ($_POST['sale_status'] ?? 'completed'));
|
||||
$saleStatus = trim((string) ($_POST['sale_status'] ?? ($isEidOrder ? 'order' : 'completed')));
|
||||
$saleStatusInput = $saleStatus;
|
||||
$notes = trim((string) ($_POST['notes'] ?? ''));
|
||||
$notesInput = $notes;
|
||||
$deliveryStatus = trim((string) ($_POST['delivery_status'] ?? ($isEidOrder ? 'pending' : '')));
|
||||
$selectedDeliveryStatus = $deliveryStatus;
|
||||
$deliveryDate = trim((string) ($_POST['delivery_date'] ?? ''));
|
||||
$deliveryDateInput = $deliveryDate;
|
||||
$cartJson = (string) ($_POST['cart_json'] ?? '[]');
|
||||
$items = json_decode($cartJson, true);
|
||||
|
||||
@ -31,6 +49,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$error = tr('اختر طريقة دفع صحيحة.', 'Choose a valid payment method.');
|
||||
} elseif ($saleStatus === 'order' && !$customerId) {
|
||||
$error = tr('يجب اختيار عميل للطلب المسبق.', 'You must select a customer for a pre-order.');
|
||||
} elseif ($isEidOrder && !isset($deliveryOptions[$deliveryStatus])) {
|
||||
$error = tr('اختر حالة تجهيز صحيحة لطلب العيد.', 'Choose a valid prep status for the Eid order.');
|
||||
} elseif ($isEidOrder && ($deliveryDate === '' || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $deliveryDate))) {
|
||||
$error = tr('حدد تاريخ تسليم صحيح لطلب العيد.', 'Choose a valid delivery date for the Eid order.');
|
||||
} elseif (!is_array($items) || $items === []) {
|
||||
$error = tr('أضف صنفاً واحداً على الأقل إلى الفاتورة.', 'Add at least one item to the invoice.');
|
||||
} else {
|
||||
@ -117,14 +139,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
'vat_amount' => $totalVat,
|
||||
'total_amount' => $totalAmount,
|
||||
'status' => $saleStatus,
|
||||
'order_type' => $orderType,
|
||||
'delivery_status' => $isEidOrder ? $deliveryStatus : null,
|
||||
'delivery_date' => $isEidOrder ? $deliveryDate : null,
|
||||
'notes' => $notes !== '' ? $notes : null,
|
||||
]);
|
||||
|
||||
wablas_notify_sale_invoice($saleId);
|
||||
|
||||
set_flash('success', $saleMode === 'normal'
|
||||
? tr('تم حفظ الفاتورة بنجاح.', 'Invoice saved successfully.')
|
||||
: tr('تم حفظ عملية POS بنجاح.', 'POS sale saved successfully.'));
|
||||
set_flash('success', $isEidOrder
|
||||
? tr('تم حفظ طلب العيد بنجاح.', 'Eid order saved successfully.')
|
||||
: ($saleMode === 'normal'
|
||||
? tr('تم حفظ الفاتورة بنجاح.', 'Invoice saved successfully.')
|
||||
: tr('تم حفظ عملية POS بنجاح.', 'POS sale saved successfully.')));
|
||||
redirect_to('sale.php', ['id' => $saleId]);
|
||||
}
|
||||
}
|
||||
@ -303,8 +330,8 @@ require __DIR__ . '/header.php';
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="fw-bold mb-0 text-dark"><?= h($pageTitle) ?></h3>
|
||||
<a href="sales.php" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-left"></i> <?= h(tr('عودة للمبيعات', 'Back to Sales')) ?>
|
||||
<a href="<?= h($backUrl) ?>" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-left"></i> <?= h($backLabel) ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -314,6 +341,7 @@ require __DIR__ . '/header.php';
|
||||
|
||||
<form method="post" id="smart-sale-form">
|
||||
<input type="hidden" name="cart_json" id="cart_json" value="[]">
|
||||
<input type="hidden" name="order_type" value="<?= h($orderType) ?>">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
@ -388,13 +416,37 @@ require __DIR__ . '/header.php';
|
||||
</div>
|
||||
<div id="formCustomerDropdown" class="item-search-dropdown w-100" style="top: 100%;"></div>
|
||||
</div>
|
||||
<?php if ($isEidOrder): ?>
|
||||
<div class="alert alert-warning border-0 bg-warning-subtle text-dark small mb-3">
|
||||
<i class="bi bi-stars me-1"></i><?= h(tr('سيتم حفظ هذه الفاتورة داخل وحدة طلبات العيد مع تاريخ التسليم وحالة التجهيز.', 'This invoice will be saved inside the Eid Orders module with delivery date and prep status.')) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('نوع العملية', 'Entry Type')) ?></label>
|
||||
<select class="form-select custom-input" name="sale_status">
|
||||
<option value="completed"><?= h(tr('فاتورة بيع (تم الدفع)', 'Sale Bill (Paid)')) ?></option>
|
||||
<option value="order"><?= h(tr('طلب مسبق (دفع لاحق)', 'Order (Pay Later)')) ?></option>
|
||||
<?php if ($isEidOrder): ?>
|
||||
<option value="order" <?= $saleStatusInput === 'order' ? 'selected' : '' ?>><?= h(tr('طلب عيد قيد التجهيز', 'Eid order in preparation')) ?></option>
|
||||
<option value="completed" <?= $saleStatusInput === 'completed' ? 'selected' : '' ?>><?= h(tr('تم التسليم / مكتمل', 'Delivered / Completed')) ?></option>
|
||||
<?php else: ?>
|
||||
<option value="completed" <?= $saleStatusInput === 'completed' ? 'selected' : '' ?>><?= h(tr('فاتورة بيع (تم الدفع)', 'Sale Bill (Paid)')) ?></option>
|
||||
<option value="order" <?= $saleStatusInput === 'order' ? 'selected' : '' ?>><?= h(tr('طلب مسبق (دفع لاحق)', 'Order (Pay Later)')) ?></option>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php if ($isEidOrder): ?>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="delivery_date"><?= h(tr('تاريخ التسليم', 'Delivery date')) ?></label>
|
||||
<input type="date" class="form-control custom-input" id="delivery_date" name="delivery_date" value="<?= h($deliveryDateInput) ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="delivery_status"><?= h(tr('حالة التجهيز', 'Prep status')) ?></label>
|
||||
<select class="form-select custom-input" id="delivery_status" name="delivery_status">
|
||||
<?php foreach ($deliveryOptions as $value => $label): ?>
|
||||
<option value="<?= h($value) ?>" <?= $selectedDeliveryStatus === $value ? 'selected' : '' ?>><?= h($label) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('طريقة الدفع', 'Payment Method')) ?></label>
|
||||
<select class="form-select custom-input" name="payment_method">
|
||||
@ -411,7 +463,7 @@ require __DIR__ . '/header.php';
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label"><?= h(tr('ملاحظات (اختياري)', 'Notes (Optional)')) ?></label>
|
||||
<textarea class="form-control custom-input" name="notes" rows="2" placeholder="<?= h(tr('أي ملاحظات إضافية...', 'Any additional notes...')) ?>"></textarea>
|
||||
<textarea class="form-control custom-input" name="notes" rows="2" placeholder="<?= h(tr('أي ملاحظات إضافية...', 'Any additional notes...')) ?>"><?= h($notesInput) ?></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Summary -->
|
||||
@ -431,7 +483,7 @@ require __DIR__ . '/header.php';
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100 py-2 fs-5 rounded-3 shadow-sm">
|
||||
<i class="bi bi-check-circle me-1"></i> <?= h(tr('حفظ الفاتورة', 'Save Invoice')) ?>
|
||||
<i class="bi bi-check-circle me-1"></i> <?= h($saveLabel) ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -800,17 +852,22 @@ if (paymentMethodField && paymentAmountField) {
|
||||
}
|
||||
|
||||
// Intercept form submission to check if items exist
|
||||
const isEidOrderForm = <?= $isEidOrder ? 'true' : 'false' ?>;
|
||||
document.getElementById('smart-sale-form').addEventListener('submit', function(e) {
|
||||
const paymentMethod = document.querySelector('select[name="payment_method"]').value;
|
||||
const saleStatus = document.querySelector('select[name="sale_status"]').value;
|
||||
const customerId = document.getElementById('formCustomerId').value;
|
||||
const paymentAmount = Math.max(0, parseFloat(document.getElementById('payment_amount').value || '0') || 0);
|
||||
const deliveryDateField = document.getElementById('delivery_date');
|
||||
if (Object.keys(invoiceItems).length === 0) {
|
||||
e.preventDefault();
|
||||
Swal.fire({icon: 'warning', text: '<?= h(tr('الرجاء إضافة أصناف للفاتورة أولاً.', 'Please add items to the invoice first.')) ?>'});
|
||||
} else if (saleStatus === 'order' && !customerId) {
|
||||
e.preventDefault();
|
||||
Swal.fire({icon: 'warning', text: '<?= h(tr('يجب اختيار عميل للطلب المسبق.', 'You must select a customer for a pre-order.')) ?>'});
|
||||
} else if (isEidOrderForm && (!deliveryDateField || !deliveryDateField.value)) {
|
||||
e.preventDefault();
|
||||
Swal.fire({icon: 'warning', text: '<?= h(tr('حدد تاريخ التسليم لطلب العيد.', 'Please choose a delivery date for the Eid order.')) ?>'});
|
||||
} else if (paymentAmount > currentInvoiceTotal + 0.0005) {
|
||||
e.preventDefault();
|
||||
Swal.fire({icon: 'warning', text: '<?= h(tr('المبلغ المدفوع لا يمكن أن يتجاوز إجمالي الفاتورة.', 'Paid amount cannot exceed the invoice total.')) ?>'});
|
||||
|
||||
16
sale.php
16
sale.php
@ -14,6 +14,7 @@ if ($id > 0) {
|
||||
}
|
||||
}
|
||||
$paymentSummary = $sale ? sale_payment_summary($sale) : ['paid_amount' => 0, 'due_amount' => 0, 'payment_status' => 'paid'];
|
||||
$isEidSale = $sale && (($sale['order_type'] ?? 'standard') === 'eid');
|
||||
|
||||
// Company Info for Invoice
|
||||
$companyName = current_lang() === 'ar' ? get_setting('company_name_ar', 'حلوى الريامي') : get_setting('company_name_en', 'Al Riyami Sweets');
|
||||
@ -317,8 +318,8 @@ require __DIR__ . '/includes/header.php';
|
||||
|
||||
<!-- Print Actions -->
|
||||
<div class="print-actions d-flex justify-content-between align-items-center">
|
||||
<a href="sales.php" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-<?= current_lang() === 'ar' ? 'right' : 'left' ?> me-1"></i> <?= h(tr('رجوع للسجل', 'Back to ledger')) ?>
|
||||
<a href="<?= h($isEidSale ? url_for('eid_orders.php') : url_for('sales.php')) ?>" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-<?= current_lang() === 'ar' ? 'right' : 'left' ?> me-1"></i> <?= h($isEidSale ? tr('رجوع لطلبات العيد', 'Back to Eid Orders') : tr('رجوع للسجل', 'Back to ledger')) ?>
|
||||
</a>
|
||||
<div>
|
||||
<a href="<?= h(url_for('edit_sale.php', ['id' => $sale['id']])) ?>" class="btn btn-outline-primary btn-sm me-2">
|
||||
@ -370,6 +371,17 @@ require __DIR__ . '/includes/header.php';
|
||||
|
||||
<div class="meta-label"><?= h(tr('الحالة', 'Status')) ?>:</div>
|
||||
<div class="meta-val"><?= h(payment_status_label($paymentSummary['payment_status'])) ?><?= ($sale['status'] ?? 'completed') === 'order' ? ' · ' . h(tr('طلب حجز', 'Order')) : '' ?></div>
|
||||
|
||||
<?php if ($isEidSale): ?>
|
||||
<div class="meta-label"><?= h(tr('نوع الطلب', 'Order type')) ?>:</div>
|
||||
<div class="meta-val"><?= h(sale_order_type_label((string) ($sale['order_type'] ?? 'standard'))) ?></div>
|
||||
|
||||
<div class="meta-label"><?= h(tr('تاريخ التسليم', 'Delivery date')) ?>:</div>
|
||||
<div class="meta-val"><?= h((string) ($sale['delivery_date'] ?: '—')) ?></div>
|
||||
|
||||
<div class="meta-label"><?= h(tr('حالة التجهيز', 'Prep status')) ?>:</div>
|
||||
<div class="meta-val"><?= h(eid_delivery_status_label((string) ($sale['delivery_status'] ?? 'pending'))) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user