Autosave: 20260505-041812

This commit is contained in:
Flatlogic Bot 2026-05-05 04:18:09 +00:00
parent 4e6f8a4404
commit 46b5eae015
8 changed files with 317 additions and 39 deletions

View File

@ -36,6 +36,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
return $referer; return $referer;
}; };
$pdo = db(); $pdo = db();
$action = trim((string) ($_POST['action'] ?? ''));
if ($action === 'reset_eid_serial') {
ensure_sales_table();
reset_eid_serial_next($pdo, 1);
$respond(true, 'success', tr('تمت إعادة تعيين الرقم التسلسلي القادم لطلبات العيد إلى 1. سيُستخدم هذا للطلبات الجديدة فقط.', 'The next Eid order serial has been reset to 1. This applies to new Eid orders only.'), $redirectBack());
}
$keys = [ $keys = [
'timezone', 'company_name_ar', 'company_name_en', 'vat_percentage', 'timezone', 'company_name_ar', 'company_name_en', 'vat_percentage',
'company_vat_number', 'company_phone', 'company_email', 'company_address', 'company_vat_number', 'company_phone', 'company_email', 'company_address',

View File

@ -128,6 +128,7 @@ CREATE TABLE IF NOT EXISTS `purchase_orders` (
CREATE TABLE IF NOT EXISTS `sales_orders` ( CREATE TABLE IF NOT EXISTS `sales_orders` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`receipt_no` varchar(50) NOT NULL, `receipt_no` varchar(50) NOT NULL,
`eid_serial_no` int(10) unsigned DEFAULT NULL,
`sale_mode` varchar(20) NOT NULL, `sale_mode` varchar(20) NOT NULL,
`branch_code` varchar(30) NOT NULL, `branch_code` varchar(30) NOT NULL,
`cashier_username` varchar(60) NOT NULL, `cashier_username` varchar(60) NOT NULL,

View File

@ -441,12 +441,8 @@ require __DIR__ . '/includes/header.php';
<?php $effectiveDelivery = $order['delivery_date'] ?: date('Y-m-d', strtotime((string) $order['sale_date'])); ?> <?php $effectiveDelivery = $order['delivery_date'] ?: date('Y-m-d', strtotime((string) $order['sale_date'])); ?>
<tr> <tr>
<td> <td>
<?php <?php $eidSerialValue = max(1, (int) ($order['eid_serial_no'] ?? 0)); ?>
$modeSerialNo = max(1, (int) ($order['mode_serial_no'] ?? 0)); <div class="fw-semibold"><?= h(eid_serial_label($eidSerialValue)) ?></div>
$modeSerialPrefix = (($order['sale_mode'] ?? 'normal') === 'pos') ? 'P' : 'N';
$modeSerialLabel = $modeSerialPrefix . '-' . str_pad((string) $modeSerialNo, 4, '0', STR_PAD_LEFT);
?>
<div class="fw-semibold"><?= h($modeSerialLabel) ?></div>
<div class="small text-muted">#<?= h($order['receipt_no']) ?> · <?= h(sale_mode_label((string) ($order['sale_mode'] ?? 'normal'))) ?></div> <div class="small text-muted">#<?= h($order['receipt_no']) ?> · <?= h(sale_mode_label((string) ($order['sale_mode'] ?? 'normal'))) ?></div>
</td> </td>
<?php <?php

View File

@ -1846,6 +1846,7 @@ function ensure_sales_table(): void
'order_type' => "ALTER TABLE sales_orders ADD COLUMN order_type varchar(30) NOT NULL DEFAULT 'standard' AFTER status", '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_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", 'delivery_date' => "ALTER TABLE sales_orders ADD COLUMN delivery_date date DEFAULT NULL AFTER delivery_status",
'eid_serial_no' => "ALTER TABLE sales_orders ADD COLUMN eid_serial_no int(10) unsigned DEFAULT NULL AFTER receipt_no",
]; ];
foreach ($requiredColumns as $column => $columnSql) { foreach ($requiredColumns as $column => $columnSql) {
$exists = $pdo->query("SHOW COLUMNS FROM sales_orders LIKE " . $pdo->quote($column))->fetchColumn(); $exists = $pdo->query("SHOW COLUMNS FROM sales_orders LIKE " . $pdo->quote($column))->fetchColumn();
@ -1867,6 +1868,83 @@ function ensure_sales_table(): void
$pdo->exec("UPDATE sales_orders SET order_type = 'standard' WHERE order_type IS NULL OR order_type = ''"); $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 = ''"); $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 = ''");
$hasEidSerialColumn = $pdo->query("SHOW COLUMNS FROM sales_orders LIKE 'eid_serial_no'")->fetchColumn();
if ($hasEidSerialColumn) {
$existingAssignedStmt = $pdo->query("SELECT COUNT(*) FROM sales_orders WHERE order_type = 'eid' AND eid_serial_no IS NOT NULL AND eid_serial_no > 0");
$existingAssigned = (int) $existingAssignedStmt->fetchColumn();
$nextSerial = $existingAssigned > 0
? max(1, (int) $pdo->query("SELECT COALESCE(MAX(eid_serial_no), 0) + 1 FROM sales_orders WHERE order_type = 'eid'")->fetchColumn())
: 1;
$missingStmt = $pdo->query("SELECT id FROM sales_orders WHERE order_type = 'eid' AND (eid_serial_no IS NULL OR eid_serial_no <= 0) ORDER BY id ASC");
$assignStmt = $pdo->prepare("UPDATE sales_orders SET eid_serial_no = :serial WHERE id = :id");
while ($row = $missingStmt->fetch()) {
$assignStmt->bindValue(':serial', $nextSerial, PDO::PARAM_INT);
$assignStmt->bindValue(':id', (int) $row['id'], PDO::PARAM_INT);
$assignStmt->execute();
$nextSerial++;
}
}
}
function eid_serial_setting_key(): string
{
return 'eid_serial_sequence_next';
}
function current_eid_serial_next(PDO $pdo): int
{
ensure_sales_table();
$seedStmt = $pdo->query("SELECT COALESCE(MAX(eid_serial_no), 0) + 1 FROM sales_orders WHERE order_type = 'eid'");
$seed = max(1, (int) $seedStmt->fetchColumn());
$stmt = $pdo->prepare("SELECT setting_value FROM settings WHERE setting_key = :key LIMIT 1");
$stmt->bindValue(':key', eid_serial_setting_key());
$stmt->execute();
$current = (int) $stmt->fetchColumn();
return $current > 0 ? $current : $seed;
}
function reset_eid_serial_next(PDO $pdo, int $nextNumber = 1): void
{
$stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
$stmt->bindValue(':key', eid_serial_setting_key());
$stmt->bindValue(':value', (string) max(1, $nextNumber));
$stmt->execute();
}
function next_eid_serial_no(PDO $pdo): int
{
ensure_sales_table();
$settingKey = eid_serial_setting_key();
$seedValue = current_eid_serial_next($pdo);
$seedStmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE setting_key = VALUES(setting_key)");
$seedStmt->bindValue(':key', $settingKey);
$seedStmt->bindValue(':value', (string) $seedValue);
$seedStmt->execute();
$selectStmt = $pdo->prepare("SELECT setting_value FROM settings WHERE setting_key = :key FOR UPDATE");
$selectStmt->bindValue(':key', $settingKey);
$selectStmt->execute();
$nextNumber = max(1, (int) $selectStmt->fetchColumn());
$updateStmt = $pdo->prepare("UPDATE settings SET setting_value = :next_value WHERE setting_key = :key");
$updateStmt->bindValue(':next_value', (string) ($nextNumber + 1));
$updateStmt->bindValue(':key', $settingKey);
$updateStmt->execute();
return $nextNumber;
}
function eid_serial_label($serial): string
{
return 'E-' . str_pad((string) max(1, (int) $serial), 4, '0', STR_PAD_LEFT);
} }
function create_sale(array $data): int function create_sale(array $data): int
@ -1885,6 +1963,8 @@ function create_sale(array $data): int
$orderType = 'standard'; $orderType = 'standard';
} }
$eidSerialNo = $orderType === 'eid' ? next_eid_serial_no($pdo) : null;
$defaultDeliveryStatus = (($data['status'] ?? 'completed') === 'completed') ? 'delivered' : 'pending'; $defaultDeliveryStatus = (($data['status'] ?? 'completed') === 'completed') ? 'delivered' : 'pending';
$deliveryStatus = trim((string) ($data['delivery_status'] ?? $defaultDeliveryStatus)); $deliveryStatus = trim((string) ($data['delivery_status'] ?? $defaultDeliveryStatus));
if (!array_key_exists($deliveryStatus, eid_delivery_status_options())) { if (!array_key_exists($deliveryStatus, eid_delivery_status_options())) {
@ -1897,11 +1977,12 @@ function create_sale(array $data): int
} }
$stmt = $pdo->prepare('INSERT INTO sales_orders $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, order_type, delivery_status, delivery_date, notes, sale_date) (receipt_no, eid_serial_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 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, :order_type, :delivery_status, :delivery_date, :notes, NOW())'); (:receipt_no, :eid_serial_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(':receipt_no', $receiptNo);
$stmt->bindValue(':eid_serial_no', $eidSerialNo, $eidSerialNo === null ? PDO::PARAM_NULL : PDO::PARAM_INT);
$stmt->bindValue(':sale_mode', $data['sale_mode']); $stmt->bindValue(':sale_mode', $data['sale_mode']);
$stmt->bindValue(':branch_code', $data['branch_code']); $stmt->bindValue(':branch_code', $data['branch_code']);
$stmt->bindValue(':cashier_username', $data['cashier_username']); $stmt->bindValue(':cashier_username', $data['cashier_username']);

View File

@ -9,6 +9,7 @@
$wablasDailyAutoEnabled = (string) get_setting('wablas_daily_auto_send', '0') === '1'; $wablasDailyAutoEnabled = (string) get_setting('wablas_daily_auto_send', '0') === '1';
$wablasDailyAutoTime = wablas_format_time_setting((string) get_setting('wablas_daily_auto_time', '21:00')); $wablasDailyAutoTime = wablas_format_time_setting((string) get_setting('wablas_daily_auto_time', '21:00'));
$wablasDailyAutoLastDate = (string) get_setting('wablas_daily_auto_last_date', ''); $wablasDailyAutoLastDate = (string) get_setting('wablas_daily_auto_last_date', '');
$eidSerialNext = current_eid_serial_next(db());
?> ?>
<!-- Settings Modal --> <!-- Settings Modal -->
<div class="modal fade" id="settingsModal" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="settingsModal" tabindex="-1" aria-hidden="true">
@ -16,15 +17,25 @@
<div class="modal-content"> <div class="modal-content">
<form action="api/settings.php" method="POST" enctype="multipart/form-data"> <form action="api/settings.php" method="POST" enctype="multipart/form-data">
<div class="modal-header"> <div class="modal-header">
<div> <div class="w-100">
<h5 class="modal-title mb-1"><?= h(tr('إعدادات الشركة', 'Company Settings')) ?></h5> <div class="settings-header-toolbar">
<div class="small text-muted"><?= h(tr('إعدادات واتساب أصبحت في نافذة مستقلة حتى تظهر جميع القوالب بوضوح.', 'WhatsApp settings now open in a separate popup so all templates stay visible.')) ?></div> <div class="settings-top-actions">
</div> <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal" dir="auto"><?= h(tr('إلغاء', 'Cancel')) ?></button>
<div class="d-flex align-items-center gap-2"> <button type="submit" class="btn btn-primary btn-sm" dir="auto">
<button type="button" class="btn btn-outline-success btn-sm" data-open-wablas-settings="1"> <i class="bi bi-save me-1"></i><?= h(tr('حفظ التغييرات', 'Save Changes')) ?>
<i class="bi bi-whatsapp me-1"></i><?= h(tr('واتساب', 'WhatsApp')) ?> </button>
</button> </div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <div class="d-flex align-items-center gap-2">
<button type="button" class="btn btn-outline-success btn-sm" data-open-wablas-settings="1">
<i class="bi bi-whatsapp me-1"></i><?= h(tr('واتساب', 'WhatsApp')) ?>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="mt-3">
<h5 class="modal-title mb-1"><?= h(tr('إعدادات الشركة', 'Company Settings')) ?></h5>
<div class="small text-muted"><?= h(tr('إعدادات واتساب أصبحت في نافذة مستقلة حتى تظهر جميع القوالب بوضوح.', 'WhatsApp settings now open in a separate popup so all templates stay visible.')) ?></div>
</div>
</div> </div>
</div> </div>
<div class="modal-body pb-2"> <div class="modal-body pb-2">
@ -116,6 +127,34 @@
<div class="mt-2"><img src="<?= h(get_setting('company_favicon')) ?>" height="32" style="background: #f8f9fa; padding: 2px; border-radius: 4px;"></div> <div class="mt-2"><img src="<?= h(get_setting('company_favicon')) ?>" height="32" style="background: #f8f9fa; padding: 2px; border-radius: 4px;"></div>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="col-md-12">
<div class="rounded-4 border bg-body-tertiary p-3 settings-eid-card">
<div class="small text-uppercase text-muted mb-1"><?= h(tr('طلبات العيد', 'Eid orders')) ?></div>
<h6 class="fw-bold mb-1"><?= h(tr('إعادة ضبط التسلسل الموسمي', 'Reset seasonal serial')) ?></h6>
<p class="text-muted small mb-2"><?= h(tr('يعيد الرقم القادم لطلبات العيد إلى 1 للطلبات الجديدة فقط، بدون تغيير أرقام الطلبات السابقة.', 'Resets the next Eid order serial to 1 for new orders only, without changing older saved orders.')) ?></p>
<div class="bg-white border rounded-3 p-3 settings-eid-reset-box">
<div class="d-flex flex-column flex-sm-row justify-content-between align-items-sm-center gap-2">
<div>
<div class="small text-muted mb-1"><?= h(tr('الرقم القادم', 'Next serial')) ?></div>
<div class="fs-5 fw-bold mb-0" data-eid-serial-next-label="1"><?= h(eid_serial_label($eidSerialNext)) ?></div>
</div>
<button
type="button"
class="btn btn-outline-danger btn-sm px-3 align-self-start align-self-sm-center text-nowrap"
data-reset-eid-serial="1"
data-reset-url="<?= h(url_for('api/settings.php')) ?>"
data-reset-label="<?= h(eid_serial_label(1)) ?>"
data-reset-confirm="<?= h(tr('سيتم تعيين الرقم القادم لطلبات العيد إلى 1. لن يتم تعديل الطلبات القديمة. هل تريد المتابعة؟', 'The next Eid order serial will be reset to 1. Old orders will not be changed. Do you want to continue?')) ?>"
data-reset-success="<?= h(tr('تمت إعادة الضبط بنجاح.', 'Reset completed successfully.')) ?>"
data-reset-error="<?= h(tr('تعذر تنفيذ إعادة الضبط حالياً. حاول مرة أخرى.', 'Could not reset the Eid serial right now. Please try again.')) ?>"
dir="auto">
<i class="bi bi-arrow-counterclockwise me-1"></i><?= h(tr('إعادة الضبط إلى 1', 'Reset to 1')) ?>
</button>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="tab-pane fade" id="settings-wablas-pane" role="tabpanel" aria-labelledby="settings-wablas-tab" tabindex="0"> <div class="tab-pane fade" id="settings-wablas-pane" role="tabpanel" aria-labelledby="settings-wablas-tab" tabindex="0">
@ -274,18 +313,14 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= h(tr('إلغاء', 'Cancel')) ?></button>
<button type="submit" class="btn btn-primary"><?= h(tr('حفظ التغييرات', 'Save Changes')) ?></button>
</div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<style> <style>
#settingsModal .modal-header, #settingsModal .modal-header {
#settingsModal .modal-footer {
position: sticky; position: sticky;
z-index: 3; z-index: 3;
background: var(--bs-body-bg); background: var(--bs-body-bg);
@ -296,14 +331,33 @@
border-bottom: 1px solid var(--bs-border-color); border-bottom: 1px solid var(--bs-border-color);
} }
#settingsModal .modal-footer { #settingsModal .settings-header-toolbar {
bottom: 0; direction: ltr;
border-top: 1px solid var(--bs-border-color); display: flex;
box-shadow: 0 -10px 30px rgba(15, 23, 42, 0.06); flex-wrap: wrap;
align-items: flex-start;
justify-content: space-between;
gap: 0.75rem;
}
#settingsModal .settings-top-actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
} }
#settingsModal .modal-body { #settingsModal .modal-body {
padding-bottom: 1.25rem; padding-top: 0.85rem;
padding-bottom: 1rem;
}
#settingsModal .settings-eid-card {
margin-top: -0.125rem;
}
#settingsModal .settings-eid-reset-box {
padding-top: 0.875rem;
padding-bottom: 0.875rem;
} }
#settingsModal .settings-inline-save { #settingsModal .settings-inline-save {
@ -313,6 +367,21 @@
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.08); box-shadow: 0 12px 30px rgba(15, 23, 42, 0.08);
} }
@media (max-width: 767.98px) {
#settingsModal .settings-top-actions {
width: 100%;
}
#settingsModal .settings-top-actions .btn {
flex: 1 1 0;
}
#settingsModal .settings-header-toolbar > .d-flex {
width: 100%;
justify-content: flex-end;
}
}
#wablasSettingsModal { #wablasSettingsModal {
overflow-y: auto; overflow-y: auto;
} }
@ -593,6 +662,93 @@ document.addEventListener('DOMContentLoaded', function () {
} }
} }
document.querySelectorAll('[data-reset-eid-serial]').forEach(function (button) {
button.addEventListener('click', function () {
var actionUrl = button.getAttribute('data-reset-url') || 'api/settings.php';
var confirmMessage = button.getAttribute('data-reset-confirm') || '';
var successMessage = button.getAttribute('data-reset-success') || 'Done.';
var errorMessage = button.getAttribute('data-reset-error') || 'Request failed.';
var nextLabel = button.getAttribute('data-reset-label') || 'E-0001';
var nextLabelEl = document.querySelector('[data-eid-serial-next-label]');
var executeReset = function () {
var formData = new FormData();
formData.append('action', 'reset_eid_serial');
formData.append('return_modal', 'settings');
var originalHtml = button.innerHTML;
button.disabled = true;
button.setAttribute('aria-busy', 'true');
button.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span><?= h(tr('جارٍ التنفيذ...', 'Processing...')) ?>';
fetch(actionUrl, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
},
credentials: 'same-origin'
}).then(function (response) {
return response.json().catch(function () {
return { success: false, type: 'danger', message: errorMessage };
});
}).then(function (data) {
var ok = !!(data && data.success);
var type = data && data.type ? data.type : (ok ? 'success' : 'danger');
var message = data && data.message ? data.message : (ok ? successMessage : errorMessage);
if (ok && nextLabelEl) {
nextLabelEl.textContent = nextLabel;
}
if (typeof Swal !== 'undefined') {
Swal.fire({
icon: type === 'success' ? 'success' : (type === 'warning' ? 'warning' : 'error'),
title: message,
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3200,
timerProgressBar: true
});
} else if (!ok) {
window.alert(message);
}
}).catch(function () {
if (typeof Swal !== 'undefined') {
Swal.fire({ icon: 'error', title: errorMessage, toast: true, position: 'top-end', showConfirmButton: false, timer: 3200, timerProgressBar: true });
} else {
window.alert(errorMessage);
}
}).finally(function () {
button.disabled = false;
button.removeAttribute('aria-busy');
button.innerHTML = originalHtml;
});
};
if (typeof Swal !== 'undefined') {
Swal.fire({
icon: 'warning',
title: confirmMessage,
showCancelButton: true,
confirmButtonText: '<?= h(tr('نعم، إعادة الضبط', 'Yes, reset')) ?>',
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
}).then(function (result) {
if (result.isConfirmed) {
executeReset();
}
});
return;
}
if (window.confirm(confirmMessage)) {
executeReset();
}
});
});
if (wablasForm && window.fetch) { if (wablasForm && window.fetch) {
wablasForm.addEventListener('submit', function (event) { wablasForm.addEventListener('submit', function (event) {
var submitter = event.submitter; var submitter = event.submitter;

View File

@ -203,6 +203,12 @@ $registerNo = 'REG-01';
<span><?= h(tr('رقم الفاتورة', 'Receipt No')) ?>:</span> <span><?= h(tr('رقم الفاتورة', 'Receipt No')) ?>:</span>
<span class="font-bold"><?= h($sale['receipt_no']) ?></span> <span class="font-bold"><?= h($sale['receipt_no']) ?></span>
</div> </div>
<?php if ($isEidSale): ?>
<div class="sale-info">
<span><?= h(tr('التسلسل الموسمي', 'Season serial')) ?>:</span>
<span class="font-bold"><?= h(eid_serial_label((int) ($sale['eid_serial_no'] ?? 0))) ?></span>
</div>
<?php endif; ?>
<div class="sale-info"> <div class="sale-info">
<span><?= h(tr('التاريخ', 'Date')) ?>:</span> <span><?= h(tr('التاريخ', 'Date')) ?>:</span>
<span><?= h(date('Y-m-d H:i', strtotime((string)$sale['sale_date']))) ?></span> <span><?= h(date('Y-m-d H:i', strtotime((string)$sale['sale_date']))) ?></span>

View File

@ -374,6 +374,9 @@ require __DIR__ . '/includes/header.php';
<div class="meta-val"><?= h(payment_status_label($paymentSummary['payment_status'])) ?><?= ($sale['status'] ?? 'completed') === 'order' ? ' · ' . h(tr('طلب حجز', 'Order')) : '' ?></div> <div class="meta-val"><?= h(payment_status_label($paymentSummary['payment_status'])) ?><?= ($sale['status'] ?? 'completed') === 'order' ? ' · ' . h(tr('طلب حجز', 'Order')) : '' ?></div>
<?php if ($isEidSale): ?> <?php if ($isEidSale): ?>
<div class="meta-label"><?= h(tr('التسلسل الموسمي', 'Season serial')) ?>:</div>
<div class="meta-val"><?= h(eid_serial_label((int) ($sale['eid_serial_no'] ?? 0))) ?></div>
<div class="meta-label"><?= h(tr('نوع الطلب', 'Order type')) ?>:</div> <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-val"><?= h(sale_order_type_label((string) ($sale['order_type'] ?? 'standard'))) ?></div>

View File

@ -28,6 +28,7 @@ if ($deliveryStatus !== '' && !array_key_exists($deliveryStatus, $deliveryOption
$allowedBranches = $user && $user['role'] !== 'owner' ? get_user_branches($user) : array_keys(branches()); $allowedBranches = $user && $user['role'] !== 'owner' ? get_user_branches($user) : array_keys(branches());
$activeNav = $statusFilter === 'order' ? 'sales_orders' : 'sales'; $activeNav = $statusFilter === 'order' ? 'sales_orders' : 'sales';
$pageTitle = $statusFilter === 'order' ? tr('الطلبات', 'Orders') : tr('المبيعات', 'Sales Ledger'); $pageTitle = $statusFilter === 'order' ? tr('الطلبات', 'Orders') : tr('المبيعات', 'Sales Ledger');
$canDeleteSales = $user['role'] === 'owner' || has_permission('sales', 'del');
if (isset($_GET['mark_paid']) && is_numeric($_GET['mark_paid'])) { if (isset($_GET['mark_paid']) && is_numeric($_GET['mark_paid'])) {
try { try {
@ -45,6 +46,31 @@ if (isset($_GET['mark_paid']) && is_numeric($_GET['mark_paid'])) {
exit; exit;
} }
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_sale') {
if (!$canDeleteSales) {
set_flash('danger', tr('ليس لديك صلاحية حذف الفواتير.', 'You do not have permission to delete invoices.'));
redirect_to('sales.php', $_GET);
}
$id = (int) ($_POST['id'] ?? 0);
try {
ensure_sales_table();
$sale = fetch_sale($id);
if (!$sale) {
set_flash('warning', tr('الفاتورة غير موجودة.', 'Invoice was not found.'));
} elseif ($user['role'] !== 'owner' && !in_array((string) ($sale['branch_code'] ?? ''), $allowedBranches, true)) {
set_flash('danger', tr('لا يمكنك حذف هذه الفاتورة.', 'You cannot delete this invoice.'));
} else {
$stmt = db()->prepare('DELETE FROM sales_orders WHERE id = :id');
$stmt->execute([':id' => $id]);
set_flash('success', tr('تم حذف الفاتورة نهائياً.', 'Invoice deleted permanently.'));
}
} catch (Throwable $e) {
set_flash('danger', tr('تعذر حذف الفاتورة.', 'Failed to delete invoice.'));
}
redirect_to('sales.php', $_GET);
}
$dbError = null; $dbError = null;
$sales = []; $sales = [];
$totalPages = 1; $totalPages = 1;
@ -341,9 +367,15 @@ require __DIR__ . '/includes/header.php';
<a class="btn btn-sm btn-outline-secondary rounded-circle shadow-sm" style="width: 30px; height: 30px; padding: 0; line-height: 28px; text-align: center;" href="<?= h(url_for('edit_sale.php', ['id' => $sale['id']])) ?>" title="<?= h(tr('تعديل', 'Edit')) ?>"> <a class="btn btn-sm btn-outline-secondary rounded-circle shadow-sm" style="width: 30px; height: 30px; padding: 0; line-height: 28px; text-align: center;" href="<?= h(url_for('edit_sale.php', ['id' => $sale['id']])) ?>" title="<?= h(tr('تعديل', 'Edit')) ?>">
<i class="bi bi-pencil"></i> <i class="bi bi-pencil"></i>
</a> </a>
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm" style="width: 30px; height: 30px; padding: 0; line-height: 28px;" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>"> <?php if ($canDeleteSales): ?>
<i class="bi bi-trash"></i> <form method="post" action="" class="d-inline-block">
</button> <input type="hidden" name="action" value="delete_sale">
<input type="hidden" name="id" value="<?= h($sale['id']) ?>">
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle shadow-sm" style="width: 30px; height: 30px; padding: 0; line-height: 28px;" onclick="confirmDeleteSale(this.form)" title="<?= h(tr('حذف', 'Delete')) ?>">
<i class="bi bi-trash"></i>
</button>
</form>
<?php endif; ?>
</div> </div>
</td> </td>
</tr> </tr>
@ -470,10 +502,10 @@ function mockEdit() {
}); });
} }
function mockDelete() { function confirmDeleteSale(form) {
Swal.fire({ Swal.fire({
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>', title: '<?= h(tr('هل أنت متأكد من حذف الفاتورة؟', 'Are you sure you want to delete this invoice?')) ?>',
text: '<?= h(tr('حذف الفاتورة قد يؤثر على حسابات المخزون. (هذه الميزة تجريبية حالياً)', "Deleting an invoice might affect stock. (This is currently mock data)")) ?>', text: '<?= h(tr('سيتم حذف الفاتورة نهائياً من النظام. استخدم هذا الخيار فقط عند الضرورة.', 'This will permanently delete the invoice from the system. Use this only when necessary.')) ?>',
icon: 'warning', icon: 'warning',
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#dc3545', confirmButtonColor: '#dc3545',
@ -482,11 +514,7 @@ function mockDelete() {
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>' cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
}).then((result) => { }).then((result) => {
if (result.isConfirmed) { if (result.isConfirmed) {
Swal.fire( form.submit();
'<?= h(tr('محذوف!', 'Deleted!')) ?>',
'<?= h(tr('لم يتم الحذف فعلياً.', 'Not actually deleted.')) ?>',
'success'
);
} }
}); });
} }