Autosave: 20260419-074108
This commit is contained in:
parent
02afd76d75
commit
4f9eddc419
56
api/settings.php
Normal file
56
api/settings.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/app.php';
|
||||
require_auth();
|
||||
|
||||
$user = current_user();
|
||||
if (!in_array($user['role'], ['owner', 'manager'])) {
|
||||
set_flash('danger', tr('غير مصرح لك.', 'Unauthorized.'));
|
||||
redirect_to('../index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$pdo = db();
|
||||
$keys = [
|
||||
'company_name_ar', 'company_name_en', 'vat_percentage',
|
||||
'company_vat_number', 'company_phone', 'company_email', 'company_address'
|
||||
];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (isset($_POST[$key])) {
|
||||
$stmt->execute([$key, $_POST[$key]]);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle logo upload
|
||||
$uploadDir = __DIR__ . '/../assets/images/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0777, true);
|
||||
}
|
||||
|
||||
if (isset($_FILES['company_logo']) && $_FILES['company_logo']['error'] === UPLOAD_ERR_OK) {
|
||||
$ext = pathinfo($_FILES['company_logo']['name'], PATHINFO_EXTENSION);
|
||||
$filename = 'logo_' . time() . '.' . $ext;
|
||||
if (move_uploaded_file($_FILES['company_logo']['tmp_name'], $uploadDir . $filename)) {
|
||||
$stmt->execute(['company_logo', 'assets/images/' . $filename]);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle favicon upload
|
||||
if (isset($_FILES['company_favicon']) && $_FILES['company_favicon']['error'] === UPLOAD_ERR_OK) {
|
||||
$ext = pathinfo($_FILES['company_favicon']['name'], PATHINFO_EXTENSION);
|
||||
$filename = 'favicon_' . time() . '.' . $ext;
|
||||
if (move_uploaded_file($_FILES['company_favicon']['tmp_name'], $uploadDir . $filename)) {
|
||||
$stmt->execute(['company_favicon', 'assets/images/' . $filename]);
|
||||
}
|
||||
}
|
||||
|
||||
set_flash('success', tr('تم حفظ الإعدادات بنجاح.', 'Settings saved successfully.'));
|
||||
|
||||
// Redirect back to referring page
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? '../index.php';
|
||||
header('Location: ' . $referer);
|
||||
exit;
|
||||
}
|
||||
@ -2,4 +2,4 @@
|
||||
# https://curl.se/docs/http-cookies.html
|
||||
# This file was generated by libcurl! Edit at your own risk.
|
||||
|
||||
127.0.0.1 FALSE / FALSE 0 PHPSESSID b0jh7ohno1tnaa8tkh48odf595
|
||||
127.0.0.1 FALSE / FALSE 0 PHPSESSID 6qft39lctsp4e64kmen99qtqfo
|
||||
|
||||
691
edit_sale.php
Normal file
691
edit_sale.php
Normal file
@ -0,0 +1,691 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$user = require_roles(['owner', 'manager', 'cashier']);
|
||||
|
||||
$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) {
|
||||
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']);
|
||||
$activeNav = 'sales';
|
||||
$error = '';
|
||||
$catalog = catalog();
|
||||
$allowedBranches = $user['role'] === 'owner' ? array_keys(branches()) : [$user['branch_code']];
|
||||
|
||||
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'] ?? ''));
|
||||
$customerName = trim((string) ($_POST['customer_name'] ?? ''));
|
||||
$paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash'));
|
||||
$saleStatus = trim((string) ($_POST['sale_status'] ?? 'completed'));
|
||||
$notes = trim((string) ($_POST['notes'] ?? ''));
|
||||
$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'], true)) {
|
||||
$error = tr('اختر طريقة دفع صحيحة.', 'Choose a valid payment method.');
|
||||
} elseif (!is_array($items) || $items === []) {
|
||||
$error = tr('أضف صنفاً واحداً على الأقل إلى الفاتورة.', 'Add at least one item to the invoice.');
|
||||
} else {
|
||||
$normalized = [];
|
||||
$subtotal = 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;
|
||||
$normalized[] = [
|
||||
'sku' => $sku,
|
||||
'name_ar' => $product['name_ar'],
|
||||
'name_en' => $product['name_en'],
|
||||
'qty' => $qty,
|
||||
'price' => $price,
|
||||
'line_total' => $lineTotal,
|
||||
];
|
||||
$subtotal += $lineTotal;
|
||||
$itemCount += $qty;
|
||||
}
|
||||
|
||||
if ($normalized === []) {
|
||||
$error = tr('الفاتورة غير صالحة بعد التحقق من الأصناف.', 'The invoice is invalid after product validation.');
|
||||
} else {
|
||||
$cashierName = current_lang() === 'ar' ? $user['name_ar'] : $user['name_en'];
|
||||
$stmt = db()->prepare('UPDATE sales_orders SET
|
||||
branch_code = :branch_code,
|
||||
customer_name = :customer_name,
|
||||
payment_method = :payment_method,
|
||||
items_json = :items_json,
|
||||
item_count = :item_count,
|
||||
subtotal = :subtotal,
|
||||
total_amount = :total_amount,
|
||||
status = :status,
|
||||
notes = :notes
|
||||
WHERE id = :id');
|
||||
$stmt->execute([
|
||||
':branch_code' => $branchCode,
|
||||
':customer_name' => $customerName !== '' ? $customerName : null,
|
||||
':payment_method' => $paymentMethod,
|
||||
':items_json' => json_encode($normalized, JSON_UNESCAPED_UNICODE),
|
||||
':item_count' => $itemCount,
|
||||
':subtotal' => $subtotal,
|
||||
':total_amount' => $subtotal,
|
||||
':status' => $saleStatus,
|
||||
':notes' => $notes !== '' ? $notes : null,
|
||||
':id' => $editSaleId,
|
||||
]);
|
||||
|
||||
set_flash('success', tr('تم تحديث الفاتورة بنجاح.', 'Invoice updated successfully.'));
|
||||
redirect_to('sale.php', ['id' => $editSaleId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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="sales.php" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-left"></i> <?= h(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="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">
|
||||
<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>
|
||||
</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" <?= $editSale['payment_method'] === 'cash' ? 'selected' : '' ?>><?= h(tr('نقداً', 'Cash')) ?></option>
|
||||
<option value="card" <?= $editSale['payment_method'] === 'card' ? 'selected' : '' ?>><?= h(tr('بطاقة ائتمان', 'Credit Card')) ?></option>
|
||||
<option value="transfer" <?= $editSale['payment_method'] === 'transfer' ? 'selected' : '' ?>><?= h(tr('تحويل بنكي', 'Bank Transfer')) ?></option>
|
||||
</select>
|
||||
</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>
|
||||
</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('الضريبة (' . get_setting('vat_percentage', 5) . '%)', 'VAT (' . get_setting('vat_percentage', 5) . '%)')) ?></span>
|
||||
<span class="text-success small"><?= h(tr('مشمولة', 'Included')) ?></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);
|
||||
let invoiceItems = {};
|
||||
|
||||
// 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) {
|
||||
alert('<?= 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 : '');
|
||||
newCustomerModalObj.hide();
|
||||
const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 2000 });
|
||||
Toast.fire({ icon: 'success', title: '<?= h(tr('تم إضافة العميل', 'Customer added')) ?>' });
|
||||
} else {
|
||||
alert(data.error);
|
||||
}
|
||||
} catch(err) {
|
||||
alert('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);
|
||||
cartJson.value = '[]';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = '';
|
||||
let totalAmount = 0;
|
||||
const cartData = [];
|
||||
|
||||
skus.forEach(sku => {
|
||||
const item = invoiceItems[sku];
|
||||
const lineTotal = item.qty * item.price;
|
||||
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);
|
||||
cartJson.value = JSON.stringify(cartData);
|
||||
}
|
||||
|
||||
function updateTotals(total) {
|
||||
document.getElementById('displaySubtotal').innerText = total.toFixed(3);
|
||||
document.getElementById('displayTotal').innerText = total.toFixed(3) + currencySuffix;
|
||||
}
|
||||
|
||||
renderInvoice();
|
||||
|
||||
// Intercept form submission to check if items exist
|
||||
document.getElementById('smart-sale-form').addEventListener('submit', function(e) {
|
||||
if (Object.keys(invoiceItems).length === 0) {
|
||||
e.preventDefault();
|
||||
alert('<?= h(tr('الرجاء إضافة أصناف للفاتورة أولاً.', 'Please add items to the invoice first.')) ?>');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require __DIR__ . '/includes/footer.php'; ?>
|
||||
@ -9,9 +9,35 @@ require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
function get_settings(): array
|
||||
{
|
||||
static $settings = null;
|
||||
if ($settings === null) {
|
||||
$pdo = db();
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT setting_key, setting_value FROM settings");
|
||||
$settings = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$settings[$row['setting_key']] = $row['setting_value'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$settings = [];
|
||||
}
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
|
||||
function get_setting(string $key, $default = '')
|
||||
{
|
||||
$settings = get_settings();
|
||||
return $settings[$key] ?? $default;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function app_name(): string
|
||||
{
|
||||
return 'حلوى الريامي | Al Riyami Sweets';
|
||||
return get_setting('company_name_ar', 'حلوى الريامي') . ' | ' . get_setting('company_name_en', 'Al Riyami Sweets');
|
||||
}
|
||||
|
||||
function current_lang(): string
|
||||
@ -195,7 +221,7 @@ function catalog(): array
|
||||
{
|
||||
try {
|
||||
$db = db();
|
||||
$stmt = $db->query("SELECT * FROM items");
|
||||
$stmt = $db->query("SELECT items.*, units.name_ar as u_name_ar, units.name_en as u_name_en FROM items LEFT JOIN units ON items.unit_id = units.id");
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$catalog = [];
|
||||
foreach ($items as $item) {
|
||||
@ -209,8 +235,9 @@ function catalog(): array
|
||||
"category_id" => $item["category_id"],
|
||||
"supplier_id" => $item["supplier_id"],
|
||||
"image_url" => $item["image_url"],
|
||||
"unit_ar" => "قطعة",
|
||||
"unit_en" => "pcs"
|
||||
"unit_id" => $item["unit_id"],
|
||||
"unit_ar" => $item["u_name_ar"] ?? "قطعة",
|
||||
"unit_en" => $item["u_name_en"] ?? "pcs"
|
||||
];
|
||||
}
|
||||
return $catalog;
|
||||
|
||||
@ -9,6 +9,7 @@ $isPublic = !isset($user) || !$user;
|
||||
</div> <!-- /#page-content-wrapper -->
|
||||
</div> <!-- /#wrapper -->
|
||||
<?php endif; ?>
|
||||
<?php require_once __DIR__ . "/footer_settings.php"; ?>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
|
||||
65
includes/footer_settings.php
Normal file
65
includes/footer_settings.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php if (isset($user) && $user && in_array($user['role'], ['owner', 'manager'])): ?>
|
||||
<!-- Settings Modal -->
|
||||
<div class="modal fade" id="settingsModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<form action="api/settings.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><?= h(tr('إعدادات الشركة', 'Company Settings')) ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= h(tr('اسم الشركة (عربي)', 'Company Name (AR)')) ?></label>
|
||||
<input type="text" class="form-control" name="company_name_ar" value="<?= h(get_setting('company_name_ar')) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= h(tr('اسم الشركة (إنجليزي)', 'Company Name (EN)')) ?></label>
|
||||
<input type="text" class="form-control" name="company_name_en" value="<?= h(get_setting('company_name_en')) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= h(tr('النسبة الضريبية %', 'VAT Percentage %')) ?></label>
|
||||
<input type="number" step="0.01" class="form-control" name="vat_percentage" value="<?= h(get_setting('vat_percentage', 5)) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= h(tr('الرقم الضريبي', 'VAT Number')) ?></label>
|
||||
<input type="text" class="form-control" name="company_vat_number" value="<?= h(get_setting('company_vat_number')) ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= h(tr('رقم الهاتف', 'Phone Number')) ?></label>
|
||||
<input type="text" class="form-control" name="company_phone" value="<?= h(get_setting('company_phone')) ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= h(tr('البريد الإلكتروني', 'Email')) ?></label>
|
||||
<input type="email" class="form-control" name="company_email" value="<?= h(get_setting('company_email')) ?>">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label"><?= h(tr('العنوان', 'Address')) ?></label>
|
||||
<textarea class="form-control" name="company_address" rows="2"><?= h(get_setting('company_address')) ?></textarea>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= h(tr('الشعار (Logo)', 'Logo')) ?></label>
|
||||
<input type="file" class="form-control" name="company_logo" accept="image/*">
|
||||
<?php if (get_setting('company_logo')): ?>
|
||||
<div class="mt-2"><img src="<?= h(get_setting('company_logo')) ?>" height="50" style="background: #f8f9fa; padding: 5px; border-radius: 4px;"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= h(tr('الأيقونة (Favicon)', 'Favicon')) ?></label>
|
||||
<input type="file" class="form-control" name="company_favicon" accept="image/x-icon,image/png,image/jpeg">
|
||||
<?php if (get_setting('company_favicon')): ?>
|
||||
<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>
|
||||
</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>
|
||||
<?php endif; ?>
|
||||
@ -32,7 +32,12 @@ $isPublic = !isset($user) || !$user;
|
||||
<?php endif; ?>
|
||||
<meta name="theme-color" content="#343a40" />
|
||||
|
||||
|
||||
<?php if (get_setting('company_favicon')): ?>
|
||||
<link rel="icon" href="<?= h(get_setting('company_favicon')) ?>">
|
||||
<?php endif; ?>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= h($assetVersion) ?>">
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
@ -47,8 +52,13 @@ $isPublic = !isset($user) || !$user;
|
||||
<div class="d-flex" id="wrapper">
|
||||
<!-- Sidebar -->
|
||||
<div class="border-end bg-dark text-white shadow-sm" id="sidebar-wrapper">
|
||||
<div class="sidebar-heading text-center py-4 fs-5 fw-bold text-uppercase border-bottom border-secondary">
|
||||
<i class="bi bi-shop me-2"></i><?= h(tr('حلوى الريامي', 'Al Riyami Sweets')) ?>
|
||||
<div class="sidebar-heading text-center py-4 fs-5 fw-bold text-uppercase border-bottom border-secondary d-flex flex-column align-items-center">
|
||||
<?php if (get_setting('company_logo')): ?>
|
||||
<img src="<?= h(get_setting('company_logo')) ?>" alt="Logo" style="max-height: 50px; margin-bottom: 10px; background: white; padding: 5px; border-radius: 5px;">
|
||||
<?php else: ?>
|
||||
<i class="bi bi-shop me-2 fs-2 mb-2"></i>
|
||||
<?php endif; ?>
|
||||
<span><?= h(current_lang() === 'ar' ? get_setting('company_name_ar', 'حلوى الريامي') : get_setting('company_name_en', 'Al Riyami Sweets')) ?></span>
|
||||
</div>
|
||||
|
||||
<div class="p-3 text-center border-bottom border-secondary">
|
||||
@ -70,13 +80,13 @@ $isPublic = !isset($user) || !$user;
|
||||
<i class="bi bi-journal-text"></i> <?= h(tr('المبيعات', 'Sales')) ?>
|
||||
</a>
|
||||
|
||||
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['stock', 'categories']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapseStock" role="button" aria-expanded="<?= in_array($activeNav, ['stock', 'categories']) ? 'true' : 'false' ?>" aria-controls="collapseStock">
|
||||
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['stock', 'categories', 'units']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapseStock" role="button" aria-expanded="<?= in_array($activeNav, ['stock', 'categories', 'units']) ? 'true' : 'false' ?>" aria-controls="collapseStock">
|
||||
<div class="d-flex justify-content-between align-items-center w-100">
|
||||
<span><i class="bi bi-box-seam"></i> <?= h(tr('المخزون', 'Inventory')) ?></span>
|
||||
<i class="bi bi-chevron-down toggle-icon" style="transition: transform 0.2s;"></i>
|
||||
</div>
|
||||
</a>
|
||||
<div class="collapse <?= in_array($activeNav, ['stock', 'categories']) ? 'show' : '' ?>" id="collapseStock">
|
||||
<div class="collapse <?= in_array($activeNav, ['stock', 'categories', 'units']) ? 'show' : '' ?>" id="collapseStock">
|
||||
<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 === 'stock' ? 'active' : '' ?>" href="<?= h(url_for('stock.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
|
||||
<i class="bi bi-dot"></i> <?= h(tr('قائمة الأصناف', 'Items List')) ?>
|
||||
@ -84,6 +94,9 @@ $isPublic = !isset($user) || !$user;
|
||||
<a class="list-group-item list-group-item-action <?= $activeNav === 'categories' ? 'active' : '' ?>" href="<?= h(url_for('categories.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
|
||||
<i class="bi bi-dot"></i> <?= h(tr('التصنيفات', 'Categories')) ?>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action <?= $activeNav === 'units' ? 'active' : '' ?>" href="<?= h(url_for('units.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
|
||||
<i class="bi bi-dot"></i> <?= h(tr('الوحدات', 'Units')) ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -106,6 +119,12 @@ $isPublic = !isset($user) || !$user;
|
||||
<i class="bi bi-people"></i> <?= h(tr('المستخدمون والأدوار', 'Users & Roles')) ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($user && in_array($user['role'], ['owner', 'manager'])): ?>
|
||||
<a class="list-group-item list-group-item-action" href="#" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
<i class="bi bi-gear"></i> <?= h(tr('إعدادات الشركة', 'Company Settings')) ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /#sidebar-wrapper -->
|
||||
|
||||
@ -360,7 +360,7 @@ require __DIR__ . '/header.php';
|
||||
<span id="displaySubtotal" class="fw-medium">0.000</span>
|
||||
</div>
|
||||
<div class="totals-row">
|
||||
<span><?= h(tr('الضريبة (15%)', 'VAT (15%)')) ?></span>
|
||||
<span><?= h(tr('الضريبة (' . get_setting('vat_percentage', 5) . '%)', 'VAT (' . get_setting('vat_percentage', 5) . '%)')) ?></span>
|
||||
<span class="text-success small"><?= h(tr('مشمولة', 'Included')) ?></span>
|
||||
</div>
|
||||
<div class="totals-row grand-total">
|
||||
|
||||
251
login.php
251
login.php
@ -6,7 +6,18 @@ if (current_user()) {
|
||||
}
|
||||
|
||||
$error = '';
|
||||
$flash = pull_flash();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['action']) && $_POST['action'] === 'reset_password') {
|
||||
$reset_username = trim((string) ($_POST['reset_username'] ?? ''));
|
||||
if ($reset_username !== '') {
|
||||
// Mock sending reset link
|
||||
set_flash('success', tr('تم إرسال رابط إعادة تعيين كلمة المرور إلى بريدك الإلكتروني (تجريبي).', 'Password reset link has been sent to your email (Demo).'));
|
||||
}
|
||||
redirect_to('login.php');
|
||||
}
|
||||
|
||||
$username = trim((string) ($_POST['username'] ?? ''));
|
||||
$password = trim((string) ($_POST['password'] ?? ''));
|
||||
|
||||
@ -37,74 +48,202 @@ $accounts = demo_users();
|
||||
<meta property="og:description" content="<?= h($projectDescription) ?>" />
|
||||
<meta property="twitter:description" content="<?= h($projectDescription) ?>" />
|
||||
<?php else: ?>
|
||||
<meta name="description" content="<?= h(tr('تسجيل الدخول إلى مساحة مبيعات حلوى الريامي متعددة الفروع.', 'Sign in to the multi-branch Al Riyami Sweets sales workspace.')) ?>" />
|
||||
<meta name="description" content="<?= h(tr('تسجيل الدخول إلى مساحة المبيعات.', 'Sign in to the sales workspace.')) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<meta property="og:image" content="<?= h($projectImageUrl) ?>" />
|
||||
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if (get_setting('company_favicon')): ?>
|
||||
<link rel="icon" href="<?= h(get_setting('company_favicon')) ?>">
|
||||
<?php endif; ?>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= h($assetVersion) ?>">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
.auth-card {
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.08);
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
}
|
||||
.auth-sidebar {
|
||||
background: linear-gradient(135deg, #0d6efd 0%, #0a58ca 100%);
|
||||
color: white;
|
||||
padding: 3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.auth-form-container {
|
||||
padding: 4rem 3rem;
|
||||
}
|
||||
.company-logo {
|
||||
max-height: 90px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 1.5rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
.demo-account {
|
||||
border-radius: 12px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.demo-account:hover {
|
||||
background-color: #f8f9fa;
|
||||
transform: translateY(-2px);
|
||||
border-color: #dee2e6 !important;
|
||||
}
|
||||
.stat-chip {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="auth-body">
|
||||
<main class="auth-shell container-fluid">
|
||||
<div class="row g-4 align-items-stretch justify-content-center">
|
||||
<div class="col-lg-5">
|
||||
<section class="auth-panel h-100">
|
||||
<div class="eyebrow mb-3"><?= h(tr('MVP جاهز للاستخدام', 'MVP ready to use')) ?></div>
|
||||
<h1 class="auth-title"><?= h(tr('حلوى الريامي', 'Al Riyami Sweets')) ?></h1>
|
||||
<p class="auth-subtitle"><?= h(tr('تسجيل دخول ثنائي اللغة مع أدوار منفصلة للمالك ومدير الفرع والكاشير.', 'Bilingual role-based access for owner, branch manager, and cashier.')) ?></p>
|
||||
<div class="mini-grid mt-4">
|
||||
<div class="stat-chip"><strong>3</strong><span><?= h(tr('أدوار', 'roles')) ?></span></div>
|
||||
<div class="stat-chip"><strong>3</strong><span><?= h(tr('فروع', 'branches')) ?></span></div>
|
||||
<div class="stat-chip"><strong>2</strong><span><?= h(tr('لغات', 'languages')) ?></span></div>
|
||||
</div>
|
||||
<div class="alert alert-light border mt-4 mb-0">
|
||||
<div class="fw-semibold mb-1"><?= h(tr('أول قيمة عملية', 'First practical value')) ?></div>
|
||||
<div class="small text-muted"><?= h(tr('ابدأ ببيع POS سريع، ثم راجع المبيعات والمخزون والتقارير من نفس الواجهة.', 'Start with a fast POS sale, then review sales, stock, and reports from one workspace.')) ?></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<section class="auth-panel h-100">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="h4 mb-0"><?= h(tr('تسجيل الدخول', 'Sign in')) ?></h2>
|
||||
<div class="language-switcher">
|
||||
<a class="btn btn-sm <?= current_lang() === 'ar' ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('login.php', ['lang' => 'ar'])) ?>">AR</a>
|
||||
<a class="btn btn-sm <?= current_lang() === 'en' ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('login.php', ['lang' => 'en'])) ?>">EN</a>
|
||||
<body>
|
||||
<main class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-xl-10">
|
||||
<div class="card auth-card">
|
||||
<div class="row g-0 align-items-stretch">
|
||||
|
||||
<div class="col-lg-5 auth-sidebar d-none d-lg-flex">
|
||||
<div>
|
||||
<div class="eyebrow text-white-50 mb-3"><?= h(tr('مرحباً بك مجدداً', 'Welcome Back')) ?></div>
|
||||
<h2 class="display-6 fw-bold mb-4"><?= h(current_lang() === 'ar' ? get_setting('company_name_ar', 'نظام إدارة') : get_setting('company_name_en', 'Management System')) ?></h2>
|
||||
<p class="lead opacity-75"><?= h(tr('نظام متكامل لتسجيل المبيعات، وإدارة المخزون، والتقارير في واجهة واحدة.', 'Integrated system for logging sales, managing inventory, and reports in one interface.')) ?></p>
|
||||
|
||||
<div class="mini-grid mt-5 gap-3 d-flex flex-wrap">
|
||||
<div class="stat-chip text-white px-3 py-2 rounded"><strong>3</strong> <span><?= h(tr('أدوار', 'Roles')) ?></span></div>
|
||||
<div class="stat-chip text-white px-3 py-2 rounded"><strong>3</strong> <span><?= h(tr('فروع', 'Branches')) ?></span></div>
|
||||
<div class="stat-chip text-white px-3 py-2 rounded"><strong>2</strong> <span><?= h(tr('لغات', 'Languages')) ?></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 pt-5 border-top border-light border-opacity-25 text-white-50 small">
|
||||
© <?= date('Y') ?> <?= h($projectName) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-7 auth-form-container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="language-switcher">
|
||||
<a class="btn btn-sm <?= current_lang() === 'ar' ? 'btn-primary' : 'btn-light text-dark' ?> rounded-pill px-3" href="<?= h(url_for('login.php', ['lang' => 'ar'])) ?>">AR</a>
|
||||
<a class="btn btn-sm <?= current_lang() === 'en' ? 'btn-primary' : 'btn-light text-dark' ?> rounded-pill px-3" href="<?= h(url_for('login.php', ['lang' => 'en'])) ?>">EN</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-5">
|
||||
<?php if (get_setting('company_logo')): ?>
|
||||
<img src="<?= h(get_setting('company_logo')) ?>" alt="Logo" class="company-logo">
|
||||
<?php else: ?>
|
||||
<div class="company-logo mx-auto bg-primary bg-opacity-10 text-primary d-flex align-items-center justify-content-center" style="width: 90px; height: 90px; font-size: 2.5rem; font-weight: bold;">
|
||||
<?= h(mb_substr(current_lang() === 'ar' ? get_setting('company_name_ar', 'نظام') : get_setting('company_name_en', 'System'), 0, 1)) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<h3 class="fw-bold mb-1"><?= h(tr('تسجيل الدخول', 'Sign in to your account')) ?></h3>
|
||||
<p class="text-muted"><?= h(tr('أدخل بيانات الاعتماد الخاصة بك للوصول', 'Enter your credentials to access your account')) ?></p>
|
||||
</div>
|
||||
|
||||
<?php if ($flash): ?>
|
||||
<div class="alert alert-<?= h($flash['type'] === 'error' ? 'danger' : $flash['type']) ?> alert-dismissible fade show rounded-3" role="alert">
|
||||
<?= h($flash['message']) ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show rounded-3" role="alert">
|
||||
<?= h($error) ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" class="mb-5">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" for="username"><?= h(tr('اسم المستخدم', 'Username')) ?></label>
|
||||
<input id="username" name="username" class="form-control form-control-lg rounded-3 bg-light border-0" autocomplete="username" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<label class="form-label fw-semibold mb-0" for="password"><?= h(tr('كلمة المرور', 'Password')) ?></label>
|
||||
<a href="#" class="text-decoration-none small text-primary fw-medium" data-bs-toggle="modal" data-bs-target="#resetPasswordModal"><?= h(tr('نسيت كلمة المرور؟', 'Forgot password?')) ?></a>
|
||||
</div>
|
||||
<input id="password" name="password" type="password" class="form-control form-control-lg rounded-3 bg-light border-0" autocomplete="current-password" required>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-lg w-100 rounded-3 shadow-sm fw-semibold" type="submit"><?= h(tr('دخول', 'Sign in')) ?></button>
|
||||
</form>
|
||||
|
||||
<div class="position-relative mb-4">
|
||||
<hr class="text-muted opacity-25">
|
||||
<span class="position-absolute top-50 start-50 translate-middle bg-white px-3 small text-muted fw-medium"><?= h(tr('حسابات تجريبية سريعة', 'Quick demo access')) ?></span>
|
||||
</div>
|
||||
|
||||
<div class="row g-2">
|
||||
<?php foreach ($accounts as $account): ?>
|
||||
<div class="col-12 col-md-4">
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary border-1 w-100 p-3 text-start demo-account h-100"
|
||||
data-username="<?= h($account['username']) ?>"
|
||||
data-password="<?= h($account['password']) ?>">
|
||||
<div class="fw-bold mb-1 text-dark text-truncate"><?= h(current_lang() === 'ar' ? $account['name_ar'] : $account['name_en']) ?></div>
|
||||
<div class="small text-muted" style="font-size: 0.75rem;"><?= h(role_label($account['role'])) ?></div>
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="alert alert-warning"><?= h($error) ?></div>
|
||||
<?php endif; ?>
|
||||
<form method="post" class="d-grid gap-3">
|
||||
<div>
|
||||
<label class="form-label" for="username"><?= h(tr('اسم المستخدم', 'Username')) ?></label>
|
||||
<input id="username" name="username" class="form-control form-control-lg" autocomplete="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" for="password"><?= h(tr('كلمة المرور', 'Password')) ?></label>
|
||||
<input id="password" name="password" type="password" class="form-control form-control-lg" autocomplete="current-password" required>
|
||||
</div>
|
||||
<button class="btn btn-dark btn-lg" type="submit"><?= h(tr('دخول إلى التطبيق', 'Enter app')) ?></button>
|
||||
</form>
|
||||
<div class="divider-label"><?= h(tr('حسابات تجريبية', 'Demo accounts')) ?></div>
|
||||
<div class="d-grid gap-2">
|
||||
<?php foreach ($accounts as $account): ?>
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary text-start demo-account"
|
||||
data-username="<?= h($account['username']) ?>"
|
||||
data-password="<?= h($account['password']) ?>">
|
||||
<div class="fw-semibold"><?= h(current_lang() === 'ar' ? $account['name_ar'] : $account['name_en']) ?></div>
|
||||
<div class="small text-muted"><?= h(role_label($account['role'])) ?> · <?= h(branch_label($account['branch_code'])) ?></div>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Reset Password Modal -->
|
||||
<div class="modal fade" id="resetPasswordModal" tabindex="-1" aria-labelledby="resetPasswordModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header border-bottom-0 pb-0 pt-4 px-4">
|
||||
<h5 class="modal-title fw-bold" id="resetPasswordModalLabel"><?= h(tr('استعادة كلمة المرور', 'Reset Password')) ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="reset_password">
|
||||
<div class="modal-body px-4 py-4">
|
||||
<p class="text-muted mb-4"><?= h(tr('أدخل اسم المستخدم أو البريد الإلكتروني وسنقوم بإرسال رابط لإعادة تعيين كلمة المرور الخاصة بك.', 'Enter your username or email and we will send you a link to reset your password.')) ?></p>
|
||||
<div class="mb-3">
|
||||
<label for="reset_username" class="form-label fw-semibold"><?= h(tr('البريد الإلكتروني / اسم المستخدم', 'Email / Username')) ?></label>
|
||||
<input type="text" class="form-control form-control-lg bg-light border-0 rounded-3" id="reset_username" name="reset_username" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-top-0 pt-0 pb-4 px-4">
|
||||
<button type="button" class="btn btn-light rounded-3 px-4" data-bs-dismiss="modal"><?= h(tr('إلغاء', 'Cancel')) ?></button>
|
||||
<button type="submit" class="btn btn-primary rounded-3 px-4 fw-semibold"><?= h(tr('إرسال الرابط', 'Send Link')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
<script src="assets/js/main.js?v=<?= h($assetVersion) ?>"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.demo-account').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.getElementById('username').value = btn.dataset.username;
|
||||
document.getElementById('password').value = btn.dataset.password;
|
||||
btn.closest('form')?.submit() || document.querySelector('form').submit();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -17,9 +17,9 @@ if (!$sale) {
|
||||
}
|
||||
|
||||
// Receipt Configuration
|
||||
$storeName = tr('متجر فلات لوجيك', 'Flatlogic Store');
|
||||
$storeAddress = tr('شارع الملك فهد، الرياض، السعودية', 'King Fahd Road, Riyadh, KSA');
|
||||
$vatNo = '300123456789012';
|
||||
$storeName = current_lang() === 'ar' ? get_setting('company_name_ar', 'حلوى الريامي') : get_setting('company_name_en', 'Al Riyami Sweets');
|
||||
$storeAddress = get_setting('company_address', '');
|
||||
$vatNo = get_setting('company_vat_number', '300123456789012');
|
||||
$registerNo = 'REG-01';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
@ -173,10 +173,14 @@ $registerNo = 'REG-01';
|
||||
<!-- Logo -->
|
||||
<div class="text-center logo-area">
|
||||
<!-- SVG Placeholder Logo -->
|
||||
<svg width="60" height="60" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100" height="100" rx="20" fill="#000"/>
|
||||
<path d="M50 20 L80 80 L20 80 Z" fill="#fff"/>
|
||||
</svg>
|
||||
<?php if (get_setting('company_logo')): ?>
|
||||
<img src="<?= h(get_setting('company_logo')) ?>" alt="Logo" style="max-height: 80px; max-width: 150px;">
|
||||
<?php else: ?>
|
||||
<svg width="60" height="60" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100" height="100" rx="20" fill="#000"/>
|
||||
<path d="M50 20 L80 80 L20 80 Z" fill="#fff"/>
|
||||
</svg>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Store Info -->
|
||||
@ -184,7 +188,7 @@ $registerNo = 'REG-01';
|
||||
<div class="font-bold" style="font-size: 16px;"><?= h($storeName) ?></div>
|
||||
<div><?= h($storeAddress) ?></div>
|
||||
<div>VAT: <?= h($vatNo) ?></div>
|
||||
<div><?= h(tr('هاتف', 'Tel')) ?>: 920000000</div>
|
||||
<div><?= h(tr('هاتف', 'Tel')) ?>: <?= h(get_setting('company_phone', '')) ?></div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
@ -244,7 +248,7 @@ $registerNo = 'REG-01';
|
||||
<span><?= number_format((float)$sale['subtotal'], 3) ?></span>
|
||||
</div>
|
||||
<div class="totals-row">
|
||||
<span><?= h(tr('ضريبة القيمة المضافة (15%)', 'VAT (15%)')) ?></span>
|
||||
<span><?= h(tr('ضريبة القيمة المضافة (' . get_setting('vat_percentage', 5) . '%)', 'VAT (' . get_setting('vat_percentage', 5) . '%)')) ?></span>
|
||||
<span><?= h(tr('شامل', 'Inclusive')) ?></span>
|
||||
</div>
|
||||
<div class="totals-row grand-total">
|
||||
|
||||
21
sale.php
21
sale.php
@ -15,11 +15,11 @@ if ($id > 0) {
|
||||
}
|
||||
|
||||
// Company Info for Invoice
|
||||
$companyName = tr('متجر فلات لوجيك', 'Flatlogic Store');
|
||||
$companyAddress = tr('شارع الملك فهد، الرياض، المملكة العربية السعودية', 'King Fahd Road, Riyadh, KSA');
|
||||
$companyVat = '300123456789012';
|
||||
$companyName = current_lang() === 'ar' ? get_setting('company_name_ar', 'حلوى الريامي') : get_setting('company_name_en', 'Al Riyami Sweets');
|
||||
$companyAddress = get_setting('company_address', '');
|
||||
$companyVat = get_setting('company_vat_number', '300123456789012');
|
||||
$companyEmail = 'info@flatlogic.com';
|
||||
$companyPhone = '920000000';
|
||||
$companyPhone = '<?= h(get_setting('company_phone', '')) ?>';
|
||||
|
||||
require __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
@ -319,9 +319,14 @@ require __DIR__ . '/includes/header.php';
|
||||
<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>
|
||||
<button onclick="window.print()" class="btn btn-dark btn-sm px-4">
|
||||
<i class="bi bi-printer me-2"></i><?= h(tr('طباعة الفاتورة', 'Print Invoice')) ?>
|
||||
</button>
|
||||
<div>
|
||||
<a href="<?= h(url_for('edit_sale.php', ['id' => $sale['id']])) ?>" class="btn btn-outline-primary btn-sm me-2">
|
||||
<i class="bi bi-pencil me-1"></i><?= h(tr('تعديل الفاتورة', 'Edit Invoice')) ?>
|
||||
</a>
|
||||
<button onclick="window.print()" class="btn btn-dark btn-sm px-4">
|
||||
<i class="bi bi-printer me-2"></i><?= h(tr('طباعة الفاتورة', 'Print Invoice')) ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formal A4 Invoice -->
|
||||
@ -419,7 +424,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<td class="total-amount"><?= h(number_format((float) $sale['subtotal'], 3)) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="total-label"><?= h(tr('ضريبة القيمة المضافة (15%)', 'VAT (15%)')) ?></td>
|
||||
<td class="total-label"><?= h(tr('ضريبة القيمة المضافة (' . get_setting('vat_percentage', 5) . '%)', 'VAT (' . get_setting('vat_percentage', 5) . '%)')) ?></td>
|
||||
<td class="total-amount"><?= h(tr('شامل', 'Inclusive')) ?></td>
|
||||
</tr>
|
||||
<tr class="grand-total-row">
|
||||
|
||||
@ -165,9 +165,9 @@ require __DIR__ . '/includes/header.php';
|
||||
<a class="btn btn-sm btn-light text-primary border me-1" href="<?= h(url_for('sale.php', ['id' => $sale['id']])) ?>" title="<?= h(tr('تفاصيل', 'Detail')) ?>">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-secondary rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="mockEdit()" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<a class="btn btn-sm btn-outline-secondary rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0; line-height: 32px; text-align: center;" href="<?= h(url_for('edit_sale.php', ['id' => $sale['id']])) ?>" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
|
||||
29
stock.php
29
stock.php
@ -16,9 +16,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
$name = $_POST['name'] ?? '';
|
||||
$price = (float)($_POST['price'] ?? 0);
|
||||
$base_stock = (int)($_POST['base_stock'] ?? 0);
|
||||
$vat = (float)($_POST['vat'] ?? 5);
|
||||
$vat = (float)($_POST['vat'] ?? get_setting('vat_percentage', 5));
|
||||
$category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null;
|
||||
$supplier_id = !empty($_POST['supplier_id']) ? (int)$_POST['supplier_id'] : null;
|
||||
$unit_id = !empty($_POST['unit_id']) ? (int)$_POST['unit_id'] : null;
|
||||
|
||||
if (!$sku || !$name) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing SKU or Name']);
|
||||
@ -49,8 +50,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "UPDATE items SET sku=?, name=?, price=?, base_stock=?, vat=?, category_id=?, supplier_id=? " . ($image_url ? ", image_url=?" : "") . " WHERE sku=?";
|
||||
$params = [$sku, $name, $price, $base_stock, $vat, $category_id, $supplier_id];
|
||||
$sql = "UPDATE items SET sku=?, name=?, price=?, base_stock=?, vat=?, category_id=?, supplier_id=?, unit_id=? " . ($image_url ? ", image_url=?" : "") . " WHERE sku=?";
|
||||
$params = [$sku, $name, $price, $base_stock, $vat, $category_id, $supplier_id, $unit_id];
|
||||
if ($image_url) {
|
||||
$params[] = $image_url;
|
||||
}
|
||||
@ -62,8 +63,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'SKU already exists']);
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->prepare("INSERT INTO items (sku, name, price, base_stock, vat, category_id, supplier_id, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$sku, $name, $price, $base_stock, $vat, $category_id, $supplier_id, $image_url]);
|
||||
$stmt = $pdo->prepare("INSERT INTO items (sku, name, price, base_stock, vat, category_id, supplier_id, unit_id, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$sku, $name, $price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $image_url]);
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
@ -105,6 +106,7 @@ try {
|
||||
$pdo = db();
|
||||
$categories = $pdo->query('SELECT id, name_ar, name_en FROM categories ORDER BY name_ar ASC')->fetchAll();
|
||||
$suppliers = $pdo->query('SELECT id, name FROM suppliers ORDER BY name ASC')->fetchAll();
|
||||
$units = $pdo->query('SELECT id, name_ar, name_en FROM units ORDER BY name_ar ASC')->fetchAll();
|
||||
} catch (Throwable $e) {
|
||||
// Ignore if not present
|
||||
}
|
||||
@ -235,7 +237,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end pe-3">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="openItemModal('<?= h($row['sku']) ?>', '<?= h(addslashes($row['name'])) ?>', '<?= h($row['price']) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? 5) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>')" data-bs-toggle="tooltip" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="openItemModal('<?= h($row['sku']) ?>', '<?= h(addslashes($row['name'])) ?>', '<?= h($row['price']) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? get_setting('vat_percentage', 5)) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['unit_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>')" data-bs-toggle="tooltip" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="deleteItem('<?= h(addslashes($row['sku'])) ?>')" data-bs-toggle="tooltip" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
@ -302,9 +304,18 @@ require __DIR__ . '/includes/header.php';
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label class="form-label"><?= h(tr('الضريبة (VAT %)', 'VAT %')) ?></label>
|
||||
<input type="number" step="0.001" class="form-control" id="item_vat" value="5" required>
|
||||
<input type="number" step="0.001" class="form-control" id="item_vat" value="<?= h(get_setting('vat_percentage', 5)) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('الوحدة', 'Unit')) ?></label>
|
||||
<select class="form-select" id="item_unit">
|
||||
<option value=""><?= h(tr('-- اختر الوحدة --', '-- Select Unit --')) ?></option>
|
||||
<?php foreach($units as $unit): ?>
|
||||
<option value="<?= h($unit['id']) ?>"><?= h(current_lang() === 'ar' ? $unit['name_ar'] : $unit['name_en']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('التصنيف', 'Category')) ?></label>
|
||||
<select class="form-select" id="item_category">
|
||||
@ -393,7 +404,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
});
|
||||
|
||||
function openItemModal(sku = '', name = '', price = '', base_stock = '', vat = '5', category_id = '', supplier_id = '', image_url = '') {
|
||||
function openItemModal(sku = '', name = '', price = '', base_stock = '', vat = '<?= h(get_setting('vat_percentage', 5)) ?>', category_id = '', supplier_id = '', unit_id = '', image_url = '') {
|
||||
document.getElementById('item_original_sku').value = sku;
|
||||
document.getElementById('item_existing_image_url').value = image_url;
|
||||
document.getElementById('item_sku').value = sku;
|
||||
@ -403,6 +414,7 @@ function openItemModal(sku = '', name = '', price = '', base_stock = '', vat = '
|
||||
document.getElementById('item_vat').value = vat;
|
||||
document.getElementById('item_category').value = category_id;
|
||||
document.getElementById('item_supplier').value = supplier_id;
|
||||
document.getElementById('item_unit').value = unit_id;
|
||||
|
||||
// Remove old image preview if any
|
||||
const oldPreview = document.getElementById('image_preview');
|
||||
@ -438,6 +450,7 @@ async function handleItemSubmit(e) {
|
||||
formData.append('vat', document.getElementById('item_vat').value);
|
||||
formData.append('category_id', document.getElementById('item_category').value);
|
||||
formData.append('supplier_id', document.getElementById('item_supplier').value);
|
||||
formData.append('unit_id', document.getElementById('item_unit').value);
|
||||
formData.append('existing_image_url', document.getElementById('item_existing_image_url').value);
|
||||
|
||||
const picInput = document.getElementById('item_picture');
|
||||
|
||||
215
units.php
Normal file
215
units.php
Normal file
@ -0,0 +1,215 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$user = require_auth();
|
||||
$pageTitle = tr('الوحدات', 'Units');
|
||||
$activeNav = 'units';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Handle Form Submission
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'create') {
|
||||
$stmt = $pdo->prepare('INSERT INTO units (name_ar, name_en) VALUES (?, ?)');
|
||||
$stmt->execute([$_POST['name_ar'], $_POST['name_en']]);
|
||||
set_flash('success', tr('تمت إضافة الوحدة بنجاح', 'Unit added successfully'));
|
||||
redirect_to('units.php');
|
||||
} elseif ($action === 'edit') {
|
||||
$stmt = $pdo->prepare('UPDATE units SET name_ar = ?, name_en = ? WHERE id = ?');
|
||||
$stmt->execute([$_POST['name_ar'], $_POST['name_en'], $_POST['id']]);
|
||||
set_flash('success', tr('تم التحديث بنجاح', 'Updated successfully'));
|
||||
redirect_to('units.php');
|
||||
} elseif ($action === 'delete') {
|
||||
$stmt = $pdo->prepare('DELETE FROM units WHERE id = ?');
|
||||
$stmt->execute([$_POST['id']]);
|
||||
set_flash('success', tr('تم الحذف بنجاح', 'Deleted successfully'));
|
||||
redirect_to('units.php');
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination & Search
|
||||
$page = max(1, (int)($_GET['p'] ?? 1));
|
||||
$limit = 10;
|
||||
$offset = ($page - 1) * $limit;
|
||||
$search = $_GET['q'] ?? '';
|
||||
|
||||
$where = '1=1';
|
||||
$params = [];
|
||||
if ($search) {
|
||||
$where .= ' AND (name_ar LIKE ? OR name_en LIKE ?)';
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
}
|
||||
|
||||
$totalStmt = $pdo->prepare("SELECT COUNT(*) FROM units WHERE $where");
|
||||
$totalStmt->execute($params);
|
||||
$total = $totalStmt->fetchColumn();
|
||||
$totalPages = ceil($total / $limit);
|
||||
|
||||
$queryStmt = $pdo->prepare("SELECT * FROM units WHERE $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
|
||||
$queryStmt->execute($params);
|
||||
$items = $queryStmt->fetchAll();
|
||||
|
||||
require __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="surface-card mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h3 class="h5 mb-2"><i class="bi bi-rulers me-2"></i><?= h($pageTitle) ?></h3>
|
||||
<p class="text-muted mb-0"><?= h(tr('إدارة وحدات القياس للأصناف', 'Manage measurement units for items')) ?></p>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||
<i class="bi bi-plus-lg"></i> <?= h(tr('إضافة وحدة', 'Add Unit')) ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form class="d-flex mb-3" method="GET" action="units.php">
|
||||
<div class="input-group" style="max-width: 400px;">
|
||||
<input type="text" name="q" class="form-control" placeholder="<?= h(tr('بحث...', 'Search...')) ?>" value="<?= h($search) ?>">
|
||||
<button class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="surface-card">
|
||||
<div class="table-responsive shadow-sm" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.05);">
|
||||
<table class="table table-hover align-middle mb-0 text-center" style="background-color: #fff;">
|
||||
<thead style="background: linear-gradient(90deg, #0d6efd, #0dcaf0);">
|
||||
<tr>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent">ID</th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الاسم (عربي)', 'Name (AR)')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الاسم (إنجليزي)', 'Name (EN)')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="border-top-0">
|
||||
<?php if(empty($items)): ?>
|
||||
<tr><td colspan="4" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<tr>
|
||||
<td><?= h($item['id']) ?></td>
|
||||
<td><?= h($item['name_ar']) ?></td>
|
||||
<td><?= h($item['name_en']) ?></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="editItem(<?= htmlspecialchars(json_encode($item)) ?>)" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="deleteItem(<?= $item['id'] ?>)" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<nav class="mt-4">
|
||||
<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('units.php', ['p' => $i, 'q' => $search])) ?>"><?= $i ?></a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<!-- Add Modal -->
|
||||
<div class="modal fade" id="addModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form method="POST" action="units.php">
|
||||
<input type="hidden" name="action" value="create">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><?= h(tr('إضافة وحدة', 'Add Unit')) ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('الاسم (عربي)', 'Name (AR)')) ?></label>
|
||||
<input type="text" name="name_ar" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('الاسم (إنجليزي)', 'Name (EN)')) ?></label>
|
||||
<input type="text" name="name_en" class="form-control" required>
|
||||
</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')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form method="POST" action="units.php">
|
||||
<input type="hidden" name="action" value="edit">
|
||||
<input type="hidden" name="id" id="edit_id">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><?= h(tr('تعديل', 'Edit')) ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('الاسم (عربي)', 'Name (AR)')) ?></label>
|
||||
<input type="text" name="name_ar" id="edit_name_ar" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('الاسم (إنجليزي)', 'Name (EN)')) ?></label>
|
||||
<input type="text" name="name_en" id="edit_name_en" class="form-control" required>
|
||||
</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')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Form -->
|
||||
<form id="deleteForm" method="POST" action="units.php" style="display:none;">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="id" id="delete_id">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function editItem(item) {
|
||||
document.getElementById('edit_id').value = item.id;
|
||||
document.getElementById('edit_name_ar').value = item.name_ar;
|
||||
document.getElementById('edit_name_en').value = item.name_en;
|
||||
new bootstrap.Modal(document.getElementById('editModal')).show();
|
||||
}
|
||||
|
||||
function deleteItem(id) {
|
||||
Swal.fire({
|
||||
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
|
||||
text: '<?= h(tr('لن تتمكن من التراجع عن هذا!', "You won't be able to revert this!")) ?>',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#dc3545',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<?= h(tr('نعم، احذف', 'Yes, delete it!')) ?>',
|
||||
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('delete_id').value = id;
|
||||
document.getElementById('deleteForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require __DIR__ . '/includes/footer.php'; ?>
|
||||
Loading…
x
Reference in New Issue
Block a user