Autosave: 20260505-041812
This commit is contained in:
parent
4e6f8a4404
commit
46b5eae015
@ -36,6 +36,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
return $referer;
|
||||
};
|
||||
$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 = [
|
||||
'timezone', 'company_name_ar', 'company_name_en', 'vat_percentage',
|
||||
'company_vat_number', 'company_phone', 'company_email', 'company_address',
|
||||
|
||||
@ -128,6 +128,7 @@ CREATE TABLE IF NOT EXISTS `purchase_orders` (
|
||||
CREATE TABLE IF NOT EXISTS `sales_orders` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`receipt_no` varchar(50) NOT NULL,
|
||||
`eid_serial_no` int(10) unsigned DEFAULT NULL,
|
||||
`sale_mode` varchar(20) NOT NULL,
|
||||
`branch_code` varchar(30) NOT NULL,
|
||||
`cashier_username` varchar(60) NOT NULL,
|
||||
|
||||
@ -441,12 +441,8 @@ require __DIR__ . '/includes/header.php';
|
||||
<?php $effectiveDelivery = $order['delivery_date'] ?: date('Y-m-d', strtotime((string) $order['sale_date'])); ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php
|
||||
$modeSerialNo = max(1, (int) ($order['mode_serial_no'] ?? 0));
|
||||
$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>
|
||||
<?php $eidSerialValue = max(1, (int) ($order['eid_serial_no'] ?? 0)); ?>
|
||||
<div class="fw-semibold"><?= h(eid_serial_label($eidSerialValue)) ?></div>
|
||||
<div class="small text-muted">#<?= h($order['receipt_no']) ?> · <?= h(sale_mode_label((string) ($order['sale_mode'] ?? 'normal'))) ?></div>
|
||||
</td>
|
||||
<?php
|
||||
|
||||
@ -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",
|
||||
'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",
|
||||
'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) {
|
||||
$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 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
|
||||
@ -1885,6 +1963,8 @@ function create_sale(array $data): int
|
||||
$orderType = 'standard';
|
||||
}
|
||||
|
||||
$eidSerialNo = $orderType === 'eid' ? next_eid_serial_no($pdo) : null;
|
||||
|
||||
$defaultDeliveryStatus = (($data['status'] ?? 'completed') === 'completed') ? 'delivered' : 'pending';
|
||||
$deliveryStatus = trim((string) ($data['delivery_status'] ?? $defaultDeliveryStatus));
|
||||
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
|
||||
(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
|
||||
(: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(':eid_serial_no', $eidSerialNo, $eidSerialNo === null ? PDO::PARAM_NULL : PDO::PARAM_INT);
|
||||
$stmt->bindValue(':sale_mode', $data['sale_mode']);
|
||||
$stmt->bindValue(':branch_code', $data['branch_code']);
|
||||
$stmt->bindValue(':cashier_username', $data['cashier_username']);
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
$wablasDailyAutoEnabled = (string) get_setting('wablas_daily_auto_send', '0') === '1';
|
||||
$wablasDailyAutoTime = wablas_format_time_setting((string) get_setting('wablas_daily_auto_time', '21:00'));
|
||||
$wablasDailyAutoLastDate = (string) get_setting('wablas_daily_auto_last_date', '');
|
||||
$eidSerialNext = current_eid_serial_next(db());
|
||||
?>
|
||||
<!-- Settings Modal -->
|
||||
<div class="modal fade" id="settingsModal" tabindex="-1" aria-hidden="true">
|
||||
@ -16,15 +17,25 @@
|
||||
<div class="modal-content">
|
||||
<form action="api/settings.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<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 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 class="w-100">
|
||||
<div class="settings-header-toolbar">
|
||||
<div class="settings-top-actions">
|
||||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal" dir="auto"><?= h(tr('إلغاء', 'Cancel')) ?></button>
|
||||
<button type="submit" class="btn btn-primary btn-sm" dir="auto">
|
||||
<i class="bi bi-save me-1"></i><?= h(tr('حفظ التغييرات', 'Save Changes')) ?>
|
||||
</button>
|
||||
</div>
|
||||
<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 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>
|
||||
<?php endif; ?>
|
||||
</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 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 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#settingsModal .modal-header,
|
||||
#settingsModal .modal-footer {
|
||||
#settingsModal .modal-header {
|
||||
position: sticky;
|
||||
z-index: 3;
|
||||
background: var(--bs-body-bg);
|
||||
@ -296,14 +331,33 @@
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
}
|
||||
|
||||
#settingsModal .modal-footer {
|
||||
bottom: 0;
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
box-shadow: 0 -10px 30px rgba(15, 23, 42, 0.06);
|
||||
#settingsModal .settings-header-toolbar {
|
||||
direction: ltr;
|
||||
display: flex;
|
||||
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 {
|
||||
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 {
|
||||
@ -313,6 +367,21 @@
|
||||
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 {
|
||||
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) {
|
||||
wablasForm.addEventListener('submit', function (event) {
|
||||
var submitter = event.submitter;
|
||||
|
||||
@ -203,6 +203,12 @@ $registerNo = 'REG-01';
|
||||
<span><?= h(tr('رقم الفاتورة', 'Receipt No')) ?>:</span>
|
||||
<span class="font-bold"><?= h($sale['receipt_no']) ?></span>
|
||||
</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">
|
||||
<span><?= h(tr('التاريخ', 'Date')) ?>:</span>
|
||||
<span><?= h(date('Y-m-d H:i', strtotime((string)$sale['sale_date']))) ?></span>
|
||||
|
||||
3
sale.php
3
sale.php
@ -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>
|
||||
|
||||
<?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-val"><?= h(sale_order_type_label((string) ($sale['order_type'] ?? 'standard'))) ?></div>
|
||||
|
||||
|
||||
50
sales.php
50
sales.php
@ -28,6 +28,7 @@ if ($deliveryStatus !== '' && !array_key_exists($deliveryStatus, $deliveryOption
|
||||
$allowedBranches = $user && $user['role'] !== 'owner' ? get_user_branches($user) : array_keys(branches());
|
||||
$activeNav = $statusFilter === 'order' ? 'sales_orders' : 'sales';
|
||||
$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'])) {
|
||||
try {
|
||||
@ -45,6 +46,31 @@ if (isset($_GET['mark_paid']) && is_numeric($_GET['mark_paid'])) {
|
||||
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;
|
||||
$sales = [];
|
||||
$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')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</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')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
<?php if ($canDeleteSales): ?>
|
||||
<form method="post" action="" class="d-inline-block">
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
@ -470,10 +502,10 @@ function mockEdit() {
|
||||
});
|
||||
}
|
||||
|
||||
function mockDelete() {
|
||||
function confirmDeleteSale(form) {
|
||||
Swal.fire({
|
||||
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
|
||||
text: '<?= h(tr('حذف الفاتورة قد يؤثر على حسابات المخزون. (هذه الميزة تجريبية حالياً)', "Deleting an invoice might affect stock. (This is currently mock data)")) ?>',
|
||||
title: '<?= h(tr('هل أنت متأكد من حذف الفاتورة؟', 'Are you sure you want to delete this invoice?')) ?>',
|
||||
text: '<?= h(tr('سيتم حذف الفاتورة نهائياً من النظام. استخدم هذا الخيار فقط عند الضرورة.', 'This will permanently delete the invoice from the system. Use this only when necessary.')) ?>',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#dc3545',
|
||||
@ -482,11 +514,7 @@ function mockDelete() {
|
||||
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
Swal.fire(
|
||||
'<?= h(tr('محذوف!', 'Deleted!')) ?>',
|
||||
'<?= h(tr('لم يتم الحذف فعلياً.', 'Not actually deleted.')) ?>',
|
||||
'success'
|
||||
);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user