update migrations
This commit is contained in:
parent
7781b48bf8
commit
66989c3877
11
debug.log
11
debug.log
@ -123,3 +123,14 @@
|
||||
2026-03-20 21:56:39 - Items case hit
|
||||
2026-03-20 21:56:47 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-03-20 21:56:51 - Items case hit
|
||||
2026-05-02 17:20:57 - Items case hit
|
||||
2026-05-02 17:23:36 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 17:29:22 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 17:29:31 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 17:29:39 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 17:29:45 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 17:32:00 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 17:32:02 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 17:32:10 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 17:32:11 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 17:32:54 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
|
||||
181
includes/translation_helper.php
Normal file
181
includes/translation_helper.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../ai/LocalAIApi.php';
|
||||
|
||||
if (!function_exists('app_translate_text')) {
|
||||
function app_translate_text(string $text, string $target): array
|
||||
{
|
||||
$cleanText = trim($text);
|
||||
$cleanTarget = strtolower(trim($target)) === 'en' ? 'en' : 'ar';
|
||||
|
||||
if ($cleanText === '') {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'No text provided',
|
||||
];
|
||||
}
|
||||
|
||||
if (function_exists('mb_substr')) {
|
||||
$cleanText = mb_substr($cleanText, 0, 500);
|
||||
} else {
|
||||
$cleanText = substr($cleanText, 0, 500);
|
||||
}
|
||||
|
||||
$skipAiUntil = isset($_SESSION['translation_ai_disabled_until']) ? (int) $_SESSION['translation_ai_disabled_until'] : 0;
|
||||
$shouldTryAi = $skipAiUntil <= time();
|
||||
$aiResult = [
|
||||
'success' => false,
|
||||
'error' => 'AI translation unavailable',
|
||||
'provider' => 'ai',
|
||||
];
|
||||
|
||||
if ($shouldTryAi) {
|
||||
$aiResult = app_translate_text_with_ai($cleanText, $cleanTarget);
|
||||
if (!empty($aiResult['success']) && !empty($aiResult['translated'])) {
|
||||
unset($_SESSION['translation_ai_disabled_until']);
|
||||
return $aiResult;
|
||||
}
|
||||
|
||||
$aiError = strtolower((string) ($aiResult['error'] ?? ''));
|
||||
if ($aiError !== '' && (strpos($aiError, '401') !== false || strpos($aiError, 'unauthorized') !== false || strpos($aiError, 'timeout') !== false)) {
|
||||
$_SESSION['translation_ai_disabled_until'] = time() + 900;
|
||||
}
|
||||
}
|
||||
|
||||
$fallbackResult = app_translate_text_with_google($cleanText, $cleanTarget);
|
||||
if (!empty($fallbackResult['success']) && !empty($fallbackResult['translated'])) {
|
||||
if (!empty($aiResult['error']) && $aiResult['error'] !== 'AI translation unavailable') {
|
||||
$fallbackResult['warning'] = (string) $aiResult['error'];
|
||||
}
|
||||
return $fallbackResult;
|
||||
}
|
||||
|
||||
if (!empty($aiResult['error']) && $aiResult['error'] !== 'AI translation unavailable') {
|
||||
return $aiResult;
|
||||
}
|
||||
|
||||
return $fallbackResult;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('app_translate_text_with_ai')) {
|
||||
function app_translate_text_with_ai(string $text, string $target): array
|
||||
{
|
||||
$prompt = "Translate the following product name to " . ($target === 'ar' ? 'Arabic' : 'English') . ". Return ONLY the translation, no extra text or explanations.\n\nText: " . $text;
|
||||
|
||||
$resp = LocalAIApi::createResponse([
|
||||
'input' => [
|
||||
['role' => 'system', 'content' => 'You are a translation assistant. You translate product names between English and Arabic. Keep brand names, sizes, numbers, and SKUs accurate. Return only the translated text.'],
|
||||
['role' => 'user', 'content' => $prompt],
|
||||
],
|
||||
], [
|
||||
'poll_interval' => 1,
|
||||
'poll_timeout' => 12,
|
||||
'timeout' => 20,
|
||||
]);
|
||||
|
||||
if (!empty($resp['success'])) {
|
||||
$translated = trim(LocalAIApi::extractText($resp));
|
||||
if ($translated !== '') {
|
||||
return [
|
||||
'success' => true,
|
||||
'translated' => $translated,
|
||||
'provider' => 'ai',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => (string) ($resp['error'] ?? 'AI translation failed'),
|
||||
'provider' => 'ai',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('app_translate_text_with_google')) {
|
||||
function app_translate_text_with_google(string $text, string $target): array
|
||||
{
|
||||
if (!function_exists('curl_init')) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Translation service unavailable: PHP cURL is missing',
|
||||
'provider' => 'fallback',
|
||||
];
|
||||
}
|
||||
|
||||
$query = http_build_query([
|
||||
'client' => 'gtx',
|
||||
'sl' => 'auto',
|
||||
'tl' => $target,
|
||||
'dt' => 't',
|
||||
'q' => $text,
|
||||
]);
|
||||
$url = 'https://translate.googleapis.com/translate_a/single?' . $query;
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_HTTPGET => true,
|
||||
CURLOPT_FAILONERROR => false,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: application/json',
|
||||
'User-Agent: Mozilla/5.0',
|
||||
],
|
||||
]);
|
||||
|
||||
$body = curl_exec($ch);
|
||||
if ($body === false) {
|
||||
$error = curl_error($ch) ?: 'Unknown translation request error';
|
||||
curl_close($ch);
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $error,
|
||||
'provider' => 'fallback',
|
||||
];
|
||||
}
|
||||
|
||||
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($status < 200 || $status >= 300 || $body === '' || $body === null) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Fallback translation request failed',
|
||||
'provider' => 'fallback',
|
||||
];
|
||||
}
|
||||
|
||||
$decoded = json_decode($body, true);
|
||||
if (!is_array($decoded) || empty($decoded[0]) || !is_array($decoded[0])) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Fallback translation returned an unexpected response',
|
||||
'provider' => 'fallback',
|
||||
];
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
foreach ($decoded[0] as $segment) {
|
||||
if (is_array($segment) && isset($segment[0]) && is_string($segment[0])) {
|
||||
$parts[] = $segment[0];
|
||||
}
|
||||
}
|
||||
|
||||
$translated = trim(implode('', $parts));
|
||||
if ($translated === '') {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Fallback translation returned empty text',
|
||||
'provider' => 'fallback',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'translated' => $translated,
|
||||
'provider' => 'fallback',
|
||||
];
|
||||
}
|
||||
}
|
||||
479
index.php
479
index.php
@ -1112,30 +1112,27 @@ if (isset($_GET['action']) || isset($_POST['action'])) {
|
||||
|
||||
if ($action === 'translate') {
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/ai/LocalAIApi.php';
|
||||
$text = $_POST['text'] ?? '';
|
||||
$target = $_POST['target'] ?? 'ar';
|
||||
|
||||
if (empty($text)) {
|
||||
echo json_encode(['success' => false, 'error' => 'No text provided']);
|
||||
require_once __DIR__ . '/includes/translation_helper.php';
|
||||
|
||||
$text = trim((string) ($_POST['text'] ?? ''));
|
||||
$target = strtolower(trim((string) ($_POST['target'] ?? ''))) === 'en' ? 'en' : 'ar';
|
||||
|
||||
if ($text === '') {
|
||||
echo json_encode(['success' => false, 'error' => 'No text provided'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$prompt = "Translate the following product name to " . ($target === 'ar' ? 'Arabic' : 'English') . ". Return ONLY the translation, no extra text or explanations.\n\nText: " . $text;
|
||||
|
||||
$resp = LocalAIApi::createResponse([
|
||||
'input' => [
|
||||
['role' => 'system', 'content' => 'You are a translation assistant. You translate product names between English and Arabic.'],
|
||||
['role' => 'user', 'content' => $prompt],
|
||||
],
|
||||
]);
|
||||
|
||||
if (!empty($resp['success'])) {
|
||||
$translated = trim(LocalAIApi::extractText($resp));
|
||||
echo json_encode(['success' => true, 'translated' => $translated]);
|
||||
$translationResult = app_translate_text($text, $target);
|
||||
if (!empty($translationResult['success']) && !empty($translationResult['translated'])) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'translated' => $translationResult['translated'],
|
||||
'provider' => $translationResult['provider'] ?? 'unknown',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
error_log("Translation failed for text '$text': " . json_encode($resp));
|
||||
echo json_encode(['success' => false, 'error' => $resp['error'] ?? 'Translation failed']);
|
||||
$errorMessage = (string) ($translationResult['error'] ?? 'Translation failed');
|
||||
error_log("Translation failed for text '$text': " . json_encode($translationResult, JSON_UNESCAPED_UNICODE));
|
||||
echo json_encode(['success' => false, 'error' => $errorMessage], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
@ -1407,11 +1404,18 @@ function getPromotionalPrice($item) {
|
||||
db()->query("UPDATE stock_items SET is_promotion = 0 WHERE is_promotion = 1 AND promotion_end IS NOT NULL AND promotion_end < '" . date('Y-m-d') . "'");
|
||||
|
||||
if (isset($_POST['add_category'])) {
|
||||
db()->prepare("INSERT INTO stock_categories (name_en, name_ar, outlet_id) VALUES (?, ?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', current_outlet_id()]);
|
||||
$current_oid = current_outlet_id();
|
||||
db()->prepare("INSERT INTO stock_categories (name_en, name_ar, outlet_id) VALUES (?, ?, ?)")
|
||||
->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $current_oid]);
|
||||
redirectWithMessage("Category added!");
|
||||
}
|
||||
if (isset($_POST['add_unit'])) {
|
||||
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar, outlet_id) VALUES (?, ?, ?, ?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $_POST['short_en'] ?? '', $_POST['short_ar'] ?? '', current_outlet_id()]);
|
||||
$current_oid = current_outlet_id();
|
||||
$unitNameEn = trim((string)($_POST['name_en'] ?? ''));
|
||||
$unitNameAr = trim((string)($_POST['name_ar'] ?? ''));
|
||||
if ($unitNameAr === '') $unitNameAr = $unitNameEn;
|
||||
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar, outlet_id) VALUES (?, ?, ?, ?, ?)")
|
||||
->execute([$unitNameEn, $unitNameAr, $unitNameEn, $unitNameAr, $current_oid]);
|
||||
redirectWithMessage("Unit added!");
|
||||
}
|
||||
|
||||
@ -1424,7 +1428,10 @@ function getPromotionalPrice($item) {
|
||||
redirectWithMessage("Category deleted!");
|
||||
}
|
||||
if (isset($_POST['edit_unit'])) {
|
||||
db()->prepare("UPDATE stock_units SET name_en = ?, name_ar = ?, short_name_en = ?, short_name_ar = ? WHERE id = ?")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $_POST['short_en'] ?? '', $_POST['short_ar'] ?? '', (int)$_POST['id']]);
|
||||
$unitNameEn = trim((string)($_POST['name_en'] ?? ''));
|
||||
$unitNameAr = trim((string)($_POST['name_ar'] ?? ''));
|
||||
if ($unitNameAr === '') $unitNameAr = $unitNameEn;
|
||||
db()->prepare("UPDATE stock_units SET name_en = ?, name_ar = ?, short_name_en = ?, short_name_ar = ? WHERE id = ?")->execute([$unitNameEn, $unitNameAr, $unitNameEn, $unitNameAr, (int)$_POST['id']]);
|
||||
redirectWithMessage("Unit updated!");
|
||||
}
|
||||
if (isset($_POST['delete_unit'])) {
|
||||
@ -2089,13 +2096,14 @@ function getPromotionalPrice($item) {
|
||||
}
|
||||
if (isset($rows[0][0]) && stripos($rows[0][0], 'name') !== false) array_shift($rows);
|
||||
|
||||
$current_oid = current_outlet_id();
|
||||
foreach ($rows as $row) {
|
||||
if (empty($row[0])) continue;
|
||||
$name_en = trim((string)$row[0]);
|
||||
$name_ar = trim((string)($row[1] ?? $name_en));
|
||||
|
||||
db()->prepare("INSERT INTO stock_categories (name_en, name_ar, outlet_id) VALUES (?, ?, ?)")
|
||||
->execute([$name_en, $name_ar, current_outlet_id()]);
|
||||
->execute([$name_en, $name_ar, $current_oid]);
|
||||
$count++;
|
||||
}
|
||||
redirectWithMessage("Import categories completed! $count processed.", "index.php?page=categories");
|
||||
@ -2121,12 +2129,11 @@ function getPromotionalPrice($item) {
|
||||
foreach ($rows as $row) {
|
||||
if (empty($row[0])) continue;
|
||||
$name_en = trim((string)$row[0]);
|
||||
$name_ar = trim((string)($row[1] ?? $name_en));
|
||||
$short_en = trim((string)($row[2] ?? ''));
|
||||
$short_ar = trim((string)($row[3] ?? ''));
|
||||
$name_ar = trim((string)($row[1] ?? ''));
|
||||
if ($name_ar === '') $name_ar = $name_en;
|
||||
|
||||
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar, outlet_id) VALUES (?, ?, ?, ?, ?)")
|
||||
->execute([$name_en, $name_ar, $short_en, $short_ar, current_outlet_id()]);
|
||||
->execute([$name_en, $name_ar, $name_en, $name_ar, current_outlet_id()]);
|
||||
$count++;
|
||||
}
|
||||
redirectWithMessage("Import units completed! $count processed.", "index.php?page=units");
|
||||
@ -3678,12 +3685,14 @@ if ($page === 'export') {
|
||||
$headers = ['LPO #', 'Supplier', 'Date', 'Total', 'Status'];
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
|
||||
} elseif ($type === 'categories') {
|
||||
$stmt = db()->query("SELECT id, name_en, name_ar FROM stock_categories WHERE outlet_id = ".current_outlet_id()." ORDER BY id DESC");
|
||||
$stmt = db()->prepare("SELECT id, name_en, name_ar FROM stock_categories WHERE outlet_id = ? ORDER BY id DESC");
|
||||
$stmt->execute([current_outlet_id()]);
|
||||
$headers = ['ID', 'Name (EN)', 'Name (AR)'];
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
|
||||
} elseif ($type === 'units') {
|
||||
$stmt = db()->query("SELECT id, name_en, name_ar, short_name_en, short_name_ar FROM stock_units WHERE outlet_id = ".current_outlet_id()." ORDER BY id DESC");
|
||||
$headers = ['ID', 'Name (EN)', 'Name (AR)', 'Short (EN)', 'Short (AR)'];
|
||||
$stmt = db()->prepare("SELECT id, name_en, name_ar FROM stock_units WHERE outlet_id = ? ORDER BY id DESC");
|
||||
$stmt->execute([current_outlet_id()]);
|
||||
$headers = ['ID', 'Name (EN)', 'Name (AR)'];
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
|
||||
} elseif ($type === 'sales_returns') {
|
||||
$salesReturnReferenceColumn = sales_return_reference_column();
|
||||
@ -3736,9 +3745,16 @@ if ($page === 'export') {
|
||||
}
|
||||
|
||||
// Global data for modals
|
||||
$data['categories'] = db()->query("SELECT * FROM stock_categories WHERE outlet_id = ".current_outlet_id()." ORDER BY name_en ASC")->fetchAll();
|
||||
$data['units'] = db()->query("SELECT * FROM stock_units WHERE outlet_id = ".current_outlet_id()." ORDER BY name_en ASC")->fetchAll();
|
||||
$data['suppliers'] = db()->query("SELECT * FROM suppliers WHERE outlet_id = ".current_outlet_id()." ORDER BY name ASC")->fetchAll();
|
||||
$current_oid = current_outlet_id();
|
||||
$stmt = db()->prepare("SELECT * FROM stock_categories WHERE outlet_id = ? ORDER BY name_en ASC");
|
||||
$stmt->execute([$current_oid]);
|
||||
$data['categories'] = $stmt->fetchAll();
|
||||
$stmt = db()->prepare("SELECT * FROM stock_units WHERE outlet_id = ? ORDER BY name_en ASC");
|
||||
$stmt->execute([$current_oid]);
|
||||
$data['units'] = $stmt->fetchAll();
|
||||
$stmt = db()->prepare("SELECT * FROM suppliers WHERE outlet_id = ? ORDER BY name ASC");
|
||||
$stmt->execute([$current_oid]);
|
||||
$data['suppliers'] = $stmt->fetchAll();
|
||||
$data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll();
|
||||
$data['customers_list'] = db()->query("SELECT * FROM customers ORDER BY name ASC")->fetchAll();
|
||||
$customers = $data['customers_list']; // For backward compatibility in some modals
|
||||
@ -3861,7 +3877,7 @@ switch ($page) {
|
||||
$data['current_page'] = $page_num;
|
||||
|
||||
$oid = current_outlet_id();
|
||||
$stmt = db()->prepare("SELECT i.*, i.stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, u.short_name_en as unit_en, u.short_name_ar as unit_ar, s.name as supplier_name
|
||||
$stmt = db()->prepare("SELECT i.*, i.stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, COALESCE(NULLIF(u.name_en, ''), u.short_name_en) as unit_en, COALESCE(NULLIF(u.name_ar, ''), u.short_name_ar, u.name_en, u.short_name_en) as unit_ar, s.name as supplier_name
|
||||
FROM stock_items i
|
||||
|
||||
LEFT JOIN stock_categories c ON i.category_id = c.id
|
||||
@ -5895,231 +5911,152 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<?php elseif ($page === 'units'): ?>
|
||||
<?php
|
||||
$unitsTotal = count($data['units'] ?? []);
|
||||
$unitsArabicReady = 0;
|
||||
$unitsReceiptReady = 0;
|
||||
foreach (($data['units'] ?? []) as $unitSummary) {
|
||||
if (!empty($unitSummary['name_ar'])) {
|
||||
$unitsArabicReady++;
|
||||
}
|
||||
if (!empty($unitSummary['short_name_en']) && !empty($unitSummary['short_name_ar'])) {
|
||||
$unitsReceiptReady++;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="card p-4 units-page-card">
|
||||
<div class="units-shell">
|
||||
<section class="units-hero">
|
||||
<div class="d-flex flex-column flex-xl-row justify-content-between gap-4 align-items-xl-start">
|
||||
<div class="units-hero__copy">
|
||||
<span class="units-eyebrow" data-en="Stock setup" data-ar="إعدادات المخزون">Stock setup</span>
|
||||
<h5 class="m-0" data-en="Units built for clean inventory, receipts, and invoices" data-ar="وحدات مهيأة لمخزون منظم وإيصالات وفواتير أوضح">Units built for clean inventory, receipts, and invoices</h5>
|
||||
<p class="text-muted mt-3 mb-0" data-en="Keep names bilingual and short labels compact so item forms, tables, and printouts stay consistent." data-ar="حافظ على الأسماء ثنائية اللغة والاختصارات القصيرة حتى تبقى النماذج والجداول والمطبوعات متناسقة.">Keep names bilingual and short labels compact so item forms, tables, and printouts stay consistent.</p>
|
||||
</div>
|
||||
<div class="units-toolbar">
|
||||
<?php if (can('units_add')): ?>
|
||||
<a href="index.php?page=export&type=units&format=excel" class="btn btn-outline-success">
|
||||
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
|
||||
</a>
|
||||
<button class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#importUnitsModal">
|
||||
<i class="bi bi-file-earmark-arrow-up"></i> <span data-en="Import" data-ar="استيراد">Import</span>
|
||||
</button>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addUnitModal">
|
||||
<i class="bi bi-plus-lg"></i> <span data-en="Add Unit" data-ar="إضافة وحدة">Add Unit</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="units-stats">
|
||||
<article class="units-stat">
|
||||
<span class="units-stat__label" data-en="Total units" data-ar="إجمالي الوحدات">Total units</span>
|
||||
<strong><?= $unitsTotal ?></strong>
|
||||
<small data-en="Reusable across items and reports" data-ar="قابلة لإعادة الاستخدام عبر الأصناف والتقارير">Reusable across items and reports</small>
|
||||
</article>
|
||||
<article class="units-stat">
|
||||
<span class="units-stat__label" data-en="Arabic-ready" data-ar="جاهزة بالعربية">Arabic-ready</span>
|
||||
<strong><?= $unitsArabicReady ?></strong>
|
||||
<small><?= $unitsTotal > 0 ? $unitsArabicReady . ' / ' . $unitsTotal : '0 / 0' ?></small>
|
||||
</article>
|
||||
<article class="units-stat">
|
||||
<span class="units-stat__label" data-en="Receipt-ready" data-ar="جاهزة للإيصالات">Receipt-ready</span>
|
||||
<strong><?= $unitsReceiptReady ?></strong>
|
||||
<small data-en="Has both short labels" data-ar="تحتوي على الاختصارين">Has both short labels</small>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
<div class="card p-4">
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3 mb-4">
|
||||
<div>
|
||||
<h5 class="m-0"><span data-en="Units" data-ar="الوحدات">Units</span> (<?= count($data['units'] ?? []) ?>)</h5>
|
||||
<p class="text-muted small mb-0" data-en="Manage the English and Arabic unit names used across items, invoices, and reports." data-ar="قم بإدارة أسماء الوحدات بالإنجليزية والعربية المستخدمة في الأصناف والفواتير والتقارير.">Manage the English and Arabic unit names used across items, invoices, and reports.</p>
|
||||
</div>
|
||||
<?php if (can('units_add')): ?>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a href="index.php?page=export&type=units&format=excel" class="btn btn-outline-success">
|
||||
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
|
||||
</a>
|
||||
<button class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#importUnitsModal">
|
||||
<i class="bi bi-file-earmark-arrow-up"></i> <span data-en="Import" data-ar="استيراد">Import</span>
|
||||
</button>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addUnitModal">
|
||||
<i class="bi bi-plus-lg"></i> <span data-en="Add Unit" data-ar="إضافة وحدة">Add Unit</span>
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<section class="units-table-card">
|
||||
<div class="units-table-card__header">
|
||||
<div>
|
||||
<h6 class="mb-1" data-en="Units catalog" data-ar="دليل الوحدات">Units catalog</h6>
|
||||
<p class="text-muted small mb-0" data-en="Use clear full names and compact short codes for faster item entry." data-ar="استخدم أسماء واضحة واختصارات مختصرة لتسريع إدخال الأصناف.">Use clear full names and compact short codes for faster item entry.</p>
|
||||
</div>
|
||||
<span class="units-helper-pill" data-en="Tip: keep short labels under 6 characters" data-ar="نصيحة: اجعل الاختصار أقل من 6 أحرف">Tip: keep short labels under 6 characters</span>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle units-table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-en="Unit" data-ar="الوحدة">Unit</th>
|
||||
<th data-en="Short labels" data-ar="الاختصارات">Short labels</th>
|
||||
<th data-en="Readiness" data-ar="الجاهزية">Readiness</th>
|
||||
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($data['units'])): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-5">
|
||||
<div class="units-empty-state">
|
||||
<div class="units-empty-state__icon"><i class="bi bi-rulers"></i></div>
|
||||
<h6 class="mb-0" data-en="No units yet" data-ar="لا توجد وحدات بعد">No units yet</h6>
|
||||
<p class="text-muted mb-0" data-en="Add your first unit so items can reuse the same labels in invoices and reports." data-ar="أضف أول وحدة حتى تستخدم الأصناف نفس التسميات في الفواتير والتقارير.">Add your first unit so items can reuse the same labels in invoices and reports.</p>
|
||||
<?php if (can('units_add')): ?>
|
||||
<button class="btn btn-primary mt-2" data-bs-toggle="modal" data-bs-target="#addUnitModal">
|
||||
<i class="bi bi-plus-lg"></i> <span data-en="Create first unit" data-ar="إنشاء أول وحدة">Create first unit</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($data['units'] as $u): ?>
|
||||
<?php
|
||||
$unitNameEn = trim((string)($u['name_en'] ?? ''));
|
||||
$unitNameAr = trim((string)($u['name_ar'] ?? ''));
|
||||
$unitShortEn = trim((string)($u['short_name_en'] ?? ''));
|
||||
$unitShortAr = trim((string)($u['short_name_ar'] ?? ''));
|
||||
$hasBilingualName = $unitNameEn !== '' && $unitNameAr !== '';
|
||||
$hasReceiptShorts = $unitShortEn !== '' && $unitShortAr !== '';
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="units-name-stack">
|
||||
<span class="units-name-stack__primary"><?= htmlspecialchars($unitNameEn !== '' ? $unitNameEn : '---') ?></span>
|
||||
<span class="units-name-stack__secondary"><?= htmlspecialchars($unitNameAr !== '' ? $unitNameAr : '---') ?></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="units-short-stack">
|
||||
<span class="units-short-badge"><?= htmlspecialchars($unitShortEn !== '' ? $unitShortEn : '---') ?></span>
|
||||
<span class="units-short-badge units-short-badge--muted"><?= htmlspecialchars($unitShortAr !== '' ? $unitShortAr : '---') ?></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="units-readiness">
|
||||
<span class="badge units-status-badge <?= $hasBilingualName ? 'is-ready' : 'is-pending' ?>" data-en="Bilingual" data-ar="ثنائي اللغة">Bilingual</span>
|
||||
<span class="badge units-status-badge <?= $hasReceiptShorts ? 'is-ready' : 'is-pending' ?>" data-en="Receipt ready" data-ar="جاهزة للإيصال">Receipt ready</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="units-actions justify-content-end">
|
||||
<?php if (can('units_edit')): ?>
|
||||
<button class="btn btn-outline-primary btn-sm" title="Edit" data-bs-toggle="modal" data-bs-target="#editUnitModal<?= $u['id'] ?>"><i class="bi bi-pencil"></i></button>
|
||||
<?php endif; ?>
|
||||
<?php if (can('units_delete')): ?>
|
||||
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
|
||||
<input type="hidden" name="id" value="<?= $u['id'] ?>">
|
||||
<button type="submit" name="delete_unit" class="btn btn-outline-danger btn-sm" title="Delete"><i class="bi bi-trash"></i></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="table-responsive border rounded-3 bg-white">
|
||||
<table class="table align-middle units-table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-en="Name (EN)" data-ar="الاسم (إنجليزي)">Name (EN)</th>
|
||||
<th data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</th>
|
||||
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($data['units'])): ?>
|
||||
<tr>
|
||||
<td colspan="3" class="text-center py-5">
|
||||
<div class="units-empty-state">
|
||||
<div class="units-empty-state__icon"><i class="bi bi-rulers"></i></div>
|
||||
<h6 class="mb-0" data-en="No units yet" data-ar="لا توجد وحدات بعد">No units yet</h6>
|
||||
<p class="text-muted mb-0" data-en="Add your first unit so items can reuse the same labels in invoices and reports." data-ar="أضف أول وحدة حتى تستخدم الأصناف نفس التسميات في الفواتير والتقارير.">Add your first unit so items can reuse the same labels in invoices and reports.</p>
|
||||
<?php if (can('units_add')): ?>
|
||||
<button class="btn btn-primary mt-2" data-bs-toggle="modal" data-bs-target="#addUnitModal">
|
||||
<i class="bi bi-plus-lg"></i> <span data-en="Create first unit" data-ar="إنشاء أول وحدة">Create first unit</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($data['units'] as $u): ?>
|
||||
<?php
|
||||
$unitNameEn = trim((string)($u['name_en'] ?? ''));
|
||||
$unitNameAr = trim((string)($u['name_ar'] ?? ''));
|
||||
?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($unitNameEn !== '' ? $unitNameEn : '---') ?></td>
|
||||
<td><span lang="ar" dir="rtl" class="d-inline-block"><?= htmlspecialchars($unitNameAr !== '' ? $unitNameAr : '---') ?></span></td>
|
||||
<td class="text-end">
|
||||
<div class="units-actions justify-content-end">
|
||||
<?php if (can('units_edit')): ?>
|
||||
<button class="btn btn-outline-primary btn-sm" title="Edit" data-bs-toggle="modal" data-bs-target="#editUnitModal<?= $u['id'] ?>"><i class="bi bi-pencil"></i></button>
|
||||
<?php endif; ?>
|
||||
<?php if (can('units_delete')): ?>
|
||||
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
|
||||
<input type="hidden" name="id" value="<?= $u['id'] ?>">
|
||||
<button type="submit" name="delete_unit" class="btn btn-outline-danger btn-sm" title="Delete"><i class="bi bi-trash"></i></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="editUnitModal<?= $u['id'] ?>" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg text-start unit-modal">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<span class="units-modal-kicker" data-en="Refine labels" data-ar="تحسين التسميات">Refine labels</span>
|
||||
<h5 class="modal-title" data-en="Edit Unit" data-ar="تعديل الوحدة">Edit Unit</h5>
|
||||
<p class="text-muted small mb-0" data-en="Update the bilingual names and short codes used across stock items and invoices." data-ar="حدّث الأسماء الثنائية والاختصارات المستخدمة في الأصناف والفواتير.">Update the bilingual names and short codes used across stock items and invoices.</p>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="id" value="<?= $u['id'] ?>">
|
||||
<div class="modal-body pt-4">
|
||||
<div class="unit-form-shell">
|
||||
<section class="unit-form-section">
|
||||
<div class="unit-form-section__header">
|
||||
<div>
|
||||
<h6 class="mb-1" data-en="Display names" data-ar="الأسماء الكاملة">Display names</h6>
|
||||
<p class="text-muted small mb-0" data-en="Shown in item forms, reports, and detailed views." data-ar="تظهر في نماذج الأصناف والتقارير والعروض التفصيلية.">Shown in item forms, reports, and detailed views.</p>
|
||||
</div>
|
||||
<span class="units-helper-pill" data-en="Bilingual" data-ar="ثنائي اللغة">Bilingual</span>
|
||||
</div>
|
||||
<div class="unit-form-grid">
|
||||
<div class="unit-field">
|
||||
<label class="form-label" data-en="Name (EN)" data-ar="الاسم (إنجليزي)">Name (EN)</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="name_en" id="edit_unit_name_en_<?= $u['id'] ?>" class="form-control" value="<?= htmlspecialchars($unitNameEn) ?>" required>
|
||||
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="edit_unit_name_ar_<?= $u['id'] ?>" data-target="edit_unit_name_en_<?= $u['id'] ?>" data-to="en">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text" data-en="Example: Kilogram" data-ar="مثال: Kilogram">Example: Kilogram</div>
|
||||
</div>
|
||||
<div class="unit-field">
|
||||
<label class="form-label" data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="name_ar" id="edit_unit_name_ar_<?= $u['id'] ?>" class="form-control" value="<?= htmlspecialchars($unitNameAr) ?>" required>
|
||||
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="edit_unit_name_en_<?= $u['id'] ?>" data-target="edit_unit_name_ar_<?= $u['id'] ?>" data-to="ar">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text" data-en="Example: كيلوغرام" data-ar="مثال: كيلوغرام">Example: كيلوغرام</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="unit-form-section">
|
||||
<div class="unit-form-section__header">
|
||||
<div>
|
||||
<h6 class="mb-1" data-en="Short labels" data-ar="الاختصارات">Short labels</h6>
|
||||
<p class="text-muted small mb-0" data-en="Keep these compact for receipts, tables, and barcode labels." data-ar="اجعلها مختصرة للإيصالات والجداول وملصقات الباركود.">Keep these compact for receipts, tables, and barcode labels.</p>
|
||||
</div>
|
||||
<span class="units-helper-pill" data-en="Receipts" data-ar="الإيصالات">Receipts</span>
|
||||
</div>
|
||||
<div class="unit-form-grid">
|
||||
<div class="unit-field">
|
||||
<label class="form-label" data-en="Short (EN)" data-ar="الاختصار (إنجليزي)">Short (EN)</label>
|
||||
<input type="text" name="short_en" class="form-control" value="<?= htmlspecialchars($unitShortEn) ?>" maxlength="12" required placeholder="Kg">
|
||||
<div class="form-text" data-en="Best for item tables and invoice lines." data-ar="مثالي لصفوف الأصناف في الفواتير والجداول.">Best for item tables and invoice lines.</div>
|
||||
</div>
|
||||
<div class="unit-field">
|
||||
<label class="form-label" data-en="Short (AR)" data-ar="الاختصار (عربي)">Short (AR)</label>
|
||||
<input type="text" name="short_ar" class="form-control" value="<?= htmlspecialchars($unitShortAr) ?>" maxlength="12" required placeholder="كجم">
|
||||
<div class="form-text" data-en="Keep it easy to read when printed." data-ar="اجعله سهل القراءة عند الطباعة.">Keep it easy to read when printed.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="unit-preview-card mt-3">
|
||||
<span class="unit-preview-card__label" data-en="Current preview" data-ar="المعاينة الحالية">Current preview</span>
|
||||
<div class="unit-preview-row">
|
||||
<span class="unit-preview-chip"><?= htmlspecialchars($unitNameEn !== '' ? $unitNameEn : 'Name (EN)') ?></span>
|
||||
<span class="unit-preview-chip is-muted"><?= htmlspecialchars($unitShortEn !== '' ? $unitShortEn : 'Kg') ?></span>
|
||||
<span class="unit-preview-chip"><?= htmlspecialchars($unitNameAr !== '' ? $unitNameAr : 'الاسم') ?></span>
|
||||
<span class="unit-preview-chip is-muted"><?= htmlspecialchars($unitShortAr !== '' ? $unitShortAr : 'كجم') ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer pt-0 border-0">
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
|
||||
<button type="submit" name="edit_unit" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal fade" id="editUnitModal<?= $u['id'] ?>" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg text-start unit-modal">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<span class="units-modal-kicker" data-en="Refine labels" data-ar="تحسين التسميات">Refine labels</span>
|
||||
<h5 class="modal-title" data-en="Edit Unit" data-ar="تعديل الوحدة">Edit Unit</h5>
|
||||
<p class="text-muted small mb-0" data-en="Update the English and Arabic unit names used across stock items and invoices." data-ar="حدّث أسماء الوحدة بالإنجليزية والعربية المستخدمة في الأصناف والفواتير.">Update the English and Arabic unit names used across stock items and invoices.</p>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="id" value="<?= $u['id'] ?>">
|
||||
<div class="modal-body pt-4">
|
||||
<div class="unit-form-shell">
|
||||
<section class="unit-form-section">
|
||||
<div class="unit-form-section__header">
|
||||
<div>
|
||||
<h6 class="mb-1" data-en="Display names" data-ar="الأسماء الكاملة">Display names</h6>
|
||||
<p class="text-muted small mb-0" data-en="Shown in item forms, reports, and detailed views." data-ar="تظهر في نماذج الأصناف والتقارير والعروض التفصيلية.">Shown in item forms, reports, and detailed views.</p>
|
||||
</div>
|
||||
<span class="units-helper-pill" data-en="Bilingual" data-ar="ثنائي اللغة">Bilingual</span>
|
||||
</div>
|
||||
<div class="unit-form-grid">
|
||||
<div class="unit-field">
|
||||
<label class="form-label" data-en="Name (EN)" data-ar="الاسم (إنجليزي)">Name (EN)</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="name_en" id="edit_unit_name_en_<?= $u['id'] ?>" class="form-control" value="<?= htmlspecialchars($unitNameEn) ?>" required>
|
||||
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="edit_unit_name_ar_<?= $u['id'] ?>" data-target="edit_unit_name_en_<?= $u['id'] ?>" data-to="en">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text" data-en="Example: Kilogram" data-ar="مثال: Kilogram">Example: Kilogram</div>
|
||||
</div>
|
||||
<div class="unit-field">
|
||||
<label class="form-label" data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="name_ar" id="edit_unit_name_ar_<?= $u['id'] ?>" class="form-control" value="<?= htmlspecialchars($unitNameAr) ?>" required>
|
||||
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="edit_unit_name_en_<?= $u['id'] ?>" data-target="edit_unit_name_ar_<?= $u['id'] ?>" data-to="ar">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text" data-en="Example: كيلوغرام" data-ar="مثال: كيلوغرام">Example: كيلوغرام</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="unit-form-section">
|
||||
<div class="unit-form-section__header">
|
||||
<div>
|
||||
<h6 class="mb-1" data-en="Name preview" data-ar="معاينة الأسماء">Name preview</h6>
|
||||
<p class="text-muted small mb-0" data-en="This is how the unit name will appear across the app." data-ar="هكذا سيظهر اسم الوحدة في جميع أجزاء التطبيق.">This is how the unit name will appear across the app.</p>
|
||||
</div>
|
||||
<span class="units-helper-pill" data-en="Live label" data-ar="التسمية النهائية">Live label</span>
|
||||
</div>
|
||||
<div class="unit-preview-card mt-0">
|
||||
<span class="unit-preview-card__label" data-en="Current preview" data-ar="المعاينة الحالية">Current preview</span>
|
||||
<div class="unit-preview-row">
|
||||
<span class="unit-preview-chip"><?= htmlspecialchars($unitNameEn !== '' ? $unitNameEn : 'Name (EN)') ?></span>
|
||||
<span class="unit-preview-chip"><?= htmlspecialchars($unitNameAr !== '' ? $unitNameAr : 'الاسم') ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer pt-0 border-0">
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
|
||||
<button type="submit" name="edit_unit" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
|
||||
|
||||
@ -6311,7 +6248,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<div class="col-md-4"><label class="form-label">Name (AR)</label><div class="input-group"><input type="text" name="name_ar" id="editItemNameAr<?= $item['id'] ?>" class="form-control" value="<?= htmlspecialchars((string)($item['name_ar'] ?? '')) ?>" required><button class="btn btn-outline-secondary btn-translate" type="button" data-source="editItemNameEn<?= $item['id'] ?>" data-target="editItemNameAr<?= $item['id'] ?>" data-to="ar"><i class="bi bi-translate"></i> AR</button></div></div>
|
||||
<div class="col-md-4"><label class="form-label">SKU</label><input type="text" name="sku" class="form-control" value="<?= htmlspecialchars((string)($item['sku'] ?? '')) ?>"><div class="form-text">13-digit scale barcodes with the reserved prefixes are not allowed here. Save the 5-digit scale item code instead.</div></div>
|
||||
<div class="col-md-4"><label class="form-label" data-en="Category" data-ar="الفئة">Category</label><select name="category_id" class="form-select"><option value="">---</option><?php foreach ($data['categories'] ?? [] as $c): ?><option value="<?= $c['id'] ?>" <?= $c['id'] == $item['category_id'] ? 'selected' : '' ?>><?= htmlspecialchars($c['name_en']) ?></option><?php endforeach; ?></select></div>
|
||||
<div class="col-md-4"><label class="form-label">Unit</label><select name="unit_id" class="form-select"><option value="">---</option><?php foreach ($data['units'] ?? [] as $u): ?><option value="<?= $u['id'] ?>" <?= $u['id'] == $item['unit_id'] ? 'selected' : '' ?>><?= htmlspecialchars($u['short_name_en']) ?></option><?php endforeach; ?></select></div>
|
||||
<div class="col-md-4"><label class="form-label">Unit</label><select name="unit_id" class="form-select"><option value="">---</option><?php foreach ($data['units'] ?? [] as $u): ?><option value="<?= $u['id'] ?>" <?= $u['id'] == $item['unit_id'] ? 'selected' : '' ?>><?= htmlspecialchars((trim((string)($u['name_en'] ?? '')) !== '' ? $u['name_en'] : ($u['short_name_en'] ?? '---')) . ' / ' . (trim((string)($u['name_ar'] ?? '')) !== '' ? $u['name_ar'] : ($u['short_name_ar'] ?? '---'))) ?></option><?php endforeach; ?></select></div>
|
||||
<div class="col-md-4"><label class="form-label" data-en="Supplier" data-ar="المورد">Supplier</label><select name="supplier_id" class="form-select"><option value="">---</option><?php foreach ($data['suppliers'] ?? [] as $s): ?><option value="<?= $s['id'] ?>" <?= $s['id'] == $item['supplier_id'] ? 'selected' : '' ?>><?= htmlspecialchars($s['name']) ?></option><?php endforeach; ?></select></div>
|
||||
<div class="col-md-4"><label class="form-label">Sale Price</label><input type="number" step="0.001" name="sale_price" class="form-control" value="<?= (float)$item['sale_price'] ?>"></div>
|
||||
<div class="col-md-4"><label class="form-label">Purchase Price</label><input type="number" step="0.001" name="purchase_price" class="form-control" value="<?= (float)$item['purchase_price'] ?>"></div>
|
||||
@ -11320,7 +11257,7 @@ function loadSessionReport(id) {
|
||||
<div class="col-md-4"><label class="form-label" data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</label><div class="input-group"><input type="text" name="name_ar" id="addItemNameAr" class="form-control" required><button class="btn btn-outline-secondary btn-translate" type="button" data-source="addItemNameEn" data-target="addItemNameAr" data-to="ar"><i class="bi bi-translate"></i> AR</button></div></div>
|
||||
<div class="col-md-4"><label class="form-label" data-en="SKU / Barcode" data-ar="الباركود">SKU / Barcode</label><div class="input-group"><input type="text" name="sku" id="skuInput" class="form-control"><button class="btn btn-outline-secondary" type="button" id="suggestSkuBtn" data-en="Suggest" data-ar="اقتراح">Suggest</button></div><div class="form-text" data-en="Reserved 13-digit scale barcodes cannot be saved here. Use the 5-digit scale item code for weighing products." data-ar="لا يمكن حفظ باركود الميزان الكامل المكوّن من 13 رقمًا هنا. استخدم كود الصنف المكوّن من 5 أرقام لأصناف الميزان.">Reserved 13-digit scale barcodes cannot be saved here. Use the 5-digit scale item code for weighing products.</div></div>
|
||||
<div class="col-md-4"><label class="form-label" data-en="Category" data-ar="الفئة">Category</label><select name="category_id" class="form-select"><option value="">---</option><?php foreach ($data['categories'] ?? [] as $c): ?><option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name_en']) ?> / <?= htmlspecialchars($c['name_ar']) ?></option><?php endforeach; ?></select></div>
|
||||
<div class="col-md-4"><label class="form-label" data-en="Unit" data-ar="الوحدة">Unit</label><select name="unit_id" class="form-select"><option value="">---</option><?php foreach ($data['units'] ?? [] as $u): ?><option value="<?= $u['id'] ?>"><?= htmlspecialchars($u['short_name_en']) ?> / <?= htmlspecialchars($u['short_name_ar']) ?></option><?php endforeach; ?></select></div>
|
||||
<div class="col-md-4"><label class="form-label" data-en="Unit" data-ar="الوحدة">Unit</label><select name="unit_id" class="form-select"><option value="">---</option><?php foreach ($data['units'] ?? [] as $u): ?><option value="<?= $u['id'] ?>"><?= htmlspecialchars(trim((string)($u['name_en'] ?? '')) !== '' ? $u['name_en'] : ($u['short_name_en'] ?? '---')) ?> / <?= htmlspecialchars(trim((string)($u['name_ar'] ?? '')) !== '' ? $u['name_ar'] : ($u['short_name_ar'] ?? '---')) ?></option><?php endforeach; ?></select></div>
|
||||
<div class="col-md-4"><label class="form-label" data-en="Supplier" data-ar="المورد">Supplier</label><select name="supplier_id" class="form-select"><option value="">---</option><?php foreach ($data['suppliers'] ?? [] as $s): ?><option value="<?= $s['id'] ?>"><?= htmlspecialchars($s['name']) ?></option><?php endforeach; ?></select></div>
|
||||
<div class="col-md-4"><label class="form-label" data-en="Sale Price" data-ar="سعر البيع">Sale Price</label><input type="number" step="0.001" name="sale_price" class="form-control" value="0.000"></div>
|
||||
<div class="col-md-4"><label class="form-label" data-en="Purchase Price" data-ar="سعر الشراء">Purchase Price</label><input type="number" step="0.001" name="purchase_price" class="form-control" value="0.000"></div>
|
||||
@ -11479,9 +11416,9 @@ function loadSessionReport(id) {
|
||||
<div class="unit-import-note mb-3">
|
||||
<strong data-en="Supported columns" data-ar="الأعمدة المدعومة">Supported columns</strong>
|
||||
<ul class="mt-2 small">
|
||||
<li data-en="Name (EN), Name (AR), Short (EN), Short (AR)" data-ar="الاسم (إنجليزي)، الاسم (عربي)، الاختصار (إنجليزي)، الاختصار (عربي)">Name (EN), Name (AR), Short (EN), Short (AR)</li>
|
||||
<li data-en="Name (EN), Name (AR)" data-ar="الاسم (إنجليزي)، الاسم (عربي)">Name (EN), Name (AR)</li>
|
||||
<li data-en="CSV and XLSX files are accepted." data-ar="يتم قبول ملفات CSV و XLSX.">CSV and XLSX files are accepted.</li>
|
||||
<li data-en="Keep short labels concise for invoices and receipts." data-ar="اجعل الاختصارات قصيرة للفواتير والإيصالات.">Keep short labels concise for invoices and receipts.</li>
|
||||
<li data-en="If Arabic is blank, the English name will be reused automatically." data-ar="إذا كان الاسم العربي فارغاً فسيتم استخدام الاسم الإنجليزي تلقائياً.">If Arabic is blank, the English name will be reused automatically.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="unit-form-section">
|
||||
@ -11548,7 +11485,7 @@ function loadSessionReport(id) {
|
||||
<div>
|
||||
<span class="units-modal-kicker" data-en="Create unit" data-ar="إنشاء وحدة">Create unit</span>
|
||||
<h5 class="modal-title" data-en="Add Unit" data-ar="إضافة وحدة">Add Unit</h5>
|
||||
<p class="text-muted small mb-0" data-en="Use full bilingual names and short labels that stay readable on invoices and item cards." data-ar="استخدم أسماء ثنائية اللغة واختصارات قصيرة تبقى واضحة في الفواتير وبطاقات الأصناف.">Use full bilingual names and short labels that stay readable on invoices and item cards.</p>
|
||||
<p class="text-muted small mb-0" data-en="Use the full unit name in English and Arabic so stock screens stay consistent." data-ar="استخدم اسم الوحدة الكامل بالإنجليزية والعربية حتى تبقى شاشات المخزون متناسقة.">Use the full unit name in English and Arabic so stock screens stay consistent.</p>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
@ -11589,30 +11526,16 @@ function loadSessionReport(id) {
|
||||
<section class="unit-form-section">
|
||||
<div class="unit-form-section__header">
|
||||
<div>
|
||||
<h6 class="mb-1" data-en="Short labels" data-ar="الاختصارات">Short labels</h6>
|
||||
<p class="text-muted small mb-0" data-en="Keep these compact for receipts, tables, and barcode labels." data-ar="اجعلها مختصرة للإيصالات والجداول وملصقات الباركود.">Keep these compact for receipts, tables, and barcode labels.</p>
|
||||
<h6 class="mb-1" data-en="Name preview" data-ar="معاينة الأسماء">Name preview</h6>
|
||||
<p class="text-muted small mb-0" data-en="This is how the unit name will appear across the app." data-ar="هكذا سيظهر اسم الوحدة في جميع أجزاء التطبيق.">This is how the unit name will appear across the app.</p>
|
||||
</div>
|
||||
<span class="units-helper-pill" data-en="Receipts" data-ar="الإيصالات">Receipts</span>
|
||||
<span class="units-helper-pill" data-en="Live label" data-ar="التسمية النهائية">Live label</span>
|
||||
</div>
|
||||
<div class="unit-form-grid">
|
||||
<div class="unit-field">
|
||||
<label class="form-label" data-en="Short (EN)" data-ar="الاختصار (إنجليزي)">Short (EN)</label>
|
||||
<input type="text" name="short_en" class="form-control" maxlength="12" required placeholder="Kg">
|
||||
<div class="form-text" data-en="Best for item tables and invoice lines." data-ar="مثالي لصفوف الأصناف في الفواتير والجداول.">Best for item tables and invoice lines.</div>
|
||||
</div>
|
||||
<div class="unit-field">
|
||||
<label class="form-label" data-en="Short (AR)" data-ar="الاختصار (عربي)">Short (AR)</label>
|
||||
<input type="text" name="short_ar" class="form-control" maxlength="12" required placeholder="كجم">
|
||||
<div class="form-text" data-en="Keep it easy to read when printed." data-ar="اجعله سهل القراءة عند الطباعة.">Keep it easy to read when printed.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="unit-preview-card mt-3">
|
||||
<div class="unit-preview-card mt-0">
|
||||
<span class="unit-preview-card__label" data-en="Example preview" data-ar="معاينة مثال">Example preview</span>
|
||||
<div class="unit-preview-row">
|
||||
<span class="unit-preview-chip">Kilogram</span>
|
||||
<span class="unit-preview-chip is-muted">Kg</span>
|
||||
<span class="unit-preview-chip">كيلوغرام</span>
|
||||
<span class="unit-preview-chip is-muted">كجم</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -174,3 +174,13 @@
|
||||
2026-05-02 04:56:12 - POST: {"username":"admin","password":"admin","login":"1"}
|
||||
2026-05-02 04:56:19 - POST: {"username":"admin","password":"admin","login":"1"}
|
||||
2026-05-02 04:56:42 - POST: {"id":"3","name_en":"Bank Transfer","name_ar":"\u062a\u062d\u0648\u064a\u0644 \u0628\u0646\u0643\u064a","edit_payment_method":""}
|
||||
2026-05-02 17:20:51 - POST: {"name_en":"piece","name_ar":"\u062d\u0628\u0629","add_category":""}
|
||||
2026-05-02 17:23:36 - POST: {"action":"translate","text":"piece","target":"ar"}
|
||||
2026-05-02 17:29:22 - POST: {"action":"translate","text":"piece","target":"ar"}
|
||||
2026-05-02 17:29:31 - POST: {"action":"translate","text":"piece","target":"ar"}
|
||||
2026-05-02 17:29:44 - POST: {"name_en":"piece","name_ar":"\u062d\u0628\u0629","add_unit":""}
|
||||
2026-05-02 17:32:00 - POST: {"action":"translate","text":"piece","target":"ar"}
|
||||
2026-05-02 17:32:02 - POST: {"action":"translate","text":"%D9%82%D8%B7%D8%B9%D8%A9","target":"en"}
|
||||
2026-05-02 17:32:10 - POST: {"action":"translate","text":"\u0642\u0637\u0639\u0629","target":"en"}
|
||||
2026-05-02 17:32:11 - POST: {"action":"translate","text":"LAMING PINEAPPLE 454G","target":"ar"}
|
||||
2026-05-02 17:32:54 - POST: {"action":"translate","text":"onion","target":"ar"}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user