39728-vm/edit_sale.php
2026-05-01 18:08:28 +00:00

902 lines
40 KiB
PHP

<?php
require_once __DIR__ . '/includes/app.php';
$user = require_permission('sales', 'edit');
$editSaleId = (int)($_GET['id'] ?? 0);
$editSale = null;
if ($editSaleId > 0) {
$stmt = db()->prepare('SELECT * FROM sales_orders WHERE id = :id');
$stmt->execute([':id' => $editSaleId]);
$editSale = $stmt->fetch();
}
if ($editSale) {
$editSale['items'] = json_decode((string) ($editSale['items_json'] ?? '[]'), true) ?: [];
}
if (!$editSale) {
die(tr('الفاتورة غير موجودة.', 'Invoice not found.'));
}
if ($user['role'] !== 'owner' && $editSale['branch_code'] !== $user['branch_code']) {
die(tr('غير مصرح لك.', 'Unauthorized.'));
}
$pageTitle = tr('تعديل فاتورة', 'Edit Invoice') . ' #' . h($editSale['receipt_no']);
$isEidSale = (($editSale['order_type'] ?? 'standard') === 'eid');
$activeNav = $isEidSale ? 'eid_orders' : 'sales';
$error = '';
$editPaymentSummary = sale_payment_summary($editSale);
$paymentAmountInput = (string) ($_POST['payment_amount'] ?? 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();
} catch (Throwable $e) {
$customers = [];
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$branchCode = trim((string) ($_POST['branch_code'] ?? ''));
$customerId = isset($_POST['customer_id']) && $_POST['customer_id'] !== '' ? (int)$_POST['customer_id'] : null;
$customerName = trim((string) ($_POST['customer_name'] ?? ''));
$paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash'));
$paymentAmountInput = trim((string) ($_POST['payment_amount'] ?? ''));
$paymentAmountForCalculation = $paymentAmountInput === '' ? '0' : $paymentAmountInput;
$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);
if (!in_array($branchCode, $allowedBranches, true)) {
$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 {
$normalized = [];
$subtotal = 0.0;
$totalVat = 0.0;
$itemCount = 0;
foreach ($items as $item) {
$sku = (string) ($item['sku'] ?? '');
$qty = (int) ($item['qty'] ?? 0);
if (!isset($catalog[$sku]) || $qty < 1) {
continue;
}
$product = $catalog[$sku];
$price = (float) $product['price'];
$lineTotal = $price * $qty;
$vatPercent = (float) ($product['vat'] ?? 0);
$itemVat = $lineTotal * ($vatPercent / 100);
$totalVat += $itemVat;
$normalized[] = [
'sku' => $sku,
'name_ar' => $product['name_ar'],
'name_en' => $product['name_en'],
'qty' => $qty,
'price' => $price,
'line_total' => $lineTotal,
'vat_percent' => $vatPercent,
'vat_amount' => $itemVat
];
$subtotal += $lineTotal;
$itemCount += $qty;
}
if ($normalized === []) {
$error = tr('الفاتورة غير صالحة بعد التحقق من الأصناف.', 'The invoice is invalid after product validation.');
} else {
$totalAmount = $subtotal + $totalVat;
if ($paymentAmountInput !== '' && !is_numeric($paymentAmountInput)) {
$error = tr('أدخل مبلغاً مدفوعاً صحيحاً.', 'Enter a valid paid amount.');
} else {
$paymentMeta = sale_payment_breakdown($totalAmount, $paymentMethod, $paymentAmountForCalculation);
if ($paymentMeta['due_amount'] > 0.0005 && !$customerId) {
$error = tr('يجب اختيار عميل مسجل عند وجود مبلغ متبقٍ أو دفعة جزئية.', 'Select a registered customer when there is a remaining balance or partial payment.');
}
}
}
if ($error === '') {
$cashierName = current_lang() === 'ar' ? $user['name_ar'] : $user['name_en'];
db()->beginTransaction();
try {
sync_order_stock_reservation(
$editSale['items'] ?? [],
(string) ($editSale['status'] ?? 'completed'),
$normalized,
$saleStatus
);
$stmt = db()->prepare('UPDATE sales_orders SET
branch_code = :branch_code,
customer_id = :customer_id,
customer_name = :customer_name,
payment_method = :payment_method,
payment_status = :payment_status,
paid_amount = :paid_amount,
due_amount = :due_amount,
items_json = :items_json,
item_count = :item_count,
subtotal = :subtotal,
vat_amount = :vat_amount,
total_amount = :total_amount,
status = :status,
delivery_status = :delivery_status,
delivery_date = :delivery_date,
notes = :notes
WHERE id = :id');
$stmt->execute([
':branch_code' => $branchCode,
':customer_id' => $customerId,
':customer_name' => $customerName !== '' ? $customerName : null,
':payment_method' => $paymentMethod,
':payment_status' => $paymentMeta['payment_status'],
':paid_amount' => $paymentMeta['paid_amount'],
':due_amount' => $paymentMeta['due_amount'],
':items_json' => json_encode($normalized, JSON_UNESCAPED_UNICODE),
':item_count' => $itemCount,
':subtotal' => $subtotal,
':vat_amount' => $totalVat,
':total_amount' => $totalAmount,
':status' => $saleStatus,
':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,
]);
db()->commit();
} catch (Throwable $e) {
if (db()->inTransaction()) {
db()->rollBack();
}
$error = tr('تعذر تحديث الفاتورة.', 'Could not update the invoice.');
}
if ($error === '') {
$flashType = 'success';
$flashMessage = tr('تم تحديث الفاتورة بنجاح.', 'Invoice updated successfully.');
if ($isEidSale && wablas_is_configured()) {
$wablasResult = wablas_notify_sale_invoice($editSaleId);
if (!empty($wablasResult['success'])) {
$flashMessage = tr('تم تحديث الفاتورة وإعادة إرسالها عبر واتساب بنجاح.', 'Invoice updated and resent via WhatsApp successfully.');
} else {
$flashType = 'warning';
$flashMessage = tr('تم تحديث الفاتورة، لكن تعذر إعادة إرسالها عبر واتساب. تحقق من رقم واتساب العميل أو إعدادات واتساب.', 'Invoice updated, but resending via WhatsApp failed. Check the customer WhatsApp number or WhatsApp settings.');
}
}
set_flash($flashType, $flashMessage);
redirect_to($isEidSale ? 'eid_orders.php' : 'sales.php');
}
}
}
}
require __DIR__ . '/includes/header.php';
?>
<style>
.smart-form-card {
background: #fff;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
border: 1px solid #edf2f9;
margin-bottom: 2rem;
}
.smart-form-header {
padding: 1.5rem 2rem;
border-bottom: 1px solid #edf2f9;
background-color: #fcfdfd;
border-radius: 12px 12px 0 0;
}
.smart-form-body {
padding: 2rem;
}
.section-title {
font-size: 1.1rem;
font-weight: 600;
color: #2c3e50;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-label {
font-weight: 500;
color: #495057;
margin-bottom: 0.4rem;
}
.custom-input {
border: 1px solid #ced4da;
border-radius: 8px;
padding: 0.6rem 1rem;
font-size: 0.95rem;
transition: all 0.2s ease-in-out;
}
.custom-input:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 0.25rem rgba(59, 130, 246, 0.25);
}
.search-wrapper {
position: relative;
max-width: 600px;
margin-bottom: 2rem;
}
.search-icon {
position: absolute;
top: 50%;
left: 1rem;
transform: translateY(-50%);
color: #6c757d;
}
[dir="rtl"] .search-icon {
left: auto;
right: 1rem;
}
.search-input {
padding-left: 2.5rem;
}
[dir="rtl"] .search-input {
padding-left: 1rem;
padding-right: 2.5rem;
}
.item-search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #fff;
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
z-index: 1000;
max-height: 300px;
overflow-y: auto;
display: none;
border: 1px solid #edf2f9;
margin-top: 0.5rem;
}
.item-search-dropdown.show { display: block; }
.search-item-row {
padding: 0.75rem 1rem;
cursor: pointer;
border-bottom: 1px solid #edf2f9;
transition: background 0.15s;
}
.search-item-row:hover { background: #f8f9fa; }
.search-item-row:last-child { border-bottom: none; }
.table-modern {
width: 100%;
border-collapse: separate;
border-spacing: 0;
border: 1px solid #edf2f9;
border-radius: 8px;
overflow: hidden;
}
.table-modern th {
background: #f8f9fa;
padding: 1rem;
font-weight: 600;
color: #495057;
border-bottom: 1px solid #edf2f9;
font-size: 0.9rem;
}
.table-modern td {
padding: 1rem;
vertical-align: middle;
border-bottom: 1px solid #edf2f9;
}
.table-modern tr:last-child td {
border-bottom: none;
}
.qty-control {
width: 80px;
text-align: center;
border: 1px solid #ced4da;
border-radius: 6px;
padding: 0.4rem;
}
.btn-remove {
color: #dc3545;
background: rgba(220, 53, 69, 0.1);
border: none;
width: 32px;
height: 32px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.btn-remove:hover {
background: #dc3545;
color: #fff;
}
.totals-box {
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
border: 1px solid #edf2f9;
}
.totals-row {
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem;
color: #495057;
}
.totals-row.grand-total {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #dee2e6;
font-size: 1.25rem;
font-weight: 700;
color: #212529;
margin-bottom: 0;
}
</style>
<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="<?= 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>
<?php if ($error !== ''): ?>
<div class="alert alert-danger rounded-3 shadow-sm mb-4"><i class="bi bi-exclamation-triangle-fill me-2"></i><?= h($error) ?></div>
<?php endif; ?>
<form method="post" id="smart-sale-form">
<input type="hidden" name="cart_json" id="cart_json" value="[]">
<div class="row">
<div class="col-lg-8">
<!-- Items Section -->
<div class="smart-form-card">
<div class="smart-form-header">
<div class="section-title mb-0">
<i class="bi bi-cart-plus text-primary"></i> <?= h(tr('عناصر الفاتورة', 'Invoice Items')) ?>
</div>
</div>
<div class="smart-form-body">
<!-- Search Bar -->
<div class="search-wrapper">
<i class="bi bi-search search-icon"></i>
<input type="text" id="itemSearchInput" class="form-control custom-input search-input form-control-lg" placeholder="<?= h(tr('ابحث بالاسم أو الباركود...', 'Search by name or barcode...')) ?>" autocomplete="off">
<div id="itemDropdown" class="item-search-dropdown"></div>
</div>
<!-- Table -->
<div class="table-responsive">
<table class="table-modern" id="invoiceTable">
<thead>
<tr>
<th width="45%"><?= h(tr('المنتج', 'Product')) ?></th>
<th width="15%" class="text-center"><?= h(tr('السعر', 'Price')) ?></th>
<th width="15%" class="text-center"><?= h(tr('الكمية', 'Qty')) ?></th>
<th width="20%" class="text-center"><?= h(tr('الإجمالي', 'Total')) ?></th>
<th width="5%"></th>
</tr>
</thead>
<tbody id="invoiceLines">
<tr id="emptyInvoiceRow">
<td colspan="5" class="text-center py-5 text-muted">
<i class="bi bi-inbox fs-1 d-block mb-2 text-light"></i>
<?= h(tr('لم يتم إضافة أي منتجات بعد.', 'No products added yet.')) ?>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Settings Section -->
<div class="smart-form-card">
<div class="smart-form-header">
<div class="section-title mb-0">
<i class="bi bi-receipt text-primary"></i> <?= h(tr('تفاصيل الفاتورة', 'Invoice Details')) ?>
</div>
</div>
<div class="smart-form-body">
<div class="mb-3">
<label class="form-label"><?= h(tr('الفرع', 'Branch')) ?></label>
<select class="form-select custom-input" name="branch_code" <?= count($allowedBranches) === 1 ? 'readonly' : '' ?>>
<?php foreach ($allowedBranches as $branchCode): ?>
<option value="<?= h($branchCode) ?>" <?= $branchCode === $editSale['branch_code'] ? 'selected' : '' ?>><?= h(branch_label($branchCode)) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3 position-relative">
<label class="form-label"><?= h(tr('العميل', 'Customer')) ?></label>
<div class="input-group">
<input type="hidden" id="formCustomerId" name="customer_id" value="<?= h($editSale['customer_id'] ?? '' ) ?>">
<input type="text" id="formCustomer" name="customer_name" class="form-control custom-input" style="border-right-width: 1px;" placeholder="<?= h(tr('بحث (اسم أو هاتف)', 'Search (Name or Phone)')) ?>" autocomplete="off" value="<?= h($editSale['customer_name'] ?? '' ) ?>">
<button class="btn btn-outline-primary px-3" style="border-radius: 0 8px 8px 0;" type="button" onclick="openNewCustomerModal()" title="<?= h(tr('إضافة عميل', 'Add Customer')) ?>">
<i class="bi bi-person-plus-fill"></i>
</button>
</div>
<div id="formCustomerDropdown" class="item-search-dropdown w-100" style="top: 100%;"></div>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('نوع العملية', 'Entry Type')) ?></label>
<select class="form-select custom-input" name="sale_status">
<?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">
<label class="form-label"><?= h(tr('طريقة الدفع', 'Payment Method')) ?></label>
<select class="form-select custom-input" name="payment_method">
<option value="cash" <?= ($paymentMethod ?? $editSale['payment_method']) === 'cash' ? 'selected' : '' ?>><?= h(tr('نقداً', 'Cash')) ?></option>
<option value="card" <?= ($paymentMethod ?? $editSale['payment_method']) === 'card' ? 'selected' : '' ?>><?= h(tr('بطاقة ائتمان', 'Credit Card')) ?></option>
<option value="transfer" <?= ($paymentMethod ?? $editSale['payment_method']) === 'transfer' ? 'selected' : '' ?>><?= h(tr('تحويل بنكي', 'Bank Transfer')) ?></option>
<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">
<div class="row g-2 mt-2" aria-live="polite">
<div class="col-6">
<div class="border rounded-3 px-3 py-2 bg-light-subtle h-100">
<div class="small text-muted mb-1"><?= h(tr('المدفوع بعد الحفظ', 'Paid after save')) ?></div>
<div class="fw-semibold text-success" id="displayPaidAmountLive">0.000 <?= h(tr('ر.ع', 'OMR')) ?></div>
</div>
</div>
<div class="col-6">
<div class="border rounded-3 px-3 py-2 bg-light-subtle h-100">
<div class="small text-muted mb-1"><?= h(tr('المتبقي بعد الحفظ', 'Remaining after save')) ?></div>
<div class="fw-semibold text-primary" id="displayDueAmountLive">0.000 <?= h(tr('ر.ع', 'OMR')) ?></div>
</div>
</div>
</div>
<div class="form-text" id="paymentAmountHint"><?= h(tr('يمكنك تعديل المبلغ المدفوع يدوياً وسيتم إعادة احتساب المتبقي تلقائياً.', 'You can edit the paid amount manually and the remaining balance will be recalculated automatically.')) ?></div>
</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($notesInput) ?></textarea>
</div>
<!-- Summary -->
<div class="totals-box mb-4">
<div class="totals-row">
<span><?= h(tr('المجموع الفرعي', 'Subtotal')) ?></span>
<span id="displaySubtotal" class="fw-medium">0.000</span>
</div>
<div class="totals-row">
<span><?= h(tr('الضريبة (مضافة)', 'VAT (Added)')) ?></span>
<span id="displayVat" class="text-muted">0.000</span>
</div>
<div class="totals-row grand-total">
<span><?= h(tr('الإجمالي', 'Total')) ?></span>
<span id="displayTotal" class="text-primary">0.000 <?= h(tr('ر.ع', 'OMR')) ?></span>
</div>
</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')) ?>
</button>
</div>
</div>
</div>
</div>
</form>
</div>
<!-- New Customer Modal -->
<div class="modal fade" id="newCustomerModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content border-0 shadow-lg" style="border-radius: 16px;">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold"><?= h(tr('إضافة عميل', 'Add Customer')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label text-muted small mb-1"><?= h(tr('الاسم', 'Name')) ?> <span class="text-danger">*</span></label>
<input type="text" id="ncName" class="form-control rounded-3">
</div>
<div class="mb-3">
<label class="form-label text-muted small mb-1"><?= h(tr('رقم الهاتف', 'Phone')) ?></label>
<input type="text" id="ncPhone" class="form-control rounded-3" dir="ltr">
</div>
<div class="d-grid mt-4">
<button class="btn btn-primary rounded-pill fw-semibold shadow-sm" onclick="saveNewCustomer()"><?= h(tr('حفظ العميل', 'Save Customer')) ?></button>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
const catalogData = <?= json_encode($catalog, JSON_UNESCAPED_UNICODE) ?>;
const catalogArray = Object.values(catalogData);
const partialPaymentText = '<?= h(tr('متبقٍ بعد الحفظ:', 'Due after save:')) ?>';
let invoiceItems = {};
let currentInvoiceTotal = 0;
// Prepopulate from editSale
const initialItemsJson = <?= empty($editSale['items_json']) ? '[]' : $editSale['items_json'] ?>;
initialItemsJson.forEach(item => {
invoiceItems[item.sku] = {
sku: item.sku,
name: '<?= current_lang() ?>' === 'ar' ? item.name_ar : item.name_en,
price: parseFloat(item.price),
qty: parseInt(item.qty)
};
});
// renderInvoice();
const searchInput = document.getElementById('itemSearchInput');
const dropdown = document.getElementById('itemDropdown');
const tbody = document.getElementById('invoiceLines');
const emptyRow = document.getElementById('emptyInvoiceRow');
const cartJson = document.getElementById('cart_json');
const currencySuffix = ' <?= h(tr('ر.ع', 'OMR')) ?>';
// Customers Logic
let customersData = <?= json_encode($customers, JSON_UNESCAPED_UNICODE) ?>;
const custInput = document.getElementById('formCustomer');
const custDropdown = document.getElementById('formCustomerDropdown');
custInput.addEventListener('input', function() {
const q = this.value.toLowerCase().trim();
custDropdown.innerHTML = '';
if (q.length < 2) {
custDropdown.classList.remove('show');
return;
}
const matches = customersData.filter(c =>
c.name.toLowerCase().includes(q) ||
(c.phone && c.phone.toLowerCase().includes(q))
).slice(0, 5);
if (matches.length > 0) {
matches.forEach(c => {
const div = document.createElement('div');
div.className = 'search-item-row';
div.innerHTML = `<strong>${c.name}</strong> ${c.phone ? '<small class="text-muted ms-2">'+c.phone+'</small>' : ''}`;
div.onclick = function() {
custInput.value = c.name + (c.phone ? ' - ' + c.phone : '');
custDropdown.classList.remove('show');
};
custDropdown.appendChild(div);
});
custDropdown.classList.add('show');
} else {
custDropdown.classList.remove('show');
}
});
document.addEventListener('click', function(e) {
if (!custInput.contains(e.target) && !custDropdown.contains(e.target)) {
custDropdown.classList.remove('show');
}
});
let newCustomerModalObj = null;
function openNewCustomerModal() {
if (!newCustomerModalObj) {
newCustomerModalObj = new bootstrap.Modal(document.getElementById('newCustomerModal'));
}
document.getElementById('ncName').value = '';
document.getElementById('ncPhone').value = '';
newCustomerModalObj.show();
}
async function saveNewCustomer() {
const name = document.getElementById('ncName').value.trim();
const phone = document.getElementById('ncPhone').value.trim();
if (!name) {
Swal.fire({icon: 'warning', text: '<?= h(tr('الاسم مطلوب', 'Name is required')) ?>'});
return;
}
const formData = new FormData();
formData.append('name', name);
formData.append('phone', phone);
try {
const res = await fetch('api/customers.php', {
method: 'POST',
body: formData
});
const data = await res.json();
if (data.success) {
customersData.push(data.customer);
custInput.value = data.customer.name + (data.customer.phone ? ' - ' + data.customer.phone : '');
document.getElementById('formCustomerId').value = data.customer.id;
newCustomerModalObj.hide();
Swal.fire({
icon: 'success',
text: '<?= h(tr('تم إضافة العميل', 'Customer added')) ?>',
position: 'center',
confirmButtonText: '<?= h(tr('حسناً', 'OK')) ?>',
confirmButtonColor: '#0d6efd'
});
} else {
Swal.fire({icon: 'warning', text: data.error});
}
} catch(err) {
Swal.fire({icon: 'warning', text: 'Error saving customer'});
}
}
// Search logic
searchInput.addEventListener('input', function() {
const q = this.value.toLowerCase().trim();
dropdown.innerHTML = '';
if (q === '') {
dropdown.classList.remove('show');
return;
}
const matches = catalogArray.filter(item => {
const nameAr = (item.name_ar || '').toLowerCase();
const nameEn = (item.name_en || '').toLowerCase();
const sku = (item.sku || '').toLowerCase();
return nameAr.includes(q) || nameEn.includes(q) || sku.includes(q);
}).slice(0, 6);
if (matches.length > 0) {
matches.forEach(item => {
const div = document.createElement('div');
div.className = 'search-item-row d-flex justify-content-between align-items-center';
const name = '<?= current_lang() ?>' === 'ar' ? item.name_ar : item.name_en;
div.innerHTML = `
<div>
<div class="fw-medium text-dark">${name}</div>
<div class="text-muted small">SKU: ${item.sku}</div>
</div>
<div class="fw-semibold text-primary">${parseFloat(item.price).toFixed(3)}</div>
`;
div.onclick = () => {
addItemToInvoice(item.sku);
searchInput.value = '';
dropdown.classList.remove('show');
searchInput.focus();
};
dropdown.appendChild(div);
});
dropdown.classList.add('show');
} else {
dropdown.classList.remove('show');
}
});
// Barcode scanner integration on enter
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
const q = this.value.trim();
if(q === '') return;
const match = catalogArray.find(item => item.sku === q);
if (match) {
addItemToInvoice(match.sku);
searchInput.value = '';
dropdown.classList.remove('show');
}
}
});
document.addEventListener('click', function(e) {
if (!searchInput.contains(e.target) && !dropdown.contains(e.target)) {
dropdown.classList.remove('show');
}
});
function addItemToInvoice(sku) {
if (invoiceItems[sku]) {
invoiceItems[sku].qty += 1;
} else {
const item = catalogData[sku];
invoiceItems[sku] = {
sku: sku,
name: '<?= current_lang() ?>' === 'ar' ? item.name_ar : item.name_en,
price: parseFloat(item.price),
qty: 1
};
}
renderInvoice();
}
function changeQty(sku, newQty) {
const qty = parseInt(newQty);
if (isNaN(qty) || qty < 1) {
delete invoiceItems[sku];
} else {
invoiceItems[sku].qty = qty;
}
renderInvoice();
}
function removeItem(sku) {
delete invoiceItems[sku];
renderInvoice();
}
function renderInvoice() {
const skus = Object.keys(invoiceItems);
if (skus.length === 0) {
tbody.innerHTML = '';
tbody.appendChild(emptyRow);
updateTotals(0, 0);
cartJson.value = '[]';
return;
}
tbody.innerHTML = '';
let totalAmount = 0;
let totalVat = 0;
const cartData = [];
skus.forEach(sku => {
const item = invoiceItems[sku];
const lineTotal = item.qty * item.price;
const vatPercent = parseFloat(catalogData[sku].vat) || 0;
const itemVat = lineTotal * (vatPercent / 100);
totalVat += itemVat;
totalAmount += lineTotal;
cartData.push({ sku: item.sku, qty: item.qty });
const tr = document.createElement('tr');
tr.innerHTML = `
<td>
<div class="fw-medium text-dark">${item.name}</div>
<div class="text-muted small">SKU: ${item.sku}</div>
</td>
<td class="text-center text-muted align-middle">${item.price.toFixed(3)}</td>
<td class="text-center align-middle">
<input type="number" class="qty-control mx-auto fw-medium" min="1" value="${item.qty}" onchange="changeQty('${sku}', this.value)" onkeyup="if(event.key==='Enter') changeQty('${sku}', this.value)">
</td>
<td class="text-center fw-semibold text-dark align-middle">${lineTotal.toFixed(3)}</td>
<td class="text-center align-middle">
<button type="button" class="btn-remove mx-auto" onclick="removeItem('${sku}')" title="<?= h(tr('إزالة', 'Remove')) ?>">
<i class="bi bi-trash"></i>
</button>
</td>
`;
tbody.appendChild(tr);
});
updateTotals(totalAmount, totalVat);
cartJson.value = JSON.stringify(cartData);
}
function formatMoney(value) {
return value.toFixed(3) + currencySuffix;
}
function updatePaymentAmountHint() {
const paymentAmountField = document.getElementById('payment_amount');
const paymentAmountHint = document.getElementById('paymentAmountHint');
const displayPaidAmountLive = document.getElementById('displayPaidAmountLive');
const displayDueAmountLive = document.getElementById('displayDueAmountLive');
if (!paymentAmountField || !paymentAmountHint) {
return;
}
const entered = Math.max(0, parseFloat(paymentAmountField.value || '0') || 0);
const paid = Math.min(entered, currentInvoiceTotal);
const due = Math.max(0, currentInvoiceTotal - paid);
paymentAmountHint.innerText = partialPaymentText + ' ' + formatMoney(due);
if (displayPaidAmountLive) {
displayPaidAmountLive.innerText = formatMoney(paid);
}
if (displayDueAmountLive) {
displayDueAmountLive.innerText = formatMoney(due);
}
}
function syncPaymentAmount(force = false) {
const paymentMethodField = document.querySelector('select[name="payment_method"]');
const paymentAmountField = document.getElementById('payment_amount');
if (!paymentMethodField || !paymentAmountField) {
return;
}
const isManual = paymentAmountField.dataset.manual === '1';
if (force || !isManual) {
const defaultAmount = paymentMethodField.value === 'pay_later' ? 0 : currentInvoiceTotal;
paymentAmountField.value = defaultAmount.toFixed(3);
paymentAmountField.dataset.manual = '0';
}
updatePaymentAmountHint();
}
function updateTotals(total, vat) {
const subtotal = total;
const finalTotal = subtotal + vat;
currentInvoiceTotal = finalTotal;
document.getElementById('displaySubtotal').innerText = subtotal.toFixed(3);
document.getElementById('displayVat').innerText = vat.toFixed(3);
document.getElementById('displayTotal').innerText = finalTotal.toFixed(3) + currencySuffix;
updatePaymentAmountHint();
}
const paymentMethodField = document.querySelector('select[name="payment_method"]');
const paymentAmountField = document.getElementById('payment_amount');
if (paymentMethodField && paymentAmountField) {
paymentAmountField.dataset.manual = paymentAmountField.value !== '' ? '1' : '0';
paymentMethodField.addEventListener('change', () => syncPaymentAmount());
paymentAmountField.addEventListener('input', () => {
paymentAmountField.dataset.manual = '1';
updatePaymentAmountHint();
});
}
renderInvoice();
// Intercept form submission to check if items exist
document.getElementById('smart-sale-form').addEventListener('submit', function(e) {
const customerId = document.getElementById('formCustomerId').value;
const paymentAmount = Math.max(0, parseFloat(document.getElementById('payment_amount').value || '0') || 0);
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.')) ?>'});
} else if (paymentAmount < currentInvoiceTotal && !customerId) {
e.preventDefault();
Swal.fire({icon: 'warning', text: '<?= h(tr('يجب اختيار عميل مسجل عند وجود مبلغ متبقٍ.', 'Select a registered customer when there is a remaining balance.')) ?>'});
}
});
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>