Autosave: 20260419-100930
This commit is contained in:
parent
1ac0f55ef8
commit
6e3e0c556a
@ -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');
|
||||
|
||||
@ -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'])) {
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$user = require_auth();
|
||||
$user = require_permission('categories', 'show');
|
||||
$pageTitle = tr('التصنيفات', 'Categories');
|
||||
$activeNav = 'categories';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$user = require_auth();
|
||||
$user = require_permission('customers', 'show');
|
||||
$pageTitle = tr('العملاء', 'Customers');
|
||||
$activeNav = 'customers';
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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>
|
||||
|
||||
2
pos.php
2
pos.php
@ -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
278
print_labels.php
Normal 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>
|
||||
@ -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;
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
2
sale.php
2
sale.php
@ -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);
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
51
stock.php
51
stock.php
@ -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")) ?>', 'warninginaly);
|
||||
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"; ?>
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$user = require_auth();
|
||||
$user = require_permission('suppliers', 'show');
|
||||
$pageTitle = tr('الموردون', 'Suppliers');
|
||||
$activeNav = 'suppliers';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$user = require_auth();
|
||||
$user = require_permission('units', 'show');
|
||||
$pageTitle = tr('الوحدات', 'Units');
|
||||
$activeNav = 'units';
|
||||
|
||||
|
||||
91
users.php
91
users.php
@ -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();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user