Autosave: 20260419-100930

This commit is contained in:
Flatlogic Bot 2026-04-19 10:09:23 +00:00
parent 1ac0f55ef8
commit 6e3e0c556a
19 changed files with 427 additions and 40 deletions

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/../includes/app.php';
$user = require_auth();
$user = require_permission('customers', 'add');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json');

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/../includes/app.php';
require_auth();
require_permission('settings', 'edit');
$user = current_user();
if (!in_array($user['role'], ['owner', 'manager'])) {

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/../includes/app.php';
$user = require_auth();
$user = require_permission('suppliers', 'add');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json');

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_auth();
$user = require_permission('categories', 'show');
$pageTitle = tr('التصنيفات', 'Categories');
$activeNav = 'categories';

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_auth();
$user = require_permission('customers', 'show');
$pageTitle = tr('العملاء', 'Customers');
$activeNav = 'customers';

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager', 'cashier']);
$user = require_permission('sales', 'edit');
$editSaleId = (int)($_GET['id'] ?? 0);
$editSale = null;

View File

@ -166,6 +166,7 @@ function require_auth(): array
return $user;
}
function get_app_modules(): array { return ["pos" => ["name_ar" => "نقاط البيع", "name_en" => "POS", "actions" => ["show", "add"]], "normal_sale" => ["name_ar" => "بيع عادي", "name_en" => "Normal Sale", "actions" => ["show", "add"]], "sales" => ["name_ar" => "المبيعات", "name_en" => "Sales", "actions" => ["show", "edit", "del"]], "purchases" => ["name_ar" => "المشتريات", "name_en" => "Purchases", "actions" => ["show", "add", "edit", "del"]], "stock" => ["name_ar" => "المخزون", "name_en" => "Stock", "actions" => ["show", "add", "edit", "del"]], "reports" => ["name_ar" => "التقارير", "name_en" => "Reports", "actions" => ["show"]], "customers" => ["name_ar" => "العملاء", "name_en" => "Customers", "actions" => ["show", "add", "edit", "del"]], "suppliers" => ["name_ar" => "الموردين", "name_en" => "Suppliers", "actions" => ["show", "add", "edit", "del"]], "categories" => ["name_ar" => "التصنيفات", "name_en" => "Categories", "actions" => ["show", "add", "edit", "del"]], "units" => ["name_ar" => "الوحدات", "name_en" => "Units", "actions" => ["show", "add", "edit", "del"]], "users" => ["name_ar" => "المستخدمين", "name_en" => "Users", "actions" => ["show", "add", "edit", "del"]], "settings" => ["name_ar" => "الإعدادات", "name_en" => "Settings", "actions" => ["show", "edit"]]]; } function has_permission(string $m, string $a = "show"): bool { $u = current_user(); if (!$u) return false; if ($u["role"] === "owner") return true; $p = !empty($u["permissions"]) ? (is_array($u["permissions"]) ? $u["permissions"] : json_decode($u["permissions"], true)) : []; return !empty($p[$m][$a]); } function require_permission(string $m, string $a = "show"): array { $u = require_auth(); if (!has_permission($m, $a)) { set_flash("warning", tr("ليس لديك صلاحية.", "You do not have permission.")); redirect_to("index.php"); } return $u; }
function require_roles(array $roles): array
{
$user = require_auth();

View File

@ -71,7 +71,8 @@ $isPublic = !isset($user) || !$user;
<i class="bi bi-speedometer2"></i> <?= h(tr('لوحة التحكم', 'Dashboard')) ?>
</a>
<!-- المخزون (Inventory) - Now First -->
<?php if (has_permission('stock', 'show') || has_permission('categories', 'show') || has_permission('units', 'show')): ?>
<!-- المخزون (Inventory) - Now First -->
<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>
@ -91,8 +92,10 @@ $isPublic = !isset($user) || !$user;
</a>
</div>
</div>
<?php endif; ?>
<!-- المبيعات (Sales) - Now Collapsible -->
<?php if (has_permission('sales', 'show') || has_permission('normal_sale', 'show') || has_permission('pos', 'show')): ?>
<!-- المبيعات (Sales) - Now Collapsible -->
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['sales', 'normal', 'pos']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapseSales" role="button" aria-expanded="<?= in_array($activeNav, ['sales', 'normal', 'pos']) ? 'true' : 'false' ?>" aria-controls="collapseSales">
<div class="d-flex justify-content-between align-items-center w-100">
<span><i class="bi bi-cart"></i> <?= h(tr('المبيعات', 'Sales')) ?></span>
@ -112,8 +115,10 @@ $isPublic = !isset($user) || !$user;
</a>
</div>
</div>
<?php endif; ?>
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['purchases', 'new_purchase']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapsePurchases" role="button" aria-expanded="<?= in_array($activeNav, ['purchases', 'new_purchase']) ? 'true' : 'false' ?>" aria-controls="collapsePurchases">
<?php if (has_permission('purchases', 'show')): ?>
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['purchases', 'new_purchase']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapsePurchases" role="button" aria-expanded="<?= in_array($activeNav, ['purchases', 'new_purchase']) ? 'true' : 'false' ?>" aria-controls="collapsePurchases">
<div class="d-flex justify-content-between align-items-center w-100">
<span><i class="bi bi-bag-plus"></i> <?= h(tr('المشتريات', 'Purchases')) ?></span>
<i class="bi bi-chevron-down toggle-icon" style="transition: transform 0.2s;"></i>
@ -129,24 +134,25 @@ $isPublic = !isset($user) || !$user;
</a>
</div>
</div>
<?php endif; ?>
<a class="list-group-item list-group-item-action <?= $activeNav === 'suppliers' ? 'active' : '' ?>" href="<?= h(url_for('suppliers.php')) ?>">
<i class="bi bi-truck"></i> <?= h(tr('الموردون', 'Suppliers')) ?>
</a>
<a class="list-group-item list-group-item-action <?= $activeNav === 'customers' ? 'active' : '' ?>" href="<?= h(url_for('customers.php')) ?>">
<i class="bi bi-people-fill"></i> <?= h(tr('العملاء', 'Customers')) ?>
</a>
<?php if ($user && in_array($user['role'], ['owner', 'manager'], true)): ?>
<?php if (has_permission('reports', 'show')): ?>
<a class="list-group-item list-group-item-action <?= $activeNav === 'reports' ? 'active' : '' ?>" href="<?= h(url_for('reports.php')) ?>">
<i class="bi bi-bar-chart"></i> <?= h(tr('التقارير', 'Reports')) ?>
</a>
<?php endif; ?>
<?php if ($user && $user['role'] === 'owner'): ?>
<?php if (has_permission('users', 'show')): ?>
<a class="list-group-item list-group-item-action <?= $activeNav === 'users' ? 'active' : '' ?>" href="<?= h(url_for('users.php')) ?>">
<i class="bi bi-people"></i> <?= h(tr('المستخدمون والأدوار', 'Users & Roles')) ?>
</a>
<?php endif; ?>
<?php if ($user && in_array($user['role'], ['owner', 'manager'])): ?>
<?php if (has_permission('settings', 'show')): ?>
<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>

View File

@ -1,7 +1,7 @@
<?php
$saleMode = 'pos';
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager', 'cashier']);
$user = require_permission('pos', 'show');
$pageTitle = tr('نقاط البيع', 'Smart POS');
$activeNav = 'pos';
$error = '';

278
print_labels.php Normal file
View File

@ -0,0 +1,278 @@
<?php
require_once __DIR__ . '/includes/app.php';
require_permission('stock', 'show');
$pdo = db();
$items = [];
$skus = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['skus'])) {
$skus = is_array($_POST['skus']) ? $_POST['skus'] : explode(',', $_POST['skus']);
} elseif (!empty($_GET['sku'])) {
$skus = [$_GET['sku']];
}
if (!empty($skus)) {
$placeholders = str_repeat('?,', count($skus) - 1) . '?';
$stmt = $pdo->prepare("SELECT * FROM items WHERE sku IN ($placeholders)");
$stmt->execute($skus);
$results = $stmt->fetchAll();
// Index by SKU
foreach ($results as $row) {
$items[$row['sku']] = $row;
}
}
// Templates configuration
$templates = [
'avery_65' => [
'name' => 'Avery L7651 - 65 Labels (38.1 x 21.2 mm)',
'cols' => 5,
'rows' => 13,
'width' => '38.1mm',
'height' => '21.2mm',
'margin_top' => '10.5mm',
'margin_left' => '4.7mm',
'gap_x' => '2.5mm',
'gap_y' => '0mm',
],
'avery_54' => [
'name' => 'Avery 54 Labels (32 x 25.4 mm)',
'cols' => 6,
'rows' => 9,
'width' => '32mm',
'height' => '25.4mm',
'margin_top' => '10mm',
'margin_left' => '5mm',
'gap_x' => '2mm',
'gap_y' => '0mm',
],
'avery_45' => [
'name' => 'Avery 45 Labels (38.1 x 29.6 mm)',
'cols' => 5,
'rows' => 9,
'width' => '38.1mm',
'height' => '29.6mm',
'margin_top' => '15mm',
'margin_left' => '5mm',
'gap_x' => '2mm',
'gap_y' => '0mm',
]
];
$templateId = $_REQUEST['template'] ?? 'avery_65';
$tpl = $templates[$templateId] ?? $templates['avery_65'];
// Prepare labels to print
$labelsToPrint = [];
if (!empty($items)) {
// Check if quantities are posted
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['qty'])) {
foreach ($_POST['qty'] as $sku => $qty) {
if (isset($items[$sku])) {
for ($i = 0; $i < (int)$qty; $i++) {
$labelsToPrint[] = $items[$sku];
}
}
}
} else {
// Default 1 per item
foreach ($skus as $sku) {
if (isset($items[$sku])) {
$labelsToPrint[] = $items[$sku];
}
}
}
}
?>
<!DOCTYPE html>
<html lang="<?= current_lang() ?>" dir="<?= current_lang() === 'ar' ? 'rtl' : 'ltr' ?>">
<head>
<meta charset="UTF-8">
<title><?= h(tr('طباعة ملصقات', 'Print Labels')) ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
<style>
body { background: #f0f2f5; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
.no-print { display: block; }
.print-only { display: none; }
.page {
width: 210mm;
min-height: 297mm;
padding-top: <?= $tpl['margin_top'] ?>;
padding-left: <?= $tpl['margin_left'] ?>;
padding-right: <?= $tpl['margin_left'] ?>;
margin: 10mm auto;
border: 1px solid #D3D3D3;
border-radius: 5px;
background: white;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
display: grid;
grid-template-columns: repeat(<?= $tpl['cols'] ?>, <?= $tpl['width'] ?>);
grid-auto-rows: <?= $tpl['height'] ?>;
column-gap: <?= $tpl['gap_x'] ?>;
row-gap: <?= $tpl['gap_y'] ?>;
box-sizing: border-box;
page-break-after: always;
}
.label-item {
width: <?= $tpl['width'] ?>;
height: <?= $tpl['height'] ?>;
border: 1px dashed #ccc;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
box-sizing: border-box;
padding: 2px;
text-align: center;
}
.label-name { font-size: 9px; font-weight: bold; line-height: 1.1; margin-bottom: 2px; max-height: 20px; overflow: hidden; }
.label-price { font-size: 10px; font-weight: bold; margin-top: 1px; }
.label-sku { font-size: 8px; color: #555; }
.label-barcode { margin: 0; display: flex; align-items: center; justify-content: center; }
.label-barcode svg { width: 100%; height: 100%; max-height: 14px; }
@media print {
body { background: white; margin: 0; padding: 0; }
.no-print { display: none !important; }
.print-only { display: block; }
.page {
margin: 0;
border: initial;
border-radius: initial;
width: 210mm;
min-height: 297mm;
box-shadow: initial;
background: initial;
page-break-after: always;
}
.label-item { border: none; }
@page { size: A4 portrait; margin: 0; }
}
</style>
</head>
<body>
<div class="container mt-4 no-print">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><?= h(tr('إعدادات الطباعة', 'Print Settings')) ?></h5>
<a href="stock.php" class="btn btn-sm btn-light"><?= h(tr('رجوع للمخزون', 'Back to Stock')) ?></a>
</div>
<div class="card-body">
<form method="POST">
<div class="row g-3 align-items-end mb-3">
<div class="col-md-4">
<label class="form-label fw-bold"><?= h(tr('قالب الملصقات', 'Label Template')) ?></label>
<select name="template" class="form-select" onchange="this.form.submit()">
<?php foreach($templates as $key => $t): ?>
<option value="<?= $key ?>" <?= $templateId === $key ? 'selected' : '' ?>><?= $t['name'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-8 text-end">
<button type="button" class="btn btn-success" onclick="window.print()">
<i class="bi bi-printer"></i> <?= h(tr('طباعة الآن', 'Print Now')) ?>
</button>
</div>
</div>
<div class="table-responsive border rounded">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>SKU</th>
<th><?= h(tr('الصنف', 'Item')) ?></th>
<th><?= h(tr('السعر', 'Price')) ?></th>
<th width="150" class="text-center"><?= h(tr('عدد الملصقات', 'Labels Count')) ?></th>
<th width="80" class="text-center"><?= h(tr('إزالة', 'Remove')) ?></th>
</tr>
</thead>
<tbody>
<?php foreach($items as $sku => $item): ?>
<tr>
<td class="fw-bold text-secondary">
<?= h($sku) ?>
<input type="hidden" name="skus[]" value="<?= h($sku) ?>">
</td>
<td><?= h($item['name']) ?></td>
<td><span class="badge bg-light text-dark border"><?= h(currency($item['price'])) ?></span></td>
<td>
<input type="number" name="qty[<?= h($sku) ?>]" class="form-control text-center" value="<?= isset($_POST['qty'][$sku]) ? (int)$_POST['qty'][$sku] : 1 ?>" min="1">
</td>
<td class="text-center">
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle" onclick="this.closest('tr').remove();" title="<?= h(tr('إزالة', 'Remove')) ?>">
<i class="bi bi-x-lg"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
<?php if(empty($items)): ?>
<tr>
<td colspan="5" class="text-center text-muted py-4">
<i class="bi bi-inbox fs-3 d-block mb-2"></i>
<?= h(tr('لم يتم تحديد أصناف', 'No items selected')) ?>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php if(!empty($items)): ?>
<div class="mt-3">
<button type="submit" class="btn btn-primary">
<i class="bi bi-arrow-clockwise"></i> <?= h(tr('تحديث المعاينة', 'Update Preview')) ?>
</button>
</div>
<?php endif; ?>
</form>
</div>
</div>
</div>
<?php if (!empty($labelsToPrint)): ?>
<div class="print-preview mt-4">
<h5 class="text-center no-print text-muted mb-3"><i class="bi bi-view-list"></i> <?= h(tr('معاينة الطباعة', 'Print Preview')) ?></h5>
<?php
$labelsPerPage = $tpl['cols'] * $tpl['rows'];
$pages = array_chunk($labelsToPrint, $labelsPerPage);
foreach ($pages as $pageIdx => $pageLabels):
?>
<div class="page bg-white">
<?php foreach ($pageLabels as $label): ?>
<div class="label-item">
<div class="label-name"><?= h($label['name']) ?></div>
<div class="label-barcode">
<svg class="barcode"
jsbarcode-format="code128"
jsbarcode-value="<?= h($label['sku']) ?>"
jsbarcode-displayvalue="false"
jsbarcode-width="1"
jsbarcode-height="15"
jsbarcode-margin="0">
</svg>
</div>
<div class="label-sku"><?= h($label['sku']) ?></div>
<div class="label-price"><?= h(currency($label['price'])) ?></div>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<script>
document.addEventListener("DOMContentLoaded", function() {
if (typeof JsBarcode !== 'undefined') {
JsBarcode(".barcode").init();
}
});
</script>
</body>
</html>

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager', 'cashier']);
$user = require_permission('sales', 'show');
$id = (int) ($_GET['id'] ?? 0);
$sale = null;
$dbError = null;

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager']);
$user = require_permission('purchases', 'show');
$pageTitle = tr('المشتريات', 'Purchases');
$activeNav = 'purchases';

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager']);
$user = require_permission('reports', 'show');
$pageTitle = tr('التقارير', 'Reports');
$activeNav = 'reports';

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager', 'cashier']);
$user = require_permission('sales', 'show');
$pageTitle = tr('تفاصيل الفاتورة الضريبية', 'Tax Invoice Details');
$activeNav = 'sales';
$id = (int) ($_GET['id'] ?? 0);

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager', 'cashier']);
$user = require_permission('sales', 'show');
$pageTitle = tr('المبيعات', 'Sales Ledger');
$activeNav = 'sales';

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager', 'cashier']);
$user = require_permission('stock', 'show');
$pageTitle = tr('المخزون', 'Stock');
$activeNav = 'stock';
$dbError = null;
@ -149,6 +149,9 @@ require __DIR__ . '/includes/header.php';
<p class="text-muted mb-0"><?= h(tr('إدارة الأصناف وجرد المخزون.', 'Manage items and inventory.')) ?></p>
</div>
<div class="col-lg-4 text-lg-end">
<button type="button" class="btn btn-secondary shadow-sm me-2" onclick="printBulkLabels()">
<i class="bi bi-upc-scan"></i> <?= h(tr('طباعة ملصقات', 'Print Labels')) ?>
</button>
<button type="button" class="btn btn-primary shadow-sm" onclick="openItemModal()">
<i class="bi bi-plus-lg"></i> <?= h(tr('إضافة صنف', 'Add Item')) ?>
</button>
@ -198,6 +201,9 @@ require __DIR__ . '/includes/header.php';
<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 width="40" class="text-white border-0 py-3 fw-semibold bg-transparent">
<input class="form-check-input" type="checkbox" id="selectAllCheckbox" onclick="toggleAllCheckboxes(this)">
</th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent">SKU</th>
<th width="70" class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('صورة', 'Pic')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent text-start"><?= h(tr('الصنف', 'Product')) ?></th>
@ -212,11 +218,14 @@ require __DIR__ . '/includes/header.php';
</thead>
<tbody class="border-top-0">
<?php if(empty($stockRows)): ?>
<tr><td colspan="10" class="text-center text-muted py-5"><i class="bi bi-inbox fs-1 d-block text-black-50 mb-2"></i> <?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
<tr><td colspan="11" class="text-center text-muted py-5"><i class="bi bi-inbox fs-1 d-block text-black-50 mb-2"></i> <?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
<?php endif; ?>
<?php foreach ($stockRows as $row): ?>
<tr style="transition: all 0.2s ease;">
<td class="text-secondary"><?= h($row['sku']) ?></td>
<td>
<input class="form-check-input item-checkbox" type="checkbox" value="<?= h($row['sku']) ?>">
</td>
<td class="text-secondary"><?= h($row['sku']) ?></td>
<td>
<?php if (!empty($row['image_url'])): ?>
<img src="<?= h($row['image_url']) ?>" alt="pic" class="img-thumbnail p-0 shadow-sm" style="width: 45px; height: 45px; object-fit: cover; border-radius: 10px;">
@ -243,6 +252,9 @@ require __DIR__ . '/includes/header.php';
<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['cost_price'] ?? 0) ?>', '<?= 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>
<a href="print_labels.php?sku=<?= urlencode($row['sku']) ?>" class="btn btn-sm btn-outline-secondary rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0; line-height: 32px;" data-bs-toggle="tooltip" title="<?= h(tr('طباعة ملصق', 'Print Label')) ?>">
<i class="bi bi-printer"></i>
</a>
<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')) ?>">
<i class="bi bi-trash"></i>
</button>
@ -533,6 +545,37 @@ function deleteItem(sku) {
}
});
}
function toggleAllCheckboxes(source) {
const checkboxes = document.querySelectorAll(".item-checkbox");
for (let i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = source.checked;
}
}
function printBulkLabels() {
const checkboxes = document.querySelectorAll(".item-checkbox:checked");
if (checkboxes.length === 0) {
Swal.fire('<?= h(tr("تنبيه", "Warning")) ?>', '<?= h(tr("يرجى تحديد صنف واحد على الأقل", "Please select at least one item")) ?>', 'warning inaly);
return;
}
let skus = [];
checkboxes.forEach((cb) => skus.push(cb.value));
const form = document.createElement('form');
form.method = 'POST';
form.action = 'print_labels.php';
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'skus';
input.value = skus.join(\",\");
form.appendChild(input);
document.body.appendChild(form);
form.submit();
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>
<?php require __DIR__ . "/includes/footer.php"; ?>

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_auth();
$user = require_permission('suppliers', 'show');
$pageTitle = tr('الموردون', 'Suppliers');
$activeNav = 'suppliers';

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_auth();
$user = require_permission('units', 'show');
$pageTitle = tr('الوحدات', 'Units');
$activeNav = 'units';

View File

@ -1,6 +1,6 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner']);
$user = require_permission('users', 'show');
$pageTitle = tr('المستخدمون والأدوار', 'Users & Roles');
$activeNav = 'users';
@ -11,6 +11,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'add') {
if (!has_permission('users', 'add')) { set_flash('error', tr('ليس لديك صلاحية', 'No permission')); redirect_to('users.php'); }
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$name_ar = trim($_POST['name_ar'] ?? '');
@ -21,8 +22,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($username && $password && $name_ar) {
$hash = password_hash($password, PASSWORD_DEFAULT);
try {
$stmt = db()->prepare("INSERT INTO users (username, password, role, branch_code, name_ar, name_en) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$username, $hash, $role, $branch_code, $name_ar, $name_en]);
$stmt = db()->prepare("INSERT INTO users (username, password, role, branch_code, name_ar, name_en, permissions) VALUES (?, ?, ?, ?, ?, ?, ?)");
$perms = isset($_POST["permissions"]) ? json_encode($_POST["permissions"]) : "{}";
$stmt->execute([$username, $hash, $role, $branch_code, $name_ar, $name_en, $perms]);
set_flash('success', tr('تمت إضافة المستخدم بنجاح.', 'User added successfully.'));
} catch (PDOException $e) {
set_flash('error', tr('حدث خطأ، قد يكون اسم المستخدم موجوداً مسبقاً.', 'Error occurred, username might already exist.'));
@ -34,6 +36,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
if ($action === 'edit') {
if (!has_permission('users', 'edit')) { set_flash('error', tr('ليس لديك صلاحية', 'No permission')); redirect_to('users.php'); }
$id = (int)($_POST['id'] ?? 0);
$username = trim($_POST['username'] ?? '');
$name_ar = trim($_POST['name_ar'] ?? '');
@ -46,11 +49,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
if ($password) {
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = db()->prepare("UPDATE users SET username=?, password=?, role=?, branch_code=?, name_ar=?, name_en=? WHERE id=?");
$stmt->execute([$username, $hash, $role, $branch_code, $name_ar, $name_en, $id]);
$stmt = db()->prepare("UPDATE users SET username=?, password=?, role=?, branch_code=?, name_ar=?, name_en=?, permissions=? WHERE id=?");
$perms = isset($_POST["permissions"]) ? json_encode($_POST["permissions"]) : "{}";
$stmt->execute([$username, $hash, $role, $branch_code, $name_ar, $name_en, $perms, $id]);
} else {
$stmt = db()->prepare("UPDATE users SET username=?, role=?, branch_code=?, name_ar=?, name_en=? WHERE id=?");
$stmt->execute([$username, $role, $branch_code, $name_ar, $name_en, $id]);
$stmt = db()->prepare("UPDATE users SET username=?, role=?, branch_code=?, name_ar=?, name_en=?, permissions=? WHERE id=?");
$perms = isset($_POST["permissions"]) ? json_encode($_POST["permissions"]) : "{}";
$stmt->execute([$username, $role, $branch_code, $name_ar, $name_en, $perms, $id]);
}
set_flash('success', tr('تم تعديل المستخدم بنجاح.', 'User updated successfully.'));
} catch (PDOException $e) {
@ -61,6 +66,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
if ($action === 'delete') {
if (!has_permission('users', 'del')) { set_flash('error', tr('ليس لديك صلاحية', 'No permission')); redirect_to('users.php'); }
$id = (int)($_POST['id'] ?? 0);
if ($id && $id !== $user['id']) {
$stmt = db()->prepare("DELETE FROM users WHERE id=?");
@ -103,9 +109,11 @@ require __DIR__ . '/includes/header.php';
<h3 class="h5 mb-2"><i class="bi bi-people me-2"></i><?= h(tr('الوصول حسب الدور', 'Role-based access')) ?></h3>
<p class="text-muted mb-0"><?= h(tr('إدارة المستخدمين وصلاحيات الوصول للنظام.', 'Manage users and system access permissions.')) ?></p>
</div>
<?php if(has_permission("users", "add")): ?>
<button type="button" class="btn btn-primary" onclick="openAddModal()">
<i class="bi bi-person-plus"></i> <?= h(tr('إضافة مستخدم', 'Add User')) ?>
</button>
<?php endif; ?>
</div>
<form class="d-flex mb-3" method="GET" action="users.php">
@ -131,9 +139,7 @@ require __DIR__ . '/includes/header.php';
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('المستخدم', 'User')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الدور', 'Role')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الفرع', 'Branch')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent">POS</th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('تقارير', 'Reports')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('مستخدمون', 'Users')) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr("صلاحيات مخصصة", "Custom Permissions")) ?></th>
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('إجراءات', 'Actions')) ?></th>
</tr>
</thead>
@ -149,15 +155,13 @@ require __DIR__ . '/includes/header.php';
</td>
<td><?= h(role_label($account['role'])) ?></td>
<td><?= h(branch_label($account['branch_code'])) ?></td>
<td><span class="badge text-bg-light border"><?= h(tr('نعم', 'Yes')) ?></span></td>
<td><span class="badge <?= in_array($account['role'], ['owner', 'manager'], true) ? 'text-bg-light border' : 'text-bg-secondary' ?>"><?= h(in_array($account['role'], ['owner', 'manager'], true) ? tr('نعم', 'Yes') : tr('لا', 'No')) ?></span></td>
<td><span class="badge <?= $account['role'] === 'owner' ? 'text-bg-light border' : 'text-bg-secondary' ?>"><?= h($account['role'] === 'owner' ? tr('نعم', 'Yes') : tr('لا', 'No')) ?></span></td>
<td><span class="badge text-bg-light border"><?= h(empty($account["permissions"]) || $account["permissions"] === "{}" ? tr("الافتراضي", "Default") : tr("مخصصة", "Custom")) ?></span></td>
<td>
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;"
onclick='openEditModal(<?= json_encode($account) ?>)' title="<?= h(tr('تعديل', 'Edit')) ?>">
onclick='openEditModal(<?= htmlspecialchars(json_encode($account), ENT_QUOTES, "UTF-8") ?>)' title="<?= h(tr('تعديل', 'Edit')) ?>">
<i class="bi bi-pencil"></i>
</button>
<?php if ($account['id'] !== $user['id']): ?>
<?php if ($account['id'] !== $user['id'] && has_permission("users", "del")): ?>
<form method="POST" class="d-inline" onsubmit="return confirm('<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= h($account['id']) ?>">
@ -232,7 +236,43 @@ require __DIR__ . '/includes/header.php';
<?php endforeach; ?>
</select>
</div>
</div> <!-- Close row -->
<h6 class="mt-4 mb-3"><?= h(tr('صلاحيات الوصول المخصصة', 'Custom Access Permissions')) ?></h6>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead class="table-light">
<tr>
<th><?= h(tr('الواجهة', 'Module')) ?></th>
<th class="text-center"><?= h(tr('عرض', 'Show')) ?></th>
<th class="text-center"><?= h(tr('إضافة', 'Add')) ?></th>
<th class="text-center"><?= h(tr('تعديل', 'Edit')) ?></th>
<th class="text-center"><?= h(tr('حذف', 'Delete')) ?></th>
</tr>
</thead>
<tbody>
<?php foreach (get_app_modules() as $key => $module): ?>
<tr>
<td><?= h(current_lang() === 'ar' ? $module['name_ar'] : $module['name_en']) ?></td>
<?php foreach (['show', 'add', 'edit', 'del'] as $act): ?>
<td class="text-center">
<?php if (in_array($act, $module['actions'])): ?>
<div class="form-check d-flex justify-content-center mb-0">
<input class="form-check-input perm-check" type="checkbox" name="permissions[<?= $key ?>][<?= $act ?>]" id="perm_<?= $key ?>_<?= $act ?>" value="1">
</div>
<?php else:
?>
<span class="text-muted">-</span>
<?php endif; ?>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= h(tr('إلغاء', 'Cancel')) ?></button>
@ -244,12 +284,16 @@ require __DIR__ . '/includes/header.php';
</div>
<script>
const userModal = new bootstrap.Modal(document.getElementById('userModal'));
let userModal;
document.addEventListener('DOMContentLoaded', function() {
userModal = new bootstrap.Modal(document.getElementById('userModal'));
});
function openAddModal() {
document.getElementById('userAction').value = 'add';
document.getElementById('userId').value = '';
document.getElementById('userForm').reset();
document.querySelectorAll('.perm-check').forEach(cb => cb.checked = false);
document.getElementById('userModalLabel').innerText = '<?= h(tr('إضافة مستخدم جديد', 'Add New User')) ?>';
document.getElementById('password').required = true;
document.getElementById('passwordHelp').innerText = '';
@ -271,6 +315,21 @@ function openEditModal(account) {
document.getElementById('password').value = '';
document.getElementById('passwordHelp').innerText = '(<?= h(tr('اتركه فارغاً لعدم التغيير', 'Leave blank to keep unchanged')) ?>)';
document.querySelectorAll('.perm-check').forEach(cb => cb.checked = false);
document.querySelectorAll('.perm-check').forEach(cb => cb.checked = false);
if (account.permissions) {
try {
let perms = typeof account.permissions === 'string' ? JSON.parse(account.permissions) : account.permissions;
for (let mod in perms) {
for (let act in perms[mod]) {
let cb = document.getElementById('perm_' + mod + '_' + act);
if (cb) cb.checked = true;
}
}
} catch (e) {}
}
document.getElementById('userModalLabel').innerText = '<?= h(tr('تعديل مستخدم', 'Edit User')) ?>';
userModal.show();
}