diff --git a/api/settings.php b/api/settings.php
new file mode 100644
index 0000000..3fff4c0
--- /dev/null
+++ b/api/settings.php
@@ -0,0 +1,56 @@
+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;
+}
diff --git a/cookies.txt b/cookies.txt
index 40643ba..24dbe0b 100644
--- a/cookies.txt
+++ b/cookies.txt
@@ -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
diff --git a/edit_sale.php b/edit_sale.php
new file mode 100644
index 0000000..4e6c672
--- /dev/null
+++ b/edit_sale.php
@@ -0,0 +1,691 @@
+ 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';
+?>
+
+
+
+
+
+
+
+
= h($error) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ = h(tr('الاسم', 'Name')) ?> *
+
+
+
+ = h(tr('رقم الهاتف', 'Phone')) ?>
+
+
+
+ = h(tr('حفظ العميل', 'Save Customer')) ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/app.php b/includes/app.php
index 58d9da0..1bc51e7 100644
--- a/includes/app.php
+++ b/includes/app.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;
diff --git a/includes/footer.php b/includes/footer.php
index abaaa98..69a9e57 100644
--- a/includes/footer.php
+++ b/includes/footer.php
@@ -9,6 +9,7 @@ $isPublic = !isset($user) || !$user;
+
@@ -47,8 +52,13 @@ $isPublic = !isset($user) || !$user;
-
- = h(tr('الضريبة (15%)', 'VAT (15%)')) ?>
+ = h(tr('الضريبة (' . get_setting('vat_percentage', 5) . '%)', 'VAT (' . get_setting('vat_percentage', 5) . '%)')) ?>
= h(tr('مشمولة', 'Included')) ?>
diff --git a/login.php b/login.php
index e673e2b..52d571e 100644
--- a/login.php
+++ b/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();
-
+
+
+
+
+
-
-
-
-
-
- = h(tr('MVP جاهز للاستخدام', 'MVP ready to use')) ?>
- = h(tr('حلوى الريامي', 'Al Riyami Sweets')) ?>
- = h(tr('تسجيل دخول ثنائي اللغة مع أدوار منفصلة للمالك ومدير الفرع والكاشير.', 'Bilingual role-based access for owner, branch manager, and cashier.')) ?>
-
-
3 = h(tr('أدوار', 'roles')) ?>
-
3 = h(tr('فروع', 'branches')) ?>
-
2 = h(tr('لغات', 'languages')) ?>
-
-
-
= h(tr('أول قيمة عملية', 'First practical value')) ?>
-
= h(tr('ابدأ ببيع POS سريع، ثم راجع المبيعات والمخزون والتقارير من نفس الواجهة.', 'Start with a fast POS sale, then review sales, stock, and reports from one workspace.')) ?>
-
-
-
-
-
-
-
= h(tr('تسجيل الدخول', 'Sign in')) ?>
-
-
AR
-
EN
+
+
+
+
+
+
-
-
= h($error) ?>
-
-
-
- = h(tr('اسم المستخدم', 'Username')) ?>
-
-
-
- = h(tr('كلمة المرور', 'Password')) ?>
-
-
- = h(tr('دخول إلى التطبيق', 'Enter app')) ?>
-
-
= h(tr('حسابات تجريبية', 'Demo accounts')) ?>
-
-
-
- = h(current_lang() === 'ar' ? $account['name_ar'] : $account['name_en']) ?>
- = h(role_label($account['role'])) ?> · = h(branch_label($account['branch_code'])) ?>
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
= h(tr('أدخل اسم المستخدم أو البريد الإلكتروني وسنقوم بإرسال رابط لإعادة تعيين كلمة المرور الخاصة بك.', 'Enter your username or email and we will send you a link to reset your password.')) ?>
+
+ = h(tr('البريد الإلكتروني / اسم المستخدم', 'Email / Username')) ?>
+
+
+
+
+
+
+
+
+
-
+
-
+