splitting pages and final touch

This commit is contained in:
Flatlogic Bot 2026-05-03 04:30:55 +00:00
parent b89863b4d3
commit c53e944d52
45 changed files with 3715 additions and 36975 deletions

View File

@ -28,6 +28,53 @@ class LocalAIApi
/** @var array<string,mixed>|null */ /** @var array<string,mixed>|null */
private static ?array $configCache = null; private static ?array $configCache = null;
private static function debugFileLoggingEnabled(): bool
{
static $enabled = null;
if ($enabled !== null) {
return $enabled;
}
$candidates = [
getenv('APP_FILE_DEBUG_LOGS'),
$_ENV['APP_FILE_DEBUG_LOGS'] ?? null,
$_SERVER['APP_FILE_DEBUG_LOGS'] ?? null,
];
foreach ($candidates as $candidate) {
if ($candidate === false || $candidate === null) {
continue;
}
$value = strtolower(trim((string) $candidate));
if ($value === '') {
continue;
}
if (in_array($value, ['1', 'true', 'yes', 'on'], true)) {
$enabled = true;
return true;
}
if (in_array($value, ['0', 'false', 'no', 'off'], true)) {
$enabled = false;
return false;
}
}
$enabled = false;
return false;
}
private static function debugFileLog(string $message): void
{
if (!self::debugFileLoggingEnabled()) {
return;
}
@file_put_contents(__DIR__ . '/../debug.log', rtrim($message, "\r\n") . PHP_EOL, FILE_APPEND);
}
/** /**
* Signature compatible with the OpenAI Responses API. * Signature compatible with the OpenAI Responses API.
* *
@ -107,8 +154,7 @@ class LocalAIApi
$projectUuid = $cfg['project_uuid']; $projectUuid = $cfg['project_uuid'];
// DEBUG LOGGING self::debugFileLog(date('Y-m-d H:i:s') . " - Requesting AI. UUID: [" . ($projectUuid ?? 'NULL') . "] CFG: " . json_encode($cfg));
file_put_contents(__DIR__ . '/../debug.log', date('Y-m-d H:i:s') . " - Requesting AI. UUID: [" . ($projectUuid ?? 'NULL') . "] CFG: " . json_encode($cfg) . "\n", FILE_APPEND);
if (empty($projectUuid)) { if (empty($projectUuid)) {
return [ return [

View File

@ -1 +0,0 @@
2026-02-21 18:35:57 - 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}

View File

@ -1,5 +0,0 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
127.0.0.1 FALSE / FALSE 0 PHPSESSID b6ihps49oas08569v0vg5i1hnb

View File

@ -1,5 +0,0 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
127.0.0.1 FALSE / FALSE 0 PHPSESSID 2uea9bvpufvp1th7vnnvcog2as

View File

@ -1,5 +0,0 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
127.0.0.1 FALSE / FALSE 0 PHPSESSID f3c2q95r0m6iaptq2skotkpemo

View File

@ -1,5 +0,0 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
127.0.0.1 FALSE / FALSE 0 PHPSESSID il48dnd8duujsd9cbumbfbum85

138
debug.log
View File

@ -1,138 +0,0 @@
[INFO] AI agent editing: index.php
2026-02-26 08:18:16 - Items case hit
2026-03-01 13:00:29 - Items case hit
2026-03-01 13:17:12 - Items case hit
2026-03-01 18:24:40 - Items case hit
2026-03-17 09:58:59 - Items case hit
2026-03-17 10:04:31 - Items case hit
2026-03-17 10:12:20 - Items case hit
2026-03-17 17:26:10 - Items case hit
2026-03-17 18:04:40 - Items case hit
2026-03-17 19:06:45 - Items case hit
2026-03-17 19:07:15 - 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-17 19:07:52 - 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-17 19:08:16 - Items case hit
2026-03-17 19:08:47 - Items case hit
2026-03-17 21:28:08 - Items case hit
2026-03-17 21:37:02 - Items case hit
2026-03-17 21:46:30 - Items case hit
2026-03-17 21:47:03 - 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-17 21:47:17 - Items case hit
2026-03-17 21:47:27 - Items case hit
2026-03-17 22:30:34 - Items case hit
2026-03-17 22:30:54 - Items case hit
2026-03-17 22:31:12 - Items case hit
2026-03-17 22:31:17 - Items case hit
2026-03-17 22:36:20 - Items case hit
2026-03-17 22:40:51 - Items case hit
2026-03-17 18:45:47 - Items case hit
2026-03-17 18:47:55 - Items case hit
2026-03-17 18:51:56 - Items case hit
2026-03-17 18:54:27 - Items case hit
2026-03-18 01:51:29 - Items case hit
2026-03-18 01:51:45 - Items case hit
2026-03-18 02:00:48 - Items case hit
2026-03-18 02:02:37 - Items case hit
2026-03-18 02:03:03 - Items case hit
2026-03-18 02:05:55 - Items case hit
2026-03-18 02:06:44 - Items case hit
2026-03-18 02:12:27 - Items case hit
2026-03-18 02:14:19 - Items case hit
2026-03-18 02:15:13 - Items case hit
2026-03-18 02:15:31 - Items case hit
2026-03-18 02:15:56 - Items case hit
2026-03-18 02:16:33 - Items case hit
2026-03-18 02:17:32 - Items case hit
2026-03-18 02:17:48 - Items case hit
2026-03-18 02:18:21 - Items case hit
2026-03-18 02:19:01 - Items case hit
2026-03-18 02:19:49 - Items case hit
2026-03-18 02:20:06 - Items case hit
2026-03-18 02:20:47 - Items case hit
2026-03-18 02:21:53 - Items case hit
2026-03-18 02:24:31 - Items case hit
2026-03-18 02:26:44 - Items case hit
2026-03-18 02:28:48 - Items case hit
2026-03-18 02:28:58 - Items case hit
2026-03-18 06:00:05 - Items case hit
2026-03-18 14:33:50 - Items case hit
2026-03-18 14:34:10 - Items case hit
2026-03-18 17:01:37 - Items case hit
2026-03-18 17:07:17 - Items case hit
2026-03-18 17:09:46 - Items case hit
2026-03-18 17:35:42 - Items case hit
2026-03-18 17:36:53 - Items case hit
2026-03-18 17:49:12 - Items case hit
2026-03-18 18:07:03 - Items case hit
2026-03-18 18:08:41 - Items case hit
2026-03-18 22:12:51 - Items case hit
2026-03-18 22:15:09 - Items case hit
2026-03-18 22:24:28 - Items case hit
2026-03-18 22:25:14 - Items case hit
2026-03-18 22:25:43 - Items case hit
2026-03-18 22:26:38 - Items case hit
2026-03-18 22:29:27 - Items case hit
2026-03-18 22:29:45 - Items case hit
2026-03-18 22:30:17 - 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-18 22:30:49 - Items case hit
2026-03-18 22:31:04 - 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-18 22:31:16 - Items case hit
2026-03-18 22:31:26 - 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-18 22:31:35 - Items case hit
2026-03-18 22:31: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}
2026-03-18 22:32:01 - Items case hit
2026-03-18 22:32:15 - 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-18 22:32:28 - Items case hit
2026-03-18 22:33:07 - Items case hit
2026-03-18 22:40:07 - Items case hit
2026-03-18 22:40:13 - Items case hit
2026-03-18 22:40:18 - Items case hit
2026-03-18 22:40:29 - Items case hit
2026-03-18 22:40:46 - Items case hit
2026-03-18 22:41:01 - Items case hit
2026-03-18 22:41:16 - Items case hit
2026-03-18 23:02:18 - Items case hit
2026-03-18 23:03:06 - Items case hit
2026-03-18 23:03:19 - Items case hit
2026-03-18 23:05:39 - Items case hit
2026-03-18 23:06:02 - Items case hit
2026-03-19 05:57:24 - Items case hit
2026-03-19 05:57:45 - Items case hit
2026-03-19 05:58:17 - 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-19 05:58:34 - Items case hit
2026-03-19 05:58:51 - 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-19 05:59:01 - Items case hit
2026-03-19 06:00:05 - Items case hit
2026-03-19 18:14:48 - Items case hit
2026-03-19 18:15:00 - Items case hit
2026-03-19 18:15:13 - Items case hit
2026-03-19 21:18:34 - Items case hit
2026-03-19 21:18:52 - Items case hit
2026-03-20 04:58:13 - Test write from web server
2026-03-20 09:01:08 - 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 09:01: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:46:17 - Items case hit
2026-03-20 21:46:35 - 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:46:43 - Items case hit
2026-03-20 21:46:56 - 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:47:03 - Items case hit
2026-03-20 21:47:15 - 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:47:23 - Items case hit
2026-03-20 21:56:20 - Items case hit
2026-03-20 21:56:30 - 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: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}
2026-05-02 18:41:44 - Items case hit
2026-05-03 01:36:24 - Items case hit

View File

@ -1,18 +0,0 @@
<?php
require_once 'db/config.php';
require_once 'includes/accounting_helper.php';
try {
echo "Checking acc_accounts table...\n";
$accounts = db()->query("SELECT * FROM acc_accounts LIMIT 5")->fetchAll();
echo "Found " . count($accounts) . " accounts.\n";
print_r($accounts);
echo "\nTesting getAccountBalance('1100')...\n";
$balance = getAccountBalance('1100');
echo "Balance for 1100: " . $balance . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}

View File

@ -1,17 +0,0 @@
<?php
require_once 'db/config.php';
$whereSql = "1=1";
$params = [];
$stmt = db()->prepare("SELECT i.*, 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
FROM stock_items i
LEFT JOIN stock_categories c ON i.category_id = c.id
LEFT JOIN stock_units u ON i.unit_id = u.id
LEFT JOIN suppliers s ON i.supplier_id = s.id
WHERE $whereSql
ORDER BY i.id DESC");
$stmt->execute($params);
$items = $stmt->fetchAll();
echo "Count: " . count($items) . "\n";
foreach ($items as $item) {
echo "ID: {$item['id']}, Name: {$item['name_en']}\n";
}

View File

@ -1,51 +0,0 @@
<?php
$file = 'index.php';
$content = file_get_contents($file);
if ($content === false) {
die("Failed to read index.php");
}
$search = <<<'EOD'
case 'accounting':
$data['journal_entries'] = db()->query("SELECT je.*,
(SELECT SUM(debit) FROM acc_ledger WHERE journal_entry_id = je.id) as total_debit
FROM acc_journal_entries je
ORDER BY je.entry_date DESC, je.id DESC LIMIT 100")->fetchAll();
EOD;
$replace = <<<'EOD'
case 'accounting':
// Pagination for Journal Entries
$currentPage = isset($_GET['p']) ? max(1, (int)$_GET['p']) : 1;
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20;
$offset = ($currentPage - 1) * $limit;
$total_entries = db()->query("SELECT COUNT(*) FROM acc_journal_entries")->fetchColumn();
$data['total_pages'] = ceil($total_entries / $limit);
$data['current_page'] = $currentPage;
$data['journal_entries'] = db()->query("SELECT je.*,
(SELECT SUM(debit) FROM acc_ledger WHERE journal_entry_id = je.id) as total_debit
FROM acc_journal_entries je
ORDER BY je.entry_date DESC, je.id DESC LIMIT $limit OFFSET $offset")->fetchAll();
EOD;
// Normalize line endings
$content = str_replace("\r\n", "\n", $content);
$search = str_replace("\r\n", "\n", $search);
$replace = str_replace("\r\n", "\n", $replace);
if (strpos($content, $search) !== false) {
$newContent = str_replace($search, $replace, $content);
file_put_contents($file, $newContent);
echo "Successfully patched index.php\n";
} else {
echo "Could not find the code block to replace.\n";
// Debug: print a small chunk around where we expect it
$pos = strpos($content, "case 'accounting':");
if ($pos !== false) {
echo "Found case 'accounting': at position $pos. Content around it:\n";
echo substr($content, $pos, 500) . "\n";
}
}

View File

@ -1,30 +0,0 @@
<?php
$content = file_get_contents('index.php');
$search1 = '<div class="mb-3">
<label class="form-label fw-semibold" data-en="Assigned Outlet" data-ar="الفرع">Assigned Outlet</label>
<select name="outlet_id" class="form-select">
<option value="">--- Global (Admin only) ---</option>
<?php foreach (($data[\'outlets\'] ?? []) as $o): ?>
<option value="<?= $o[\'id\'] ?>" <?= ($u[\'outlet_id\'] ?? null) == $o[\'id\'] ? \'selected\' : \'\' ?>><?= htmlspecialchars($o[\'name\']) ?></option>
<?php endforeach; ?>
</select>
</div>';
$content = str_replace($search1 . "\n " . $search1, $search1, $content);
$search2 = '<div class="mb-3">
<label class="form-label" data-en="Assigned Outlet" data-ar="الفرع">Assigned Outlet</label>
<select name="outlet_id" class="form-select">
<option value="">--- Global (Admin only) ---</option>
<?php foreach (($data[\'outlets\'] ?? []) as $o): ?>
<option value="<?= $o[\'id\'] ?>"><?= htmlspecialchars($o[\'name\']) ?></option>
<?php endforeach; ?>
</select>
</div>';
$content = str_replace($search2 . "\n " . $search2, $search2, $content);
file_put_contents('index.php', $content);
echo "Duplicates removed.\n";

View File

@ -1,149 +0,0 @@
import re
file_path = 'index.php'
with open(file_path, 'r') as f:
content = f.read()
# 1. Replace <div class="row g-3"> with <div class="form-grid-3">
# We want to be careful. The user specifically mentioned "Items edit form" and "Add new items form".
# And "Apply the style to all forms in app".
# "row g-3" is the standard Bootstrap form layout class. Replacing it with "form-grid-3" (which I defined)
# should be safe for forms.
# However, "row g-3" might be used for non-form layouts (e.g. stats cards).
# I should look for inputs inside.
# But checking content inside is hard with regex.
# Let's count how many "row g-3" there are.
matches = len(re.findall(r'class=["\']row g-3["\']', content))
print(f"Found {matches} instances of 'row g-3'.")
# Strategy:
# Replace 'class="row g-3"' with 'class="form-grid-3"' IF it's inside a form or modal context.
# Or just replace all? "row g-3" implies a grid with gap 3 (1rem).
# My "form-grid-3" is a grid with gap 1rem and 3 columns.
# If "row g-3" was used for 2 columns (col-6) or 4 columns (col-3), forcing 3 columns might break layout.
# BUT the user said "Apply the style to all forms".
# Most "row g-3" in this app seem to be forms (based on previous grep).
# Non-form grids usually use "row" without "g-3" or with different gap?
# Let's assume "row g-3" is the form standard here.
# I'll define a function to replace specific occurrences if I can identify them.
# The "Edit Item" modal is around line 4964.
# The "Add Item" modal is around line 10141.
# Regex to find <div class="row g-3"> and replace it, but maybe verify context?
# I'll just do a global replace for now, but I will backup first.
# Wait, "row g-3" in a dashboard stats widget (e.g. 4 cards) would become 3 columns.
# That might be annoying.
# I should inspect if there are stats widgets using "row g-3".
# Let's look for "col-md-3" or "col-xl-3" inside "row g-3".
# If I see "col-xl-3", it's likely a 4-column layout (12/3 = 4).
# My "form-grid-3" forces 3 columns.
# So replacing it would break 4-column layouts.
# "Edit Item" uses "col-md-3", "col-md-4", "col-md-6".
# If I change the container to grid-3, the children become grid items.
# I added CSS to make children width 100%.
# To be safer, I will only replace "row g-3" if it contains "form-control" or "form-select" or "form-label" inside it (heuristic).
# This is tricky with regex.
# Alternative: Find the specific blocks for Items and change them.
# Then find other *forms*.
# Let's use a simpler approach.
# I will search for the specific lines for Edit Item and Add Item and change them.
# Then I will search for other obvious forms.
new_content = content
# 1. Edit Item Modal (around line 4975)
# Context: <div class="modal fade" id="editItemModal...
# ... <form ...> ... <div class="modal-body"> ... <div class="row g-3">
# I'll use regex to match the structure.
# Pattern for Edit/Add Item Modals (and potentially others)
# Matches <div class="row g-3"> inside a form or modal body?
# Actually, the user wants "items edit form" specifically.
# I'll replace specifically in the Item Modals first.
# "Edit Item" has id="editItemModal..."
# "Add Item" has id="addItemModal"
# But "Edit Item" is inside a PHP loop? No, the modal ID has PHP echo.
# L4965: <div class="modal fade" id="editItemModal<?= $item['id'] ?>" ...
# ...
# L4975: <div class="row g-3">
# I'll verify the lines again.
# L4975 is likely correct.
# I'll proceed with a more manual replacement for the Items forms to ensure they are fixed.
# Then I'll check "Edit Profile" (L8558) and "Company Profile" (L8594).
def replace_block(text, start_marker, end_marker, old_class, new_class):
# Find start marker
pattern = re.compile(re.escape(start_marker) + r'.*?' + re.escape(old_class), re.DOTALL)
# This is hard because of multiple occurrences.
pass
# Direct replacement on known lines (approximate) is safer if I can locate them uniquely.
# Or replace all "row g-3" that are inside <form>?
# That covers "all forms".
# Let's try to replace <div class="row g-3"> with <div class="form-grid-3"> everywhere,
# BUT checking if it looks like a form.
# A form usually has <input>, <select>, <label>.
# I'll iterate through all "row g-3" occurrences.
# For each, I'll check if the following content (up to closing div) contains form elements.
# This requires parsing HTML, which is hard.
# Let's go with the user's specific request + "all forms" interpretation.
# I will replace `class="row g-3"` with `class="form-grid-3"` ONLY if the immediate context looks like a form.
# I.e. if I see `<form ... class="row g-3">` -> Replace.
# If I see `<div class="row g-3">` followed closely by `<div class="col`.
# Let's use a regex that matches `<div class="row g-3">` followed by whitespace and `<div class="col`.
# Most forms follow this pattern.
# Pattern: <div class="row g-3">\s*<div class="col
regex_div = r'(<div class="row g-3">)(\s*<div class="col)'
new_content = re.sub(regex_div, r'<div class="form-grid-3">\2', new_content)
# Pattern: <form ... class="row g-3">
regex_form = r'(<form [^>]*class=")(row g-3)(")'
new_content = re.sub(regex_form, r'\1form-grid-3\3', new_content)
# Also explicitly fix the Items modals if they weren't caught (e.g. if there's comment or something in between).
# L4975: <div class="row g-3"> (inside Edit Item).
# It has <div class="col-md-6"> immediately after?
# Let's check L4975 in `read_file` output.
# L4975: <div class="row g-3">
# L4976: <div class="col-md-6">
# Yes, it matches.
# L10151 (Add Item):
# L10151: <div class="row g-3">
# L10152: <div class="col-md-6">
# Matches.
# So the regex `(<div class="row g-3">)(\s*<div class="col)` should catch them.
# One more thing: The user asked to "Add background to titles bar".
# I've added CSS for `.modal-header`.
# But for non-modals (like Edit Profile), they use `<h5>`.
# I should try to wrap them or add a class.
# L8554: <h5 class="mb-4 fw-bold" data-en="Edit Profile"...
# I'll use regex to find these specific h5 titles in cards and wrap them or add a class.
# Search for `class="card p-4 ...">` then `<h5>`.
# This is too brittle. I'll stick to the CSS solution for Modals and hope it's enough for "titles bar".
# The user specifically mentioned "Items edit form" (which is a modal).
# If "Edit Profile" doesn't have a background title, I can manually fix it if requested or if I can find it reliably.
# I'll stick to the safe replacement for now.
with open(file_path, 'w') as f:
f.write(new_content)
print("Replacement complete.")

View File

@ -1,58 +0,0 @@
with open('index.php', 'r') as f:
lines = f.readlines()
# The corrupted block starts around line 50 (index 50 is line 51)
# And ends around line 65 where "// Timezone Setup" is.
# Let's find "require_once 'db/config.php';" which I added, and "// Timezone Setup"
start_idx = -1
end_idx = -1
for i, line in enumerate(lines):
if "require_once 'db/config.php';" in line:
start_idx = i
if "// Timezone Setup" in line:
end_idx = i
break
if start_idx != -1 and end_idx != -1:
print(f"Replacing lines {start_idx+1} to {end_idx}")
new_block = """require_once 'db/config.php';
require_once 'includes/stock_helper.php';
// Helper for current outlet
if (!function_exists('current_outlet_id')) {
function current_outlet_id() {
if (session_status() === PHP_SESSION_NONE) session_start();
return (int)($_SESSION['outlet_id'] ?? 1);
}
}
// Handle Outlet Switch
if (isset($_GET['action']) && $_GET['action'] === 'switch_outlet' && isset($_GET['id'])) {
$target_id = (int)$_GET['id'];
$allowed_outlets = $_SESSION['user_outlets'] ?? [1];
$is_admin = ($_SESSION['user_role_name'] ?? '') === 'Administrator';
if ($is_admin || in_array($target_id, $allowed_outlets)) {
$stmt = db()->prepare("SELECT id FROM outlets WHERE id = ? AND status = 'active'");
$stmt->execute([$target_id]);
if ($stmt->fetchColumn()) {
$_SESSION['outlet_id'] = $target_id;
}
}
header("Location: index.php");
exit;
}
"""
# Replace the slice
lines[start_idx:end_idx] = [new_block]
with open('index.php', 'w') as f:
f.writelines(lines)
print("Fixed index.php")
else:
print("Could not find block boundaries.")

View File

@ -1,11 +0,0 @@
<?php
$content = file_get_contents('index.php');
$search = "\$_SESSION['outlet_id'] = \$u['outlet_id'];";
$replacement = "\$_SESSION['outlet_id'] = \$u['outlet_id'];";
// To fix duplicates, we can simply replace 3 of them with 1
$content = preg_replace("/(\s*\\\$_\SESSION\['outlet_id'\] = \\\$u\['outlet_id'\];){2,}/", "\n \$_SESSION['outlet_id'] = \$u['outlet_id'];", $content);
file_put_contents('index.php', $content);
echo "Duplicate login outlet_ids removed.\n";

View File

@ -1,16 +0,0 @@
<?php
// ... (lines 1-464)
if ($action === 'get_payment_details') {
header('Content-Type: application/json');
$payment_id = (int)$_GET['payment_id'];
$stmt = db()->prepare("SELECT p.*, i.customer_id, c.name as customer_name, o.name as outlet_name
FROM payments p
JOIN invoices i ON p.invoice_id = i.id
JOIN customers c ON i.customer_id = c.id
LEFT JOIN outlets o ON i.outlet_id = o.id
WHERE p.id = ?");
$stmt->execute([$payment_id]);
echo json_encode($stmt->fetch(PDO::FETCH_ASSOC));
exit;
}
// ...

View File

@ -1,30 +0,0 @@
<?php
require_once 'db/config.php';
$pdo = db();
try {
// 1. Assign default outlet_id to NULLs
$pdo->exec("UPDATE _stock_items SET outlet_id = 1 WHERE outlet_id IS NULL");
$pdo->exec("UPDATE _stock_categories SET outlet_id = 1 WHERE outlet_id IS NULL");
$pdo->exec("UPDATE _stock_units SET outlet_id = 1 WHERE outlet_id IS NULL");
// 2. Fix _stock_items index
// First, drop old unique index if it exists
try {
$pdo->exec("ALTER TABLE _stock_items DROP INDEX sku");
} catch (Exception $e) {
echo "Note: index 'sku' might not exist or already dropped: " . $e->getMessage() . "\n";
}
// Add new unique index (outlet_id, sku)
$pdo->exec("ALTER TABLE _stock_items ADD UNIQUE KEY outlet_sku (outlet_id, sku)");
// 3. Fix _stock_categories index
$pdo->exec("ALTER TABLE _stock_categories ADD UNIQUE KEY outlet_cat_name (outlet_id, name_en)");
// 4. Fix _stock_units index
$pdo->exec("ALTER TABLE _stock_units ADD UNIQUE KEY outlet_unit_name (outlet_id, name_en)");
echo "Database schema updated successfully.\n";
} catch (Exception $e) {
echo "Error updating database schema: " . $e->getMessage() . "\n";
}

View File

@ -1,2 +0,0 @@
<?php
// Just a final check to remove any leftovers from previous attempts if any

View File

@ -1,36 +0,0 @@
<?php
$content = file_get_contents('index.php');
$search_th = '<th data-en="Access Level" data-ar="مستوى الوصول">Access Level</th>';
$replace_th = '<th data-en="Access Level" data-ar="مستوى الوصول">Access Level</th>
<th data-en="Outlet" data-ar="الفرع">Outlet</th>';
$content = str_replace($search_th, $replace_th, $content);
$search_td = '<span class="badge rounded-pill bg-info bg-opacity-10 text-info px-3">
<?= htmlspecialchars((string)($u[\'group_name\'] ?? \'No Role Assigned\')) ?>
</span>
</td>';
$replace_td = '<span class="badge rounded-pill bg-info bg-opacity-10 text-info px-3">
<?= htmlspecialchars((string)($u[\'group_name\'] ?? \'No Role Assigned\')) ?>
</span>
</td>
<td>
<span class="badge rounded-pill bg-secondary bg-opacity-10 text-secondary px-3">
<?php
$out_name = "Global / All Outlets";
foreach (($data["outlets"] ?? []) as $out) {
if ($out["id"] == $u["outlet_id"]) {
$out_name = $out["name"];
break;
}
}
echo htmlspecialchars($out_name);
?>
</span>
</td>';
$content = str_replace($search_td, $replace_td, $content);
file_put_contents('index.php', $content);
echo "Users table updated to show assigned outlet.\n";

3699
index.php

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
2026-02-26 17:58:24 - Failed login for 'admin'. Reason: Password mismatch
2026-02-26 17:58:40 - Failed login for 'admin'. Reason: Password mismatch
2026-02-26 17:58:46 - Failed login for 'admin'. Reason: Password mismatch
2026-02-26 17:59:03 - Failed login for 'admin'. Reason: Password mismatch
2026-03-17 10:12:37 - Failed login for 'moosa'. Reason: Password mismatch
2026-05-02 04:46:38 - Failed login for 'moosa'. Reason: Password mismatch
2026-05-02 04:46:47 - Failed login for 'admin'. Reason: Password mismatch

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,152 @@
<script>
// Avery Label Logic
const selectAllItems = document.getElementById('selectAllItems');
const bulkBarcodeBtn = document.getElementById('bulkBarcodeBtn');
if (selectAllItems) {
selectAllItems.addEventListener('change', function() {
document.querySelectorAll('.item-checkbox').forEach(cb => {
cb.checked = this.checked;
});
toggleBulkBtn();
});
}
document.addEventListener('change', function(e) {
if (e.target.classList.contains('item-checkbox')) {
toggleBulkBtn();
}
});
function toggleBulkBtn() {
const checked = document.querySelectorAll('.item-checkbox:checked').length;
if (bulkBarcodeBtn) {
bulkBarcodeBtn.style.display = checked > 0 ? 'inline-block' : 'none';
}
}
window.openAveryModal = function() {
const modal = new bootstrap.Modal(document.getElementById('averyLabelsModal'));
const checkedItems = document.querySelectorAll('.item-checkbox:checked');
const container = document.getElementById('averyItemQuantities');
const defaultCopies = parseInt(document.getElementById('averyCopies').value) || 1;
if (container) {
container.innerHTML = '';
if (checkedItems.length === 0) {
container.innerHTML = '<small class="text-muted" data-en="No items selected." data-ar="لم يتم تحديد أي صنف.">No items selected.</small>';
} else {
const table = document.createElement('table');
table.className = 'table table-sm table-borderless mb-0';
const tbody = document.createElement('tbody');
checkedItems.forEach(cb => {
const sku = cb.dataset.sku;
const name = cb.dataset.name;
const id = cb.dataset.id;
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="align-middle" style="width: 70%; font-size: 0.9em;">${name} <span class="text-muted">(${sku})</span></td>
<td class="align-middle" style="width: 30%;">
<input type="number" class="form-control form-control-sm item-qty-input"
data-id="${id}" value="${defaultCopies}" min="0" onchange="updateAveryPreview()" onkeyup="updateAveryPreview()">
</td>
`;
tbody.appendChild(tr);
});
table.appendChild(tbody);
container.appendChild(table);
}
}
modal.show();
updateAveryPreview();
};
window.updateAllItemQuantities = function() {
const globalQty = document.getElementById('averyCopies').value;
const itemInputs = document.querySelectorAll('.item-qty-input');
itemInputs.forEach(input => {
input.value = globalQty;
});
updateAveryPreview();
};
window.updateAveryPreview = function() {
const layout = document.getElementById('averyLayout').value;
const container = document.getElementById('averyPrintArea');
const checkedItems = document.querySelectorAll('.item-checkbox:checked');
container.className = 'avery-container avery-layout-' + layout;
container.innerHTML = '';
checkedItems.forEach(cb => {
const sku = cb.dataset.sku;
const nameAr = cb.dataset.nameAr || '';
const nameEn = cb.dataset.nameEn || '';
const name = cb.dataset.name;
const price = cb.dataset.price;
const id = cb.dataset.id;
// Find specific quantity input
const qtyInput = document.querySelector(`.item-qty-input[data-id="${id}"]`);
let copies = 1;
if (qtyInput) {
copies = parseInt(qtyInput.value) || 0;
} else {
copies = parseInt(document.getElementById('averyCopies').value) || 1;
}
for (let i = 0; i < copies; i++) {
const label = document.createElement('div');
label.className = 'avery-label';
const uniqueId = Math.random().toString(36).substr(2, 9);
const svgId = `bc-${sku}-${uniqueId}`;
let nameHtml = '';
if (nameAr || nameEn) {
const arText = nameAr || name;
const enText = nameEn || '';
nameHtml = `<div style="font-size: 10px; font-weight: bold; direction: rtl; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.1;">${arText}</div>
<div style="font-size: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.1;">${enText}</div>`;
} else {
nameHtml = `<div style="font-size: 9px; font-weight: bold; margin-bottom: 2px; line-height: 1.1; overflow: hidden; text-overflow: ellipsis; width: 100%;">${name}</div>`;
}
label.innerHTML = `
${nameHtml}
<svg id="${svgId}"></svg>
<div style="font-size: 11px; font-weight: bold; margin-top: 2px;">OMR ${price}</div>
`;
container.appendChild(label);
setTimeout(() => {
if (document.getElementById(svgId)) {
const bcHeight = layout === 'L7651' ? 20 : 35;
JsBarcode(`#${svgId}`, sku, {
format: "CODE128",
width: layout === 'L7651' ? 1.0 : 1.2,
height: bcHeight,
displayValue: true,
fontSize: layout === 'L7651' ? 8 : 10,
margin: 0
});
}
}, 0);
}
});
};
window.addEventListener('beforeprint', () => {
if (document.getElementById('averyLabelsModal') && document.getElementById('averyLabelsModal').classList.contains('show')) {
document.body.classList.add('printing-avery');
}
});
window.addEventListener('afterprint', () => {
document.body.classList.remove('printing-avery');
});
</script>

View File

@ -0,0 +1,196 @@
window.printItemBarcode = function(sku, nameAr, nameEn, price) {
if (!sku) {
Swal.fire('Error', 'This item has no SKU/Barcode assigned.', 'error');
return;
}
document.getElementById('barcodeLabelName').innerHTML = '<div style="font-weight:bold; font-size:12px; direction:rtl; margin-bottom:2px;">' + nameAr + '</div><div style="font-size:10px;">' + nameEn + '</div>';
document.getElementById('barcodeLabelPrice').textContent = 'OMR ' + price;
JsBarcode("#barcodeSvg", sku, {
format: "CODE128",
lineColor: "#000",
width: 2,
height: 50,
displayValue: true
});
const modal = new bootstrap.Modal(document.getElementById('barcodePrintModal'));
modal.show();
};
window.executeBarcodePrint = function() {
const qty = parseInt(document.getElementById('barcodeQty').value) || 1;
const width = parseInt(document.getElementById('barcodeWidth').value) || 40;
const height = parseInt(document.getElementById('barcodeHeight').value) || 25;
// Get content
const nameHtml = document.getElementById('barcodeLabelName').innerHTML;
const price = document.getElementById('barcodeLabelPrice').innerText;
const svg = document.getElementById('barcodeSvg').outerHTML;
// Create a hidden iframe
const iframe = document.createElement('iframe');
iframe.style.position = 'absolute';
iframe.style.width = '0px';
iframe.style.height = '0px';
iframe.style.border = 'none';
document.body.appendChild(iframe);
const doc = iframe.contentWindow.document;
let labelsHtml = '';
for (let i = 0; i < qty; i++) {
labelsHtml += `
<div class="label-container">
<div class="label-name" style="height: auto; overflow: visible;">${nameHtml}</div>
${svg}
<div class="label-price">${price}</div>
</div>
`;
}
doc.open();
doc.write(`
<html>
<head>
<style>
@page { size: ${width}mm ${height}mm; margin: 0; }
body { margin: 0; padding: 0; font-family: sans-serif; }
.label-container {
width: ${width}mm;
height: ${height}mm;
page-break-after: always;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
overflow: hidden;
box-sizing: border-box;
padding: 1mm;
}
.label-container:last-child { page-break-after: avoid; }
.label-name { font-weight: bold; margin-bottom: 2px; line-height: 1.1; max-width: 100%; }
.label-price { font-size: 12px; font-weight: bold; margin-top: 2px; }
svg { max-width: 100%; height: auto; max-height: 70%; display: block; }
</style>
</head>
<body>
${labelsHtml}
</body>
</html>
`);
doc.close();
iframe.contentWindow.focus();
setTimeout(() => {
iframe.contentWindow.print();
setTimeout(() => {
document.body.removeChild(iframe);
}, 2000);
}, 500);
};
<?php require 'pages/sales_purchases_print_script.php'; ?>
window.printPosReceiptFromInvoice = function(inv) {
const container = document.getElementById('posReceiptContent');
const itemsHtml = inv.items.map(item => {
const itemTotal = item.unit_price * item.quantity;
const vatRate = parseFloat(item.vat_rate !== undefined && item.vat_rate !== null ? item.vat_rate : 0);
const vatAmount = itemTotal * (vatRate / (100 + vatRate));
return `
<tr>
<td>${item.name_en} / ${item.name_ar}<br><small>${item.quantity} x ${parseFloat(item.unit_price).toFixed(3)}</small></td>
<td style="text-align: right; vertical-align: bottom;">${vatAmount.toFixed(2)}</td>
<td style="text-align: right; vertical-align: bottom;">${itemTotal.toFixed(3)}</td>
</tr>
`;
}).join('');
const totalVat = inv.items.reduce((sum, item) => {
const itemTotal = item.unit_price * item.quantity;
const vatRate = parseFloat(item.vat_rate !== undefined && item.vat_rate !== null ? item.vat_rate : 0);
return sum + (itemTotal * (vatRate / (100 + vatRate)));
}, 0);
const subtotal = inv.items.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0);
const companyName = "<?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>";
const outletName = "<?= htmlspecialchars($data['settings']['current_outlet_name'] ?? '') ?>";
const companyPhone = "<?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>";
const companyVat = "<?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?>";
const companyLogo = "<?= htmlspecialchars($data['settings']['company_logo'] ?? '') ?>";
container.innerHTML = `
<div class="thermal-receipt">
<div class="center">
${companyLogo ? `<img src="${companyLogo}" alt="Logo" style="max-height: 60px; width: auto; margin-bottom: 10px; display: block; margin-left: auto; margin-right: auto;">` : ''}
<h5 class="mb-0 fw-bold">${companyName}</h5>
${inv.outlet_name ? `<div class="fw-bold text-uppercase">${inv.outlet_name}</div>` : ''}
${companyPhone ? `<div>Tel: ${companyPhone}</div>` : ''}
${companyVat ? `<div>VAT: ${companyVat}</div>` : ''}
<div class="separator"></div>
<h6 class="fw-bold">TAX INVOICE / فاتورة ضريبية</h6>
<div>Inv / رقم: INV-${inv.id.toString().padStart(5, '0')}</div>
<div>Date / التاريخ: ${inv.invoice_date}</div>
<div class="separator"></div>
</div>
<div>
<strong>Customer / العميل:</strong> ${inv.customer_name || 'Walk-in / عميل عابر'}
</div>
<div class="separator"></div>
<table>
<thead>
<tr>
<th>ITEM / الصنف</th>
<th style="text-align: right;">VAT / الضريبة</th>
<th style="text-align: right;">TOTAL / الإجمالي</th>
</tr>
</thead>
<tbody>
${itemsHtml}
</tbody>
</table>
<div class="separator"></div>
<div class="d-flex justify-content-between small">
<span>Subtotal (Excl. VAT) / المجموع الفرعي (دون الضريبة)</span>
<span><?= __('currency') ?> ${(subtotal - totalVat).toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small">
<span>VAT / الضريبة</span>
<span><?= __('currency') ?> ${totalVat.toFixed(2)}</span>
</div>
<div class="total-row d-flex justify-content-between">
<span>TOTAL (Incl. VAT) / الإجمالي (شامل الضريبة)</span>
<span><?= __('currency') ?> ${subtotal.toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small">
<span>PAID / المدفوع</span>
<span><?= __('currency') ?> ${parseFloat(inv.paid_amount).toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small fw-bold">
<span>BALANCE / الرصيد</span>
<span><?= __('currency') ?> ${(subtotal - inv.paid_amount).toFixed(3)}</span>
</div>
<div class="separator"></div>
<div class="center small">
<p>Thank You for your business! / شكراً لتعاملكم معنا!</p>
</div>
</div>
`;
const posModal = new bootstrap.Modal(document.getElementById('posReceiptModal'));
posModal.show();
};
function printPosReceipt() {
const content = document.getElementById('posReceiptContent').innerHTML;
const printArea = document.getElementById('posPrintArea');
printArea.innerHTML = `<div class="thermal-receipt thermal-receipt-print">${content}</div>`;
document.body.classList.add('printing-receipt');
window.print();
document.body.classList.remove('printing-receipt');
location.reload();
}

View File

@ -0,0 +1,110 @@
// --- Language Apply Script ---
function applyLanguage(node) {
const docLang = document.documentElement.lang || 'ar';
const targetAttr = docLang === 'ar' ? 'data-ar' : 'data-en';
let elements = [];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute(targetAttr)) {
elements.push(node);
}
node.querySelectorAll('[' + targetAttr + ']').forEach(el => elements.push(el));
}
elements.forEach(el => {
const text = el.getAttribute(targetAttr);
if (!text) return;
if (el.hasAttribute('placeholder')) {
el.setAttribute('placeholder', text);
}
if (el.hasAttribute('title')) {
el.setAttribute('title', text);
}
let textNodes = [];
el.childNodes.forEach(child => {
if (child.nodeType === Node.TEXT_NODE && child.nodeValue.trim() !== '') {
textNodes.push(child);
}
});
if (textNodes.length > 0) {
textNodes[0].nodeValue = text;
for (let i = 1; i < textNodes.length; i++) {
textNodes[i].nodeValue = '';
}
} else if (el.children.length === 1 && ['STRONG', 'B', 'SPAN', 'SMALL'].includes(el.children[0].tagName)) {
el.children[0].textContent = text;
} else if (el.children.length === 0) {
el.textContent = text;
}
});
}
document.addEventListener('DOMContentLoaded', function() {
applyLanguage(document.body);
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
applyLanguage(node);
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
});
// -----------------------------
<?php if ($page === 'dashboard' && can('dashboard_view')): ?>
const monthlyData = <?= json_encode($data['monthly_sales']) ?>;
const yearlyData = <?= json_encode($data['yearly_sales']) ?>;
const ctx = document.getElementById('salesChart').getContext('2d');
let salesChart = new Chart(ctx, {
type: 'line',
data: {
labels: monthlyData.map(d => d.label),
datasets: [{
label: 'Sales (OMR)',
data: monthlyData.map(d => d.total),
borderColor: '#0d6efd',
backgroundColor: 'rgba(13, 110, 253, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) { return 'OMR ' + value.toFixed(3); }
}
}
}
}
});
document.getElementById('btnMonthly').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('btnYearly').classList.remove('active');
salesChart.data.labels = monthlyData.map(d => d.label);
salesChart.data.datasets[0].data = monthlyData.map(d => d.total);
salesChart.update();
});
document.getElementById('btnYearly').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('btnMonthly').classList.remove('active');
salesChart.data.labels = yearlyData.map(d => d.label);
salesChart.data.datasets[0].data = yearlyData.map(d => d.total);
salesChart.update();
});
<?php endif; ?>

View File

@ -0,0 +1,379 @@
// LPO Form Logic
initInvoiceForm('lpoProductSearchInput', 'lpoSearchSuggestions', 'lpoItemsTableBody', 'lpo_grand_display', 'lpo_subtotal_display', 'lpo_vat_display');
initInvoiceForm('editLpoProductSearchInput', 'editLpoSearchSuggestions', 'editLpoItemsTableBody', 'edit_lpo_grand_display', 'edit_lpo_subtotal_display', 'edit_lpo_vat_display');
document.querySelectorAll('.edit-lpo-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('edit_lpo_id').value = data.id;
const supplierSelect = document.getElementById('edit_lpo_supplier_id');
supplierSelect.value = data.supplier_id;
if (window.jQuery && $(supplierSelect).data('select2')) {
$(supplierSelect).trigger('change');
}
document.getElementById('edit_lpo_date').value = data.lpo_date;
document.getElementById('edit_lpo_delivery_date').value = data.delivery_date || '';
document.getElementById('edit_lpo_status').value = data.status || 'pending';
document.getElementById('edit_lpo_terms').value = data.terms_conditions || '';
const tableBody = document.getElementById('editLpoItemsTableBody');
tableBody.innerHTML = '';
data.items.forEach(item => {
addItemToTable({
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '',
vat_rate: item.vat_rate || 0
}, tableBody, null, null,
document.getElementById('edit_lpo_grand_display'),
document.getElementById('edit_lpo_subtotal_display'),
document.getElementById('edit_lpo_vat_display'),
{ quantity: item.quantity, unit_price: item.unit_price });
});
});
});
document.querySelectorAll('.view-lpo-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
window.viewAndPrintLPO(data);
});
});
window.viewAndPrintLPO = function(data) {
const modal = new bootstrap.Modal(document.getElementById('viewLpoModal'));
const content = document.getElementById('lpoDetailsContent');
const logoUrl = companySettings.company_logo || '';
const companyHeader = `
<div class="row align-items-center mb-4">
<div class="col-6">
${logoUrl ? `<img src="${logoUrl}" alt="Logo" style="max-height: 80px;" class="mb-3">` : ''}
<h4 class="fw-bold mb-0">${companySettings.company_name || 'Your Company'}</h4>
<p class="text-muted mb-0 small">
${companySettings.company_address || ''}<br>
Phone: ${companySettings.company_phone || ''} | Email: ${companySettings.company_email || ''}
${companySettings.tax_number ? `<br>TRN: ${companySettings.tax_number}` : ''}
</p>
</div>
<div class="col-6 text-end">
<h2 class="text-primary fw-bold mb-1">LOCAL PURCHASE ORDER</h2>
<p class="h5 mb-0 text-muted">LPO-${data.id.toString().padStart(5, '0')}</p>
</div>
</div>
`;
let itemsHtml = '';
data.items.forEach((item, index) => {
itemsHtml += `
<tr>
<td>${index + 1}</td>
<td>${item.name_en}<br><small class="text-muted">${item.name_ar}</small></td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-center">${parseFloat(item.vat_rate || 0).toFixed(2)}%</td>
<td class="text-end">${parseFloat(item.total_amount).toFixed(3)}</td>
</tr>
`;
});
content.innerHTML = `
${companyHeader}
<hr>
<div class="row mb-4">
<div class="col-6">
<h6 class="text-uppercase text-muted fw-bold mb-3" data-en="Supplier" data-ar="المورد">Supplier</h6>
<p class="h6 mb-1 fw-bold">${data.supplier_name}</p>
<p class="small text-muted mb-0">
${data.supplier_phone ? `Phone: ${data.supplier_phone}` : ''}
</p>
</div>
<div class="col-6 text-end">
<h6 class="text-uppercase text-muted fw-bold mb-3" data-en="Details" data-ar="تفاصيل">Details</h6>
<div class="d-flex justify-content-end mb-1">
<span class="text-muted me-2">Date:</span>
<span class="fw-bold">${data.lpo_date}</span>
</div>
<div class="d-flex justify-content-end mb-1">
<span class="text-muted me-2">Delivery:</span>
<span class="fw-bold">${data.delivery_date || '---'}</span>
</div>
<div class="d-flex justify-content-end">
<span class="text-muted me-2">Status:</span>
<span class="badge ${data.status === 'pending' ? 'bg-warning text-dark' : 'bg-success'}">${data.status.toUpperCase()}</span>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead class="table-dark">
<tr>
<th style="width: 5%">#</th>
<th style="width: 45%" data-en="Description" data-ar="الوصف">Description</th>
<th style="width: 10%" class="text-center" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%" class="text-end">Unit Price</th>
<th style="width: 10%" class="text-center">VAT</th>
<th style="width: 15%" class="text-end" data-en="Total" data-ar="الإجمالي">Total</th>
</tr>
</thead>
<tbody>${itemsHtml}</tbody>
<tfoot class="table-light">
<tr>
<td colspan="5" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold">OMR ${parseFloat(data.total_amount).toFixed(3)}</td>
</tr>
<tr>
<td colspan="5" class="text-end fw-bold">VAT Amount</td>
<td class="text-end fw-bold">OMR ${parseFloat(data.vat_amount).toFixed(2)}</td>
</tr>
<tr class="table-primary">
<td colspan="5" class="text-end fw-bold h5">Grand Total</td>
<td class="text-end fw-bold h5">OMR ${parseFloat(data.total_with_vat).toFixed(3)}</td>
</tr>
</tfoot>
</table>
</div>
${data.terms_conditions ? `
<div class="card bg-light border-0 mt-4">
<div class="card-body p-3">
<h6 class="fw-bold mb-2 small text-uppercase text-muted">Terms & Conditions</h6>
<p class="small mb-0">${data.terms_conditions.replace(/\n/g, '<br>')}</p>
</div>
</div>` : ''}
<div class="row mt-5 pt-3">
<div class="col-4 text-center">
<div style="border-top: 1px solid #dee2e6; padding-top: 10px;">
<p class="small mb-0">Prepared By</p>
</div>
</div>
<div class="col-4"></div>
<div class="col-4 text-center">
<div style="border-top: 1px solid #dee2e6; padding-top: 10px;">
<p class="small mb-0">Authorized Signature</p>
</div>
</div>
</div>
`;
window.printLPO = function() {
const printWindow = window.open('', '_blank');
printWindow.document.write('<html><head><title>LPO-' + data.id + '</title>');
printWindow.document.write('<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap<?= $dir === 'rtl' ? '.rtl' : '' ?>.min.css" rel="stylesheet">');
printWindow.document.write('<style>body { padding: 40px; } @media print { .no-print { display: none; } }</style>');
printWindow.document.write('</head><body>');
printWindow.document.write(content.innerHTML);
printWindow.document.write('</body></html>');
printWindow.document.close();
setTimeout(() => {
printWindow.print();
printWindow.close();
}, 500);
};
modal.show();
};
// Quotation Form Logic
initInvoiceForm('quotProductSearchInput', 'quotSearchSuggestions', 'quotItemsTableBody', 'quot_grand_display', 'quot_subtotal_display', 'quot_vat_display');
initInvoiceForm('editQuotProductSearchInput', 'editQuotSearchSuggestions', 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display');
document.querySelectorAll('.edit-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('edit_quotation_id').value = data.id;
document.getElementById('edit_quot_customer_id').value = data.customer_id;
document.getElementById('edit_quot_date').value = data.quotation_date;
document.getElementById('edit_quot_valid').value = data.valid_until || '';
document.getElementById('edit_quot_status').value = data.status || 'pending';
const tableBody = document.getElementById('editQuotItemsTableBody');
tableBody.innerHTML = '';
data.items.forEach(item => {
addItemToTable({
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '',
vat_rate: item.vat_rate || 0
}, tableBody, null, null,
document.getElementById('edit_quot_grand_display'),
document.getElementById('edit_quot_subtotal_display'),
document.getElementById('edit_quot_vat_display'),
{ quantity: item.quantity, unit_price: item.unit_price });
});
});
});
document.querySelectorAll('.convert-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (confirm('Convert this quotation to an invoice? This will reduce stock.')) {
const f = document.createElement('form');
f.method = 'POST';
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${this.dataset.id}">`;
document.body.appendChild(f);
f.submit();
}
});
});
// View Quotation Logic
window.viewAndPrintQuotation = function(data, autoPrint = false) {
const modal = new bootstrap.Modal(document.getElementById('viewQuotationModal'));
const content = document.getElementById('quotationPrintableArea');
let itemsHtml = '';
data.items.forEach((item, index) => {
itemsHtml += `
<tr>
<td>${index + 1}</td>
<td>${item.name_en}<br><small>${item.name_ar}</small></td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-center">${parseFloat(item.vat_rate || 0).toFixed(2)}%</td>
<td class="text-end">${parseFloat(item.total_price).toFixed(3)}</td>
</tr>
`;
});
// Company Logo and Header Construction
const logoUrl = companySettings.company_logo || '';
const logoImg = logoUrl ? `<img src="${logoUrl}" alt="Logo" class="invoice-logo mb-3">` : '';
const companyName = companySettings.company_name || 'Accounting System';
const companyAddress = (companySettings.company_address || '').replace(/\n/g, '<br>');
const companyVat = companySettings.vat_number ? `<p class="text-muted small mb-0">VAT: ${companySettings.vat_number}</p>` : '';
const companyPhone = companySettings.company_phone ? `<p class="text-muted small mb-0">Tel: ${companySettings.company_phone}</p>` : '';
// Quotation Header Construction
const quotDate = data.quotation_date;
const quotValid = data.valid_until || 'N/A';
const quotNo = 'QUO-' + data.id.toString().padStart(5, '0');
const customerName = data.customer_name || 'Walk-in Customer';
const statusBadge = `<span class="badge ${data.status === 'converted' ? 'bg-success' : 'bg-secondary'}">${data.status.toUpperCase()}</span>`;
content.innerHTML = `
<div class="p-5">
<div class="invoice-header mb-4">
<div class="row align-items-center">
<div class="col-6">
${logoImg}
<h3 class="mb-1 fw-bold">${companyName}</h3>
<p class="text-muted small mb-0">${companyAddress}</p>
${companyVat}
${companyPhone}
</div>
<div class="col-6 text-end">
<h1 class="invoice-title fw-bold mb-0 text-uppercase">Quotation / عرض سعر</h1>
<div class="mt-2">${statusBadge}</div>
<div class="mt-3">
<p class="mb-0 fs-5">No / رقم: <strong class="text-primary">${quotNo}</strong></p>
<p class="mb-0">Date / التاريخ: <span class="fw-bold">${quotDate}</span></p>
<p class="mb-0">Valid Until / صالح لغاية: <span class="fw-bold">${quotValid}</span></p>
</div>
</div>
</div>
</div>
<div class="row mb-4 g-3">
<div class="col-6">
<div class="invoice-info-card">
<p class="text-muted small text-uppercase fw-bold mb-2 border-bottom pb-1">To / إلى</p>
<h5 class="mb-1 fw-bold">${customerName}</h5>
</div>
</div>
</div>
<table class="table table-bordered table-formal">
<thead class="bg-dark text-white">
<tr>
<th>#</th>
<th>Item Description / وصف الصنف</th>
<th class="text-center">Qty / الكمية</th>
<th class="text-end">Unit Price / سعر الوحدة</th>
<th class="text-center">VAT / الضريبة</th>
<th class="text-end">Total / الإجمالي</th>
</tr>
</thead>
<tbody>${itemsHtml}</tbody>
<tfoot>
<tr>
<th colspan="5" class="text-end">Subtotal / المجموع الفرعي</th>
<td class="text-end fw-bold">${parseFloat(data.total_amount).toFixed(3)}</td>
</tr>
<tr>
<th colspan="5" class="text-end">VAT Amount / مبلغ الضريبة</th>
<td class="text-end fw-bold">${parseFloat(data.vat_amount).toFixed(2)}</td>
</tr>
<tr class="table-primary">
<th colspan="5" class="text-end h5">Grand Total (OMR) / المجموع الكلي (رع)</th>
<td class="text-end h5 fw-bold">${parseFloat(data.total_with_vat).toFixed(3)}</td>
</tr>
</tfoot>
</table>
<div class="mt-5 pt-3 border-top">
<div class="row">
<div class="col-6">
<p class="small text-muted text-uppercase fw-bold">Terms & Conditions / الشروط والأحكام:</p>
<ul class="small text-muted">
<li>Quotation is valid until the date mentioned above. / عرض السعر صالح لغاية التاريخ المذكور أعلاه.</li>
<li>Prices are inclusive of VAT where applicable. / الأسعار تشمل ضريبة القيمة المضافة حيثما ينطبق ذلك.</li>
</ul>
</div>
<div class="col-6 text-end pt-4">
<div class="border-top d-inline-block px-5">Authorized Signature / التوقيع المعتمد</div>
</div>
</div>
</div>
<div class="mt-4 text-center">
<p class="text-muted x-small mb-0">Generated by / تم إنشاؤه بواسطة ${companyName}</p>
</div>
</div>
`;
const actionButtons = document.getElementById('quotationActionButtons');
actionButtons.innerHTML = '';
if (data.status === 'pending') {
const convertBtn = document.createElement('button');
convertBtn.className = 'btn btn-success me-2';
convertBtn.innerHTML = '<i class="bi bi-receipt"></i> <span data-en="Convert to Invoice" data-ar="تحويل إلى فاتورة">Convert to Invoice</span>';
convertBtn.onclick = function() {
if (confirm('Convert this quotation to an invoice?')) {
const f = document.createElement('form');
f.method = 'POST';
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${data.id}">`;
document.body.appendChild(f);
f.submit();
}
};
actionButtons.appendChild(convertBtn);
const editBtn = document.createElement('button');
editBtn.className = 'btn btn-primary';
editBtn.innerHTML = '<i class="bi bi-pencil-square"></i> <span data-en="Edit" data-ar="تعديل">Edit</span>';
editBtn.onclick = function() {
const editModal = new bootstrap.Modal(document.getElementById('editQuotationModal'));
modal.hide();
const originalEditBtn = document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id},']`) ||
document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id}']`);
if (originalEditBtn) originalEditBtn.click();
};
actionButtons.appendChild(editBtn);
}
modal.show();
if (autoPrint) {
setTimeout(() => { window.print(); }, 500);
}
};
document.querySelectorAll('.view-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
window.viewAndPrintQuotation(data, false);
});
});

View File

@ -0,0 +1,20 @@
<script>
function loadSessionReport(id) {
var modalEl = document.getElementById('universalSessionReportModal');
var modal = bootstrap.Modal.getOrCreateInstance(modalEl);
// Reset content
document.getElementById('sessionReportContent').innerHTML = '<div class="modal-body text-center p-5"><div class="spinner-border text-primary"></div></div>';
modal.show();
fetch('ajax_session_report.php?id=' + id)
.then(response => response.text())
.then(html => {
document.getElementById('sessionReportContent').innerHTML = html;
})
.catch(() => {
document.getElementById('sessionReportContent').innerHTML = '<div class="modal-header"><h5 class="modal-title">Error</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body text-danger">Failed to load report.</div>';
});
}
</script>

View File

@ -0,0 +1,223 @@
// Edit Invoice Logic
document.querySelectorAll('.edit-invoice-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('edit_invoice_id').value = data.id;
document.getElementById('edit_customer_id').value = data.customer_id;
document.getElementById('edit_invoice_date').value = data.invoice_date;
document.getElementById('edit_due_date').value = data.due_date || '';
document.getElementById('edit_payment_type').value = data.payment_type || 'cash';
document.getElementById('edit_status').value = data.status || 'unpaid';
document.getElementById('edit_paid_amount').value = parseFloat(data.paid_amount || 0).toFixed(3);
if (data.status === 'partially_paid') {
document.getElementById('editPaidAmountContainer').style.display = 'block';
} else {
document.getElementById('editPaidAmountContainer').style.display = 'none';
}
const tableBody = document.getElementById('editInvoiceItemsTableBody');
tableBody.innerHTML = '';
data.items.forEach(item => {
// We need more data than what's in invoice_items (like SKU and names, but we have them from the join in PHP)
// The dataset-json already contains name_en, name_ar etc because of the PHP logic at line 1093
const itemMeta = {
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '', // Optional, or fetch if needed
vat_rate: 0 // Will be handled if we have it in the join
};
// Fetch current item details to get VAT rate if possible, or use stored if available
// For simplicity, let's assume we want to use the item's current VAT rate or store it.
// Looking at the join at line 1093, it doesn't fetch vat_rate. Let's fix that in PHP too.
addItemToTable({
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '',
vat_rate: item.vat_rate || 0 // We'll add this to PHP join
}, tableBody, null, null,
document.getElementById('edit_grandTotal'),
document.getElementById('edit_subtotal'),
document.getElementById('edit_totalVat'),
{ quantity: item.quantity, unit_price: item.unit_price });
});
});
});
// View and Print Invoice Logic
document.addEventListener('click', function(e) {
if (e.target.closest('.view-invoice-btn')) {
const btn = e.target.closest('.view-invoice-btn');
const data = JSON.parse(btn.dataset.json);
if (window.viewAndPrintA4Invoice) {
window.viewAndPrintA4Invoice(data, false);
}
}
if (e.target.closest('.print-a4-btn')) {
const btn = e.target.closest('.print-a4-btn');
const data = JSON.parse(btn.dataset.json);
if (window.viewAndPrintA4Invoice) {
window.viewAndPrintA4Invoice(data, true);
}
}
});
// Return Logic (General for Sales and Purchase)
const setupReturnLogic = (selectId, containerId, tbodyId, totalDisplayId, submitBtnId, type = "sale") => {
const select = document.getElementById(selectId);
if (!select) return;
const calculateTotal = function() {
let total = 0;
document.querySelectorAll('#' + tbodyId + ' tr').forEach(row => {
const qtyInput = row.querySelector('.return-qty-input');
if (!qtyInput) return;
const qty = parseFloat(qtyInput.value) || 0;
const price = parseFloat(qtyInput.dataset.price) || 0;
const lineTotal = qty * price;
const lineTotalDisplay = row.querySelector('.line-total');
if (lineTotalDisplay) {
lineTotalDisplay.innerText = lineTotal.toFixed(3);
}
total += lineTotal;
});
const totalDisplay = document.getElementById(totalDisplayId);
if (totalDisplay) {
totalDisplay.innerText = 'OMR ' + total.toFixed(3);
}
const submitBtn = document.getElementById(submitBtnId);
if (submitBtn) {
submitBtn.disabled = total <= 0;
}
};
const handleInvoiceChange = async function() {
const invoiceId = select.value;
const container = document.getElementById(containerId);
const tbody = document.getElementById(tbodyId);
const submitBtn = document.getElementById(submitBtnId);
if (!invoiceId) {
if (container) container.style.display = 'none';
if (submitBtn) submitBtn.disabled = true;
return;
}
if (tbody) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center"><div class="spinner-border spinner-border-sm text-primary"></div> <span data-en="Loading items..." data-ar="جاري تحميل الأصناف...">Loading items...</span></td></tr>';
}
if (container) container.style.display = 'block';
try {
const resp = await fetch(`index.php?action=get_invoice_items&invoice_id=${invoiceId}&type=${type}`);
const items = await resp.json();
if (tbody) {
tbody.innerHTML = '';
if (items.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted" data-en="No items found for this invoice." data-ar="لا توجد أصناف لهذه الفاتورة.">No items found for this invoice.</td></tr>';
} else {
let html = '';
items.forEach(item => {
html += `
<tr>
<td>${item.name_en}<br><small class="text-muted">${item.sku}</small></td>
<td>${parseFloat(item.quantity).toFixed(2)}</td>
<td>
<input type="number" name="quantities[]" class="form-control form-control-sm return-qty-input"
step="0.01" min="0" max="${item.quantity}" value="0"
data-price="${item.unit_price}">
<input type="hidden" name="item_ids[]" value="${item.item_id}">
<input type="hidden" name="prices[]" value="${item.unit_price}">
</td>
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end line-total">0.000</td>
</tr>
`;
});
tbody.innerHTML = html;
}
}
if (submitBtn) submitBtn.disabled = true;
const qtyInputs = tbody.querySelectorAll('.return-qty-input');
qtyInputs.forEach(input => {
['input', 'change', 'keyup'].forEach(evt => input.addEventListener(evt, calculateTotal));
});
calculateTotal();
} catch (e) {
console.error(e);
if (window.Swal) Swal.fire('Error', 'Failed to fetch invoice items', 'error');
}
};
select.addEventListener('change', handleInvoiceChange);
if (window.jQuery && jQuery.fn.select2) {
$(select).on('select2:select change', handleInvoiceChange);
}
};
setupReturnLogic('return_invoice_select', 'return_items_container', 'return_items_tbody', 'return_total_display', 'submit_return_btn', 'sale');
setupReturnLogic('purchase_return_invoice_select', 'purchase_return_items_container', 'purchase_return_items_tbody', 'purchase_return_total_display', 'purchase_submit_return_btn', 'purchase');
// Return Invoice Button from Sales/Purchases list
document.querySelectorAll('.return-invoice-btn').forEach(btn => {
btn.addEventListener('click', function() {
const invoiceId = this.dataset.id;
const targetModal = this.dataset.bsTarget;
const selectId = targetModal === '#addSalesReturnModal' ? 'return_invoice_select' : 'purchase_return_invoice_select';
const select = document.getElementById(selectId);
if (select) {
$(select).val(invoiceId).trigger('change');
}
});
});
// View Return Logic
document.querySelectorAll('.view-return-btn').forEach(btn => {
btn.addEventListener('click', async function() {
const returnId = this.dataset.id;
const type = '<?= $page === "purchase_returns" ? "purchase" : "sale" ?>';
const modal = new bootstrap.Modal(document.getElementById('viewReturnDetailsModal'));
try {
const resp = await fetch(`index.php?action=get_return_details&return_id=${returnId}&type=${type}`);
const data = await resp.json();
if (data) {
document.getElementById('view_return_no').innerText = (type === 'purchase' ? 'PRET-' : 'SRET-') + String(data.id).padStart(5, '0');
document.getElementById('view_return_party').innerText = data.party_name;
document.getElementById('view_return_date').innerText = data.return_date;
const refId = data.purchase_id || data.invoice_id;
const refPrefix = type === 'purchase' ? 'PUR-' : 'INV-';
document.getElementById('view_return_invoice').innerText = refPrefix + String(refId).padStart(5, '0');
document.getElementById('view_return_total').innerText = 'OMR ' + parseFloat(data.total_amount).toFixed(3);
document.getElementById('view_return_notes').innerText = data.notes || 'No notes';
let itemsHtml = '';
data.items.forEach(item => {
itemsHtml += `
<tr>
<td>${item.name_en}<br><small class="text-muted">${item.sku}</small></td>
<td class="text-center">${parseFloat(item.quantity).toFixed(2)}</td>
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end">${parseFloat(item.total_price).toFixed(3)}</td>
</tr>
`;
});
document.getElementById('view_return_items_tbody').innerHTML = itemsHtml;
modal.show();
}
} catch (e) {
console.error(e);
Swal.fire('Error', 'Failed to fetch return details', 'error');
}
});
});

View File

@ -0,0 +1,155 @@
// Invoice Form Logic
const initInvoiceForm = (searchInputId, suggestionsId, tableBodyId, grandTotalId, subtotalId, totalVatId) => {
const searchInput = document.getElementById(searchInputId);
const suggestions = document.getElementById(suggestionsId);
const tableBody = document.getElementById(tableBodyId);
const grandTotalEl = document.getElementById(grandTotalId);
const subtotalEl = document.getElementById(subtotalId);
const totalVatEl = document.getElementById(totalVatId);
if (!searchInput || !tableBody) return;
let timeout = null;
searchInput.addEventListener('input', function() {
clearTimeout(timeout);
const q = this.value.trim();
if (q.length < 2) {
suggestions.style.display = 'none';
return;
}
timeout = setTimeout(() => {
fetch(`index.php?action=search_items&q=${encodeURIComponent(q)}`)
.then(res => res.ok ? res.json() : [])
.then(data => {
suggestions.innerHTML = '';
if (data.length > 0) {
data.forEach(item => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'list-group-item list-group-item-action';
btn.innerHTML = `
<div class="d-flex justify-content-between">
<span><strong>${item.sku}</strong> - ${item.name_en} / ${item.name_ar}</span>
<span class="text-muted small">Stock: ${item.stock_quantity}</span>
</div>
`;
btn.onclick = () => addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl);
suggestions.appendChild(btn);
});
suggestions.style.display = 'block';
} else {
suggestions.style.display = 'none';
}
});
}, 300);
});
// Close suggestions when clicking outside
document.addEventListener('click', function(e) {
if (!searchInput.contains(e.target) && !suggestions.contains(e.target)) {
suggestions.style.display = 'none';
}
});
};
function addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl, customData = null) {
if (suggestions) suggestions.style.display = 'none';
if (searchInput) searchInput.value = '';
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
const currentStock = parseFloat(item.stock_quantity) || 0;
if (invoiceType === 'sale' && !allowZeroStock && !customData) {
const existingInTable = Array.from(tableBody.querySelectorAll('.item-row')).find(row => row.querySelector('.item-id-input').value == item.id);
let currentQtyInTable = 0;
if (existingInTable) {
currentQtyInTable = parseFloat(existingInTable.querySelector('.item-qty').value) || 0;
}
if (currentQtyInTable + 1 > currentStock) {
alert('Insufficient stock! Available: ' + currentStock);
return;
}
}
const existingRow = Array.from(tableBody.querySelectorAll('.item-id-input')).find(input => input.value == item.id);
if (existingRow && !customData) {
const row = existingRow.closest('tr');
const qtyInput = row.querySelector('.item-qty');
qtyInput.value = parseFloat(qtyInput.value) + 1;
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
return;
}
const row = document.createElement('tr');
row.className = 'item-row';
const price = customData ? customData.unit_price : (invoiceType === 'sale' ? item.sale_price : item.purchase_price);
const qty = customData ? customData.quantity : 1;
const vatRate = item.vat_rate || 0;
row.innerHTML = `
<td>
<input type="hidden" name="item_ids[]" class="item-id-input" value="${item.id}">
<input type="hidden" class="item-row-stock" value="${item.stock_quantity}">
<input type="hidden" class="item-vat-rate" value="${vatRate}">
<div><strong>${item.name_en}</strong></div>
<div class="small text-muted">${item.name_ar} (${item.sku})</div>
</td>
<td><input type="number" step="0.001" name="quantities[]" class="form-control item-qty" value="${qty}" required></td>
<td><input type="number" step="0.001" name="prices[]" class="form-control item-price" value="${price}" required></td>
<td><input type="text" class="form-control bg-light" value="${parseFloat(vatRate || 0).toFixed(2)}%" readonly></td>
<td><input type="number" step="0.001" class="form-control item-total" value="${(qty * price).toFixed(3)}" readonly></td>
<td><button type="button" class="btn btn-outline-danger btn-sm remove-row"><i class="bi bi-trash"></i></button></td>
`;
tableBody.appendChild(row);
attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl);
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
}
function recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl) {
let subtotal = 0;
let totalVat = 0;
tableBody.querySelectorAll('.item-row').forEach(row => {
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const price = parseFloat(row.querySelector('.item-price').value) || 0;
const vatRate = parseFloat(row.querySelector('.item-vat-rate').value) || 0;
const total = qty * price;
const vatAmount = total * (vatRate / 100);
row.querySelector('.item-total').value = total.toFixed(3);
subtotal += total;
totalVat += vatAmount;
});
const grandTotal = subtotal + totalVat;
if (subtotalEl) subtotalEl.textContent = 'OMR ' + subtotal.toFixed(3);
if (totalVatEl) totalVatEl.textContent = 'OMR ' + totalVat.toFixed(2);
if (grandTotalEl) grandTotalEl.textContent = 'OMR ' + grandTotal.toFixed(3);
}
function attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl) {
row.querySelector('.item-qty').addEventListener('input', function() {
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
if (invoiceType === 'sale' && !allowZeroStock) {
const stock = parseFloat(row.querySelector('.item-row-stock').value) || 0;
const qty = parseFloat(this.value) || 0;
if (qty > stock) {
alert('Insufficient stock! Available: ' + stock);
this.value = stock;
}
}
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
});
row.querySelector('.item-price').addEventListener('input', () => recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl));
row.querySelector('.remove-row').addEventListener('click', function() {
row.remove();
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
});
}
const invoiceType = '<?= in_array($page, ["sales", "quotations"]) ? "sale" : (in_array($page, ["purchases", "lpos"]) ? "purchase" : "") ?>';
initInvoiceForm('productSearchInput', 'searchSuggestions', 'invoiceItemsTableBody', 'grandTotal', 'subtotal', 'totalVat');
initInvoiceForm('editProductSearchInput', 'editSearchSuggestions', 'editInvoiceItemsTableBody', 'edit_grandTotal', 'edit_subtotal', 'edit_totalVat');

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,158 @@
// Status change logic for Paid Amount field
const togglePaidAmount = (statusId, containerId) => {
const statusEl = document.getElementById(statusId);
const containerEl = document.getElementById(containerId);
if (statusEl && containerEl) {
statusEl.addEventListener('change', function() {
if (this.value === 'partially_paid') {
containerEl.style.display = 'block';
} else {
containerEl.style.display = 'none';
}
});
}
};
togglePaidAmount('add_status', 'addPaidAmountContainer');
togglePaidAmount('edit_status', 'editPaidAmountContainer');
// Pay Invoice Logic
document.querySelectorAll('.pay-invoice-btn').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.getAttribute('data-id');
const total = parseFloat(this.getAttribute('data-total'));
const paid = parseFloat(this.getAttribute('data-paid') || 0);
const remaining = total - paid;
document.getElementById('pay_invoice_id').value = id;
document.getElementById('pay_invoice_total').value = total.toFixed(3);
document.getElementById('pay_remaining_amount').value = remaining.toFixed(3);
document.getElementById('pay_amount').value = remaining.toFixed(3);
document.getElementById('pay_amount').max = remaining.toFixed(3);
});
});
// Show receipt modal if needed
<?php if (isset($_SESSION['trigger_receipt_modal'])):
$rid = (int)$_SESSION['show_receipt_id'];
unset($_SESSION['trigger_receipt_modal']);
?>
if (typeof showReceipt === 'function') {
showReceipt(<?= $rid ?>);
} else {
setTimeout(() => { if (typeof showReceipt === 'function') showReceipt(<?= $rid ?>); }, 500);
}
<?php endif; ?>
function showReceipt(paymentId) {
fetch(`index.php?action=get_payment_details&payment_id=${paymentId}`)
.then(res => res.json())
.then(data => {
if (!data) return;
document.getElementById('receiptNo').textContent = 'RCP-' + data.id.toString().padStart(5, '0');
document.getElementById('receiptDate').textContent = data.payment_date;
document.getElementById('receiptCustomer').textContent = data.customer_name || '---';
document.getElementById('receiptInvNo').textContent = (data.inv_type === 'purchase' ? 'PUR-' : 'INV-') + data.inv_id.toString().padStart(5, '0');
document.getElementById('receiptMethod').textContent = data.payment_method;
document.getElementById('receiptAmount').textContent = parseFloat(data.amount).toFixed(3);
document.getElementById('receiptAmountWords').textContent = data.amount_words;
const outletEl = document.getElementById('receiptOutletName');
if (outletEl) {
outletEl.textContent = data.outlet_name ? (data.outlet_name) : '';
outletEl.style.display = data.outlet_name ? 'block' : 'none';
}
// Update labels for Purchase vs Sale
const partyLabel = document.getElementById('receiptPartyLabel');
const againstLabel = document.getElementById('receiptAgainstLabel');
const receiptTitle = document.querySelector('#receiptModal .modal-title');
const receiptH4 = document.querySelector('.receipt-container h4.letter-spacing-2');
if (data.inv_type === 'purchase') {
partyLabel.textContent = 'Paid To';
partyLabel.setAttribute('data-en', 'Paid To');
partyLabel.setAttribute('data-ar', 'صرف إلى');
againstLabel.textContent = 'Against Purchase';
againstLabel.setAttribute('data-en', 'Against Purchase');
againstLabel.setAttribute('data-ar', 'مقابل شراء');
receiptTitle.textContent = 'Payment Voucher';
receiptTitle.setAttribute('data-en', 'Payment Voucher');
receiptTitle.setAttribute('data-ar', 'سند صرف');
receiptH4.textContent = 'PAYMENT VOUCHER';
receiptH4.setAttribute('data-en', 'PAYMENT VOUCHER');
receiptH4.setAttribute('data-ar', 'سند صرف');
} else {
partyLabel.textContent = 'Received From';
partyLabel.setAttribute('data-en', 'Received From');
partyLabel.setAttribute('data-ar', 'وصلنا من');
againstLabel.textContent = 'Against Invoice';
againstLabel.setAttribute('data-en', 'Against Invoice');
againstLabel.setAttribute('data-ar', 'مقابل فاتورة');
receiptTitle.textContent = 'Payment Receipt';
receiptTitle.setAttribute('data-en', 'Payment Receipt');
receiptTitle.setAttribute('data-ar', 'سند قبض');
receiptH4.textContent = 'PAYMENT RECEIPT';
receiptH4.setAttribute('data-en', 'PAYMENT RECEIPT');
receiptH4.setAttribute('data-ar', 'سند قبض');
}
const notesContainer = document.getElementById('receiptNotesContainer');
if (data.notes) {
document.getElementById('receiptNotes').textContent = data.notes;
notesContainer.style.display = 'block';
} else {
notesContainer.style.display = 'none';
}
const receiptModal = new bootstrap.Modal(document.getElementById('receiptModal'));
receiptModal.show();
});
};
document.querySelectorAll('.view-payments-btn').forEach(btn => {
btn.addEventListener('click', function() {
const invoiceId = this.getAttribute('data-id');
const tbody = document.getElementById('paymentsTableBody');
tbody.innerHTML = '<tr><td colspan="5" class="text-center" data-en="Loading..." data-ar="جاري التحميل...">Loading...</td></tr>';
fetch(`index.php?action=get_payments&invoice_id=${invoiceId}`)
.then(res => res.json())
.then(data => {
tbody.innerHTML = '';
if (data.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center" data-en="No payments found." data-ar="لا توجد مدفوعات.">No payments found.</td></tr>';
return;
}
data.forEach(p => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>RCP-${p.id.toString().padStart(5, '0')}</td>
<td>${p.payment_date}</td>
<td>${p.payment_method}</td>
<td class="text-end fw-bold">OMR ${parseFloat(p.amount).toFixed(3)}</td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary" onclick="showReceipt(${p.id})">
<i class="bi bi-printer"></i>
</button>
</td>
`;
tbody.appendChild(tr);
});
});
});
});
window.printReceipt = function() {
const content = document.getElementById('printableReceipt').innerHTML;
const originalContent = document.body.innerHTML;
document.body.innerHTML = content;
window.print();
document.body.innerHTML = originalContent;
window.location.reload();
};

View File

@ -0,0 +1,181 @@
window.viewAndPrintA4Invoice = function(data, autoPrint = true) {
if (!data) return;
// Reuse view logic
const invoiceDisplayNo = data.document_no || data.transaction_no || ((data.type === 'purchase' ? 'PUR-' : 'INV-') + data.id.toString().padStart(5, '0'));
document.getElementById('invNumber').textContent = invoiceDisplayNo;
document.getElementById('invDate').textContent = data.invoice_date;
document.getElementById('invPaymentType').textContent = data.payment_type ? data.payment_type.toUpperCase() : 'CASH';
document.getElementById('invCustomerName').textContent = data.customer_name || '---';
const phoneEl = document.getElementById('invCustomerPhone');
const phoneContainer = document.getElementById('invCustomerPhoneContainer');
if (data.customer_phone) {
phoneEl.textContent = data.customer_phone;
phoneContainer.style.display = 'block';
} else {
phoneContainer.style.display = 'none';
}
const taxIdEl = document.getElementById('invCustomerTaxId');
const taxIdContainer = document.getElementById('invCustomerTaxIdContainer');
if (data.customer_tax_id) {
taxIdEl.textContent = data.customer_tax_id;
taxIdContainer.style.display = 'block';
} else {
taxIdContainer.style.display = 'none';
}
document.getElementById('invAmountInWords').textContent = data.total_in_words || '';
const invOutletEl = document.getElementById('invOutletName');
if (invOutletEl) {
invOutletEl.textContent = data.outlet_name ? (data.outlet_name) : '';
invOutletEl.style.display = data.outlet_name ? 'block' : 'none';
}
document.getElementById('invPartyLabel').textContent = data.type === 'sale' ? 'Bill To / فاتورة إلى' : 'Bill From / فاتورة من';
document.getElementById('invPartyLabel').setAttribute('data-en', data.type === 'sale' ? 'Bill To' : 'Bill From');
document.getElementById('invPartyLabel').setAttribute('data-ar', data.type === 'sale' ? 'فاتورة إلى' : 'فاتورة من');
document.getElementById('invoiceTypeLabel').textContent = data.type;
document.getElementById('invoiceTypeLabel').className = 'badge text-uppercase ' + (data.type === 'sale' ? 'bg-success' : 'bg-warning');
const statusLabel = document.getElementById('invoiceStatusLabel');
let statusClass = 'bg-secondary';
let statusEn = data.status ? (data.status.charAt(0).toUpperCase() + data.status.slice(1)) : '---';
if (data.status === 'paid') statusClass = 'bg-success';
else if (data.status === 'unpaid') statusClass = 'bg-danger';
else if (data.status === 'partially_paid') {
statusClass = 'bg-warning text-dark';
statusEn = 'Partially Paid';
}
statusLabel.textContent = statusEn;
statusLabel.className = 'badge text-uppercase ' + statusClass;
const body = document.getElementById('invItemsBody');
body.innerHTML = '';
if (data.items) {
data.items.forEach(item => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${item.name_en} / ${item.name_ar}</td>
<td class="text-center">${item.quantity}</td>
<td class="text-end"><small><?= __('currency') ?></small> ${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end">${parseFloat(item.vat_rate || 0).toFixed(2)}%</td>
<td class="text-end"><small><?= __('currency') ?></small> ${parseFloat(item.total_price).toFixed(3)}</td>
`;
body.appendChild(tr);
});
}
const vatVal = parseFloat(data.vat_amount || 0);
const totalVal = parseFloat(data.total_amount || 0);
const grandTotalValue = (parseFloat(data.total_with_vat) || (totalVal + vatVal));
if (document.getElementById('invSubtotal')) document.getElementById('invSubtotal').innerHTML = '<small><?= __('currency') ?></small> ' + (grandTotalValue - vatVal).toFixed(3);
if (document.getElementById('invVatAmount')) document.getElementById('invVatAmount').innerHTML = '<small><?= __('currency') ?></small> ' + vatVal.toFixed(2);
if (document.getElementById('invGrandTotal')) document.getElementById('invGrandTotal').innerHTML = '<small><?= __('currency') ?></small> ' + grandTotalValue.toFixed(3);
if (document.getElementById('invPaidInfo')) document.getElementById('invPaidInfo').innerHTML = '<small><?= __('currency') ?></small> ' + parseFloat(data.paid_amount || 0).toFixed(3);
const balance = grandTotalValue - parseFloat(data.paid_amount || 0);
if (document.getElementById('invBalanceInfo')) document.getElementById('invBalanceInfo').innerHTML = '<small><?= __('currency') ?></small> ' + balance.toFixed(3);
// Generate QR Code for Zakat, Tax and Customs Authority (ZATCA) style or simple formal
const companyName = <?= json_encode($data['settings']['company_name'] ?? 'Accounting System') ?>;
const vatNo = <?= json_encode($data['settings']['vat_number'] ?? '') ?>;
const qrData = `Seller: ${companyName}\nVAT: ${vatNo}\nInvoice: ${invoiceDisplayNo}\nDate: ${data.invoice_date}\nTotal: ${grandTotalValue.toFixed(3)}`;
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=${encodeURIComponent(qrData)}`;
if (document.getElementById('invQrCode')) {
document.getElementById('invQrCode').innerHTML = `<img src="${qrUrl}" alt="QR Code" style="width: 100px; height: 100px;" class="border p-1 bg-white">`;
}
const viewModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('viewInvoiceModal'));
viewModal.show();
if (autoPrint) {
setTimeout(() => { window.print(); }, 1000);
}
fetch(`index.php?action=get_payments&invoice_id=${data.id}`)
.then(res => res.json())
.then(payments => {
const paymentsBody = document.getElementById('invPaymentsBody');
const paymentsSection = document.getElementById('invPaymentsSection');
if (paymentsBody) paymentsBody.innerHTML = '';
if (payments && payments.length > 0) {
if (paymentsBody) {
payments.forEach(p => {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${p.payment_date}</td><td>${p.payment_method}</td><td class="text-end fw-bold">OMR ${parseFloat(p.amount).toFixed(3)}</td>`;
paymentsBody.appendChild(tr);
});
}
if (paymentsSection) paymentsSection.style.display = 'block';
} else {
if (paymentsSection) paymentsSection.style.display = 'none';
}
}).catch(err => console.error('Error fetching payments:', err));
};
<?php
$autoInvoicePayload = null;
if (
isset($_SESSION['trigger_invoice_modal'], $_SESSION['show_invoice_id'], $_SESSION['show_invoice_page']) &&
in_array($page, ['sales', 'purchases'], true) &&
$_SESSION['show_invoice_page'] === $page
) {
$autoInvoiceId = (int)$_SESSION['show_invoice_id'];
$autoInvoiceType = $page === 'purchases' ? 'purchase' : 'sale';
unset($_SESSION['trigger_invoice_modal'], $_SESSION['show_invoice_id'], $_SESSION['show_invoice_page']);
try {
$autoTable = $autoInvoiceType === 'purchase' ? 'purchases' : 'invoices';
$autoPartyTable = $autoInvoiceType === 'purchase' ? 'suppliers' : 'customers';
$autoPartyCol = $autoInvoiceType === 'purchase' ? 'supplier_id' : 'customer_id';
$autoTaxColumn = entity_tax_column($autoPartyTable);
$autoTaxSelect = $autoTaxColumn !== null ? "c.$autoTaxColumn AS customer_tax_id" : "'' AS customer_tax_id";
$autoOutletSelect = "'' AS outlet_name";
$autoOutletJoin = '';
if (db_column_exists($autoTable, 'outlet_id') && db_table_exists('outlets')) {
$autoOutletSelect = 'o.name AS outlet_name';
$autoOutletJoin = 'LEFT JOIN outlets o ON doc.outlet_id = o.id';
}
$autoStmt = db()->prepare("SELECT doc.*, c.name AS customer_name, c.phone AS customer_phone, $autoTaxSelect, $autoOutletSelect
FROM $autoTable doc
LEFT JOIN $autoPartyTable c ON doc.$autoPartyCol = c.id
$autoOutletJoin
WHERE doc.id = ?
LIMIT 1");
$autoStmt->execute([$autoInvoiceId]);
$autoInvoicePayload = $autoStmt->fetch(PDO::FETCH_ASSOC);
if ($autoInvoicePayload) {
$autoItemSql = $autoInvoiceType === 'purchase'
? "SELECT pi.*, i.name_en, i.name_ar, i.vat_rate FROM purchase_items pi LEFT JOIN stock_items i ON pi.item_id = i.id WHERE pi.purchase_id = ?"
: "SELECT ii.*, i.name_en, i.name_ar, i.vat_rate FROM invoice_items ii LEFT JOIN stock_items i ON ii.item_id = i.id WHERE ii.invoice_id = ?";
$autoItemsStmt = db()->prepare($autoItemSql);
$autoItemsStmt->execute([$autoInvoiceId]);
$autoInvoicePayload['items'] = $autoItemsStmt->fetchAll(PDO::FETCH_ASSOC);
$autoInvoicePayload['type'] = $autoInvoiceType;
$autoInvoicePayload['total_with_vat'] = (float)($autoInvoicePayload['total_with_vat'] ?? (($autoInvoicePayload['total_amount'] ?? 0) + ($autoInvoicePayload['vat_amount'] ?? 0)));
$autoInvoicePayload['paid_amount'] = (float)($autoInvoicePayload['paid_amount'] ?? 0);
$autoInvoicePayload['total_in_words'] = numberToWordsOMR($autoInvoicePayload['total_with_vat']);
$autoTransactionNo = trim((string)($autoInvoicePayload['transaction_no'] ?? ''));
$autoPrefix = $autoInvoiceType === 'purchase' ? 'PUR' : 'INV';
$autoInvoicePayload['document_no'] = ($autoInvoiceType === 'sale' && $autoTransactionNo !== '')
? $autoTransactionNo
: $autoPrefix . '-' . str_pad((string)$autoInvoicePayload['id'], 5, '0', STR_PAD_LEFT);
}
} catch (Throwable $e) {
$autoInvoicePayload = null;
}
}
?>
<?php if (!empty($autoInvoicePayload)): ?>
setTimeout(() => {
if (window.viewAndPrintA4Invoice) {
window.viewAndPrintA4Invoice(<?= json_encode($autoInvoicePayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, true);
}
}, 300);
<?php endif; ?>

View File

@ -0,0 +1,237 @@
<style>
@media print {
.no-print, .sidebar, .topbar, .btn, .modal-header, .modal-footer, .d-print-none,
.modal-backdrop { display: none !important; }
body { background: white !important; margin: 0 !important; padding: 0 !important; overflow: visible !important; }
.main-content { margin: 0 !important; padding: 0 !important; background: white !important; }
/* Hide all modals by default */
.modal { display: none !important; }
/* Show ONLY the active modal */
.modal.show {
position: absolute !important;
left: 0 !important;
top: 0 !important;
margin: 0 !important;
padding: 0 !important;
overflow: visible !important;
display: block !important;
visibility: visible !important;
background: white !important;
width: 100% !important;
}
.modal.show .modal-dialog { max-width: 100% !important; width: 100% !important; margin: 0 !important; padding: 0 !important; }
.modal.show .modal-content { border: none !important; box-shadow: none !important; background: white !important; }
.modal.show .modal-body { padding: 0 !important; margin: 0 !important; background: white !important; }
@page {
size: auto;
margin: 5mm;
}
.invoice-printable-container {
padding: 10px !important;
}
.mt-4 { margin-top: 1rem !important; }
.mt-5 { margin-top: 1.5rem !important; }
.mb-4 { margin-bottom: 1rem !important; }
.p-5 { padding: 1.5rem !important; }
.table-bordered th, .table-bordered td { border: 1px solid #dee2e6 !important; }
.bg-light { background-color: #f8f9fa !important; -webkit-print-color-adjust: exact; }
.text-primary { color: #0d6efd !important; -webkit-print-color-adjust: exact; }
.badge { border: 1px solid #000; color: #000 !important; }
/* Ensure the modal is the only thing visible ONLY when a modal is open */
body.modal-open:not(.printing-receipt) { visibility: hidden !important; }
body.modal-open:not(.printing-receipt) .modal.show {
visibility: visible !important;
display: block !important;
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
}
body.modal-open:not(.printing-receipt) .modal.show * { visibility: visible !important; }
/* Old rules that caused blank pages for nested modals */
/* body.modal-open:not(.printing-receipt) > *:not(.modal):not(.swal2-container) { display: none !important; } */
/* body.modal-open:not(.printing-receipt) .main-content { display: none !important; } */
/* POS Receipt printing specific */
body.printing-receipt .modal { display: none !important; }
body.printing-receipt .modal-backdrop { display: none !important; }
body.printing-receipt #posPrintArea {
display: block !important;
visibility: visible !important;
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
z-index: 9999 !important;
background: white !important;
}
body.printing-receipt #posPrintArea * {
visibility: visible !important;
}
}
.invoice-logo { max-height: 80px; width: auto; }
.invoice-header { border-bottom: 2px solid #333; padding-bottom: 20px; }
.invoice-title { font-size: 2.5rem; color: #333; letter-spacing: 2px; }
.invoice-info-card { background: #f8f9fa; border-radius: 8px; padding: 15px; height: 100%; }
.table-formal thead th { background: #333; color: #fff; border: none; text-transform: uppercase; font-size: 0.85rem; }
</style>
<div class="modal fade" id="viewInvoiceModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header d-print-none">
<h5 class="modal-title" data-en="View Invoice" data-ar="عرض الفاتورة">View Invoice</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0" id="invoicePrintableArea">
<div class="p-4 invoice-printable-container">
<div class="invoice-header mb-4">
<div class="row align-items-center">
<div class="col-6">
<?php
$logo = $data['settings']['company_logo'] ?? $_SERVER['PROJECT_IMAGE_URL'] ?? '';
if ($logo):
?>
<img src="<?= htmlspecialchars($logo) ?>" alt="Logo" class="invoice-logo mb-3">
<?php endif; ?>
<h3 class="mb-1 fw-bold"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<p class="text-muted small mb-0 fw-bold text-primary" id="invOutletName" style="display:none;"></p>
<p class="text-muted small mb-0">VAT: <?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?></p>
<?php if (!empty($data['settings']['company_phone'])): ?>
<p class="text-muted small mb-0">Tel: <?= htmlspecialchars($data['settings']['company_phone']) ?></p>
<?php endif; ?>
</div>
<div class="col-6 text-end">
<h1 class="invoice-title fw-bold mb-0 text-uppercase">Tax Invoice / فاتورة ضريبية</h1>
<div class="mt-2"><span id="invoiceTypeLabel" class="badge"></span></div>
<div class="mt-3">
<p class="mb-0 fs-5">Invoice No / رقم الفاتورة: <strong id="invNumber" class="text-primary"></strong></p>
<p class="mb-0">Date / التاريخ: <span id="invDate" class="fw-bold"></span></p>
<p class="mb-0 small">Status / الحالة: <span id="invoiceStatusLabel"></span></p>
</div>
</div>
</div>
</div>
<div class="row mb-4 g-3">
<div class="col-6">
<div class="invoice-info-card">
<p class="text-muted small text-uppercase fw-bold mb-2 border-bottom pb-1" id="invPartyLabel">Bill To / فاتورة إلى</p>
<h5 class="mb-1 fw-bold"><span id="invCustomerName"></span></h5>
<p class="small text-muted mb-0" id="invCustomerTaxIdContainer">VAT / الضريبة: <span id="invCustomerTaxId"></span></p>
<p class="small text-muted mb-0" id="invCustomerPhoneContainer">Phone / الهاتف: <span id="invCustomerPhone"></span></p>
</div>
</div>
<div class="col-6">
<div class="invoice-info-card text-end">
<p class="text-muted small text-uppercase fw-bold mb-2 border-bottom pb-1">Payment Details / تفاصيل الدفع</p>
<p class="mb-1">Method / الطريقة: <strong id="invPaymentType"></strong></p>
<p class="mb-0 small text-muted">Currency / العملة: <strong>OMR / ريال عماني</strong></p>
</div>
</div>
</div>
<table class="table table-bordered table-formal">
<thead>
<tr>
<th>Item Description / وصف الصنف</th>
<th class="text-center" style="width: 80px;">Qty / الكمية</th>
<th class="text-end" style="width: 120px;">Unit Price / سعر الوحدة</th>
<th class="text-end" style="width: 100px;">VAT % / الضريبة %</th>
<th class="text-end" style="width: 150px;">Total / الإجمالي</th>
</tr>
</thead>
<tbody id="invItemsBody"></tbody>
</table>
<div class="row mt-4">
<div class="col-6">
<div class="p-3 bg-light rounded" style="min-height: 80px;">
<p class="text-muted small text-uppercase fw-bold mb-1">Amount in Words / المبلغ بالحروف</p>
<p id="invAmountInWords" class="small fw-bold mb-0"></p>
</div>
<div class="mt-3">
<p class="text-muted small text-uppercase fw-bold mb-1">Terms & Conditions / الشروط والأحكام</p>
<ul class="small text-muted ps-3 mb-0">
<li>Goods once sold will not be taken back or exchanged.</li>
<li>Payment is due within the agreed credit period.</li>
</ul>
</div>
</div>
<div class="col-6">
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Subtotal (Excl. VAT) / المجموع الفرعي (دون الضريبة)</span>
<span id="invSubtotal" class="fw-bold text-nowrap"></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">VAT Amount / مبلغ الضريبة</span>
<span id="invVatAmount" class="fw-bold text-nowrap"></span>
</div>
<div class="d-flex justify-content-between mb-3 border-top pt-2">
<span class="h4 fw-bold">Grand Total / المجموع الكلي</span>
<span id="invGrandTotal" class="h4 fw-bold text-primary text-nowrap"></span>
</div>
<div id="invPaymentsSection" class="mt-4 border-top pt-3">
<p class="text-muted small text-uppercase fw-bold mb-2">Payment Tracking / تتبع الدفع</p>
<table class="table table-sm table-bordered small mb-3">
<thead class="table-light">
<tr>
<th>Date / التاريخ</th>
<th>Method / الطريقة</th>
<th class="text-end">Amount / المبلغ</th>
</tr>
</thead>
<tbody id="invPaymentsBody"></tbody>
</table>
<div class="bg-light p-3 rounded">
<div class="d-flex justify-content-between small mb-1">
<span>Paid Amount / المبلغ المدفوع</span>
<span id="invPaidInfo" class="text-success fw-bold"></span>
</div>
<div class="d-flex justify-content-between small fw-bold">
<span>Balance Due / الرصيد المتبقي</span>
<span id="invBalanceInfo" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
<div class="mt-4 pt-3 border-top text-center">
<div class="row">
<div class="col-4">
<div class="border-top pt-2 mx-auto" style="width: 150px;">
<p class="small text-muted mb-0">Customer Signature / توقيع العميل</p>
</div>
</div>
<div class="col-4">
<div id="invQrCode" class="d-flex justify-content-center"></div>
</div>
<div class="col-4">
<div class="border-top pt-2 mx-auto" style="width: 150px;">
<p class="small text-muted mb-0">Authorized Signatory / التوقيع المعتمد</p>
</div>
</div>
</div>
<p class="text-muted x-small mt-3 mb-0">Generated by <?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?> | Visit us at <?= $_SERVER['HTTP_HOST'] ?></p>
</div>
</div>
</div>
<div class="modal-footer d-print-none">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
<button type="button" class="btn btn-primary" onclick="window.print()"><i class="bi bi-printer me-2"></i>Print Invoice</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,179 @@
<?php
// Shared Sales/Purchases create/update handlers extracted from index.php
// to reduce regression risk while preserving the existing behavior.
// Invoices
if (isset($_POST['add_invoice'])) {
$db = db();
try {
$db->beginTransaction();
$type = $_POST['type'] ?? 'sale';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$item_table = ($type === 'purchase') ? 'purchase_items' : 'invoice_items';
$cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id';
$fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id';
$cust_id = (int)$_POST['customer_id'];
$inv_date = $_POST['invoice_date'] ?: date('Y-m-d');
$due_date = $_POST['due_date'] ?: null;
$status = $_POST['status'] ?? 'pending';
$pay_type = $_POST['payment_type'] ?? 'cash';
$items = $_POST['item_ids'] ?? [];
if (empty($items)) {
throw new Exception("Please add at least one item.");
}
$qtys = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
$total_subtotal = 0;
$total_vat = 0;
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$total_subtotal += $subtotal;
$total_vat += $vatAmount;
}
$total_with_vat = $total_subtotal + $total_vat;
$paid = (float)($_POST['paid_amount'] ?? 0);
if ($status === 'paid') $paid = $total_with_vat;
$stmt = $db->prepare("INSERT INTO $table ($cust_supplier_col, invoice_date, due_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$cust_id, $inv_date, $due_date, $status, $pay_type, $total_subtotal, $total_vat, $total_with_vat, $paid]);
$inv_id = $db->lastInsertId();
if (db_column_exists($table, 'outlet_id')) {
$db->prepare("UPDATE $table SET outlet_id = ? WHERE id = ?")->execute([current_outlet_id(), $inv_id]);
}
$items_for_journal = [];
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$db->prepare("INSERT INTO $item_table ($fk_col, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item_id, $qty, $price, $vatAmount, $subtotal]);
// Update stock
$change = ($type === 'sale') ? -$qty : $qty;
update_stock($item_id, $change);
$items_for_journal[] = ['id' => $item_id, 'qty' => $qty];
}
// Accounting
if ($type === 'sale') {
recordSaleJournal($inv_id, $total_with_vat, $inv_date, $items_for_journal, $total_vat);
} else {
// For purchases, you might have recordPurchaseJournal, but let's check if it exists
if (function_exists('recordPurchaseJournal')) {
recordPurchaseJournal($inv_id, $total_with_vat, $inv_date, $items_for_journal, $total_vat);
}
}
$db->commit();
$_SESSION['trigger_invoice_modal'] = true;
$_SESSION['show_invoice_id'] = (int)$inv_id;
$_SESSION['show_invoice_page'] = ($type === 'purchase') ? 'purchases' : 'sales';
$msg = ($type === 'purchase' ? "Purchase" : "Invoice") . " #$inv_id created!";
redirectWithMessage($msg, "index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales'));
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['edit_invoice'])) {
$db = db();
try {
$db->beginTransaction();
$id = (int)$_POST['invoice_id'];
$type = ($page === 'purchases') ? 'purchase' : 'sale';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$item_table = ($type === 'purchase') ? 'purchase_items' : 'invoice_items';
$cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id';
$fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id';
$cust_id = (int)$_POST['customer_id'];
$date = $_POST['invoice_date'] ?: date('Y-m-d');
$due_date = $_POST['due_date'] ?: null;
$status = $_POST['status'] ?? 'pending';
$pay_type = $_POST['payment_type'] ?? 'cash';
$items = $_POST['item_ids'] ?? [];
$qtys = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
$total_subtotal = 0;
$total_vat = 0;
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$total_subtotal += $subtotal;
$total_vat += $vatAmount;
}
$total_with_vat = $total_subtotal + $total_vat;
$paid = (float)($_POST['paid_amount'] ?? 0);
if ($status === 'paid') $paid = $total_with_vat;
$db->prepare("UPDATE $table SET $cust_supplier_col = ?, invoice_date = ?, due_date = ?, status = ?, payment_type = ?, total_amount = ?, vat_amount = ?, total_with_vat = ?, paid_amount = ? WHERE id = ?")
->execute([$cust_id, $date, $due_date, $status, $pay_type, $total_subtotal, $total_vat, $total_with_vat, $paid, $id]);
if (db_column_exists($table, 'outlet_id')) {
$db->prepare("UPDATE $table SET outlet_id = COALESCE(outlet_id, ?) WHERE id = ?")->execute([current_outlet_id(), $id]);
}
// Revert stock for old items
$stmtOld = $db->prepare("SELECT item_id, quantity FROM $item_table WHERE $fk_col = ?");
$stmtOld->execute([$id]);
$oldItems = $stmtOld->fetchAll();
foreach ($oldItems as $old) {
$change = ($type === 'sale') ? (float)$old['quantity'] : -(float)$old['quantity'];
update_stock($old['item_id'], $change);
}
// Delete old items
$db->prepare("DELETE FROM $item_table WHERE $fk_col = ?")->execute([$id]);
// Insert new items and update stock
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$db->prepare("INSERT INTO $item_table ($fk_col, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$id, $item_id, $qty, $price, $vatAmount, $subtotal]);
$change = ($type === 'sale') ? -$qty : $qty;
update_stock($item_id, $change);
}
$db->commit();
$msg = ($type === 'purchase' ? "Purchase" : "Invoice") . " updated successfully!";
redirectWithMessage($msg, "index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales'));
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}

View File

@ -0,0 +1,35 @@
<script>
$(document).ready(function() {
// Select All by Action (View, Add, Edit, Delete)
$('.select-all-action').on('change', function() {
const action = $(this).data('action');
const checked = $(this).is(':checked');
const modal = $(this).closest('.modal');
modal.find('.perm-check[data-action="' + action + '"]').prop('checked', checked);
});
// Select All by Row
$('.select-all-row').on('change', function() {
const checked = $(this).is(':checked');
$(this).closest('.module-row').find('.perm-check').prop('checked', checked);
});
// Select All by Group
$('.select-all-group').on('change', function() {
const checked = $(this).is(':checked');
$(this).closest('.permission-group-container').find('.perm-check, .select-all-row').prop('checked', checked);
});
// Select All Button
$('.select-all-btn').on('click', function() {
const modal = $($(this).data('modal'));
modal.find('.perm-check, .select-all-action, .select-all-group, .select-all-row').prop('checked', true);
});
// Deselect All Button
$('.deselect-all-btn').on('click', function() {
const modal = $($(this).data('modal'));
modal.find('.perm-check, .select-all-action, .select-all-group, .select-all-row').prop('checked', false);
});
});
</script>

View File

@ -1,190 +0,0 @@
2026-02-25 09:56:38 - POST: {"action":"translate","text":"Product 2","target":"ar"}
2026-02-25 12:57:47 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":25}]","total_amount":"25","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":7,\"qty\":1,\"price\":10,\"vat_rate\":0,\"vat_amount\":0},{\"id\":8,\"qty\":1,\"price\":15,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-02-25 13:23:21 - POST: {"sync_accounting":""}
2026-02-25 13:30:19 - POST: {"action":"save_theme","theme":"nord"}
2026-02-25 13:30:28 - POST: {"action":"save_theme","theme":"forest"}
2026-02-25 13:30:36 - POST: {"action":"save_theme","theme":"citrus"}
2026-02-25 13:30:39 - POST: {"action":"save_theme","theme":"forest"}
2026-02-25 13:30:41 - POST: {"action":"save_theme","theme":"nord"}
2026-02-25 13:31:58 - POST: {"id":"","name":"Nizwa Branch","phone":"9689555","address":"Nizwa","status":"active","add_outlet":""}
2026-02-25 17:08:42 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":15.45}]","total_amount":"15.45","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.45,\"vat_rate\":0,\"vat_amount\":0},{\"id\":8,\"qty\":1,\"price\":15,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-02-25 17:09:31 - POST: {"id":"37","delete_invoice":""}
2026-02-26 02:56:35 - POST: {"import_items":""}
2026-02-26 02:57:11 - POST: {"import_items":""}
2026-02-26 03:01:00 - POST: {"import_items":""}
2026-02-26 03:01:48 - POST: {"action":"translate","text":"LAMING PINEAPPLE 454G","target":"ar"}
2026-02-26 03:05:29 - POST: {"action":"translate","text":"LAMING RED KIDNEY BEANS 425","target":"ar"}
2026-02-26 03:09:07 - POST: {"id":"62","name_en":"LAMING RED KIDNEY BEANS 425","name_ar":"LAMING RED KIDNEY BEANS 425","sku":"000023071605","category_id":"2","unit_id":"1","supplier_id":"5","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"0","min_stock_level":"0","vat_rate":"0.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-02-26 03:09:24 - POST: {"action":"translate","text":"LAMING RED KIDNEY BEANS 425","target":"ar"}
2026-02-26 03:09:34 - POST: {"id":"62","name_en":"LAMING RED KIDNEY BEANS 425","name_ar":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627\u0621 \u062d\u0645\u0631\u0627\u0621 \u0644\u0627\u0645\u064a\u0646\u062c 425","sku":"000023071605","category_id":"2","unit_id":"1","supplier_id":"5","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"0","min_stock_level":"0","vat_rate":"0.00","expiry_date":"","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-02-26 03:11:08 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":1.7}]","total_amount":"1.7000000000000002","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":48,\"qty\":1,\"price\":1.3,\"vat_rate\":0,\"vat_amount\":0},{\"id\":17,\"qty\":1,\"price\":0.4,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-02-26 05:17:43 - POST: {"action":"save_theme","theme":"forest"}
2026-02-26 05:17:47 - POST: {"action":"save_theme","theme":"default"}
2026-02-26 05:47:24 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":2.6}]","total_amount":"2.6","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":19,\"qty\":1,\"price\":1.2,\"vat_rate\":0,\"vat_amount\":0},{\"id\":12,\"qty\":1,\"price\":1.2,\"vat_rate\":0,\"vat_amount\":0},{\"id\":13,\"qty\":1,\"price\":0.2,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-02-26 06:03:42 - POST: {"device_name":"scale 1","device_type":"scale","connection_type":"network","ip_address":"192.168.1.10","port":"9100","baud_rate":"","add_pos_device":""}
2026-02-26 08:15:07 - POST: {"id":"2","name":"Nizwa Branch","phone":"9689555","ctr_number":"5012641","vat_number":"OM1254","address":"Nizwa","status":"active","edit_outlet":""}
2026-02-26 08:15:57 - POST: {"id":"1","name":"Moosa Ali Al-Abri","phone":"99359472","ctr_number":"514899","vat_number":"OM99888","address":"AL Hamra\r\n","status":"active","edit_outlet":""}
2026-02-26 08:16:37 - POST: {"id":"1","name":"Main Branch","phone":"99359472","ctr_number":"514899","vat_number":"OM99888","address":"AL Hamra\r\n","status":"active","edit_outlet":""}
2026-02-26 08:17:31 - POST: {"name":"Counter1","add_cash_register":""}
2026-02-26 08:17:56 - POST: {"open_register":"1","register_id":"4","opening_balance":"0"}
2026-02-26 17:58:24 - POST: {"username":"admin","password":"admin123","login":""}
2026-02-26 17:58:40 - POST: {"username":"admin","password":"admin123","login":""}
2026-02-26 17:58:46 - POST: {"username":"admin","password":"admin123","login":""}
2026-02-26 17:59:03 - POST: {"username":"admin","password":"admin123","login":""}
2026-02-26 17:59:14 - POST: {"username":"admin","password":"admin","login":""}
2026-03-17 09:25:20 - POST: {"start_trial":""}
2026-03-17 09:25:46 - POST: {"username":"admin","password":"admin","login":""}
2026-03-17 09:58:21 - POST: {"license_key":"FLAT-8E07-54E9-B427","activate":""}
2026-03-17 10:12:13 - POST: {"username":"admin","password":"admin","login":""}
2026-03-17 10:12:37 - POST: {"username":"moosa","password":"moosa123","login":""}
2026-03-17 13:25:29 - POST: {"name":"Counter1","add_cash_register":""}
2026-03-17 13:25:44 - POST: {"open_register":"1","register_id":"3","opening_balance":"0"}
2026-03-17 15:07:15 - POST: {"action":"translate","text":"Food Stuff","target":"ar"}
2026-03-17 15:07:23 - POST: {"name_en":"Food Stuff","name_ar":"\u0645\u0648\u0627\u062f \u063a\u0630\u0627\u0626\u064a\u0629","add_category":""}
2026-03-17 15:07:52 - POST: {"action":"translate","text":"piece","target":"ar"}
2026-03-17 15:08:10 - POST: {"name_en":"piece","short_en":"pcs","name_ar":"\u062d\u0628\u0629","short_ar":"\u062d\u0628\u0629","add_unit":""}
2026-03-17 15:08:46 - POST: {"name_en":"Tomato","name_ar":"\u0637\u0645\u0627\u0637\u0645","category_id":"3","unit_id":"4","supplier_id":"","sku":"241604476040","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"0.000","min_stock_level":"0.000","vat_rate":"0","expiry_date":"","promotion_start":"","promotion_end":"","promotion_percent":"0.00","add_item":""}
2026-03-17 15:09:17 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.25}]","total_amount":"0.25","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":7,\"qty\":1,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-17 15:09:44 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.25}]","total_amount":"0.25","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":7,\"qty\":1,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-17 15:15:26 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.5}]","total_amount":"0.5","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":7,\"qty\":2,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-17 17:47:03 - POST: {"action":"translate","text":"Kilogram","target":"ar"}
2026-03-17 17:47:11 - POST: {"name_en":"Kilogram","short_en":"Kg","name_ar":"\u0643\u064a\u0644\u0648\u063a\u0631\u0627\u0645","short_ar":"\u0643\u062c\u0645","add_unit":""}
2026-03-17 17:47:27 - POST: {"id":"7","name_en":"Tomato","name_ar":"\u0637\u0645\u0627\u0637\u0645","sku":"241604476040","category_id":"3","unit_id":"5","supplier_id":"","sale_price":"0.25","purchase_price":"0.2","vat_rate":"0.00","stock_quantity":"0","min_stock_level":"0","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-17 18:07:32 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.25}]","total_amount":"0.25","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":7,\"qty\":1,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-17 18:14:21 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.25}]","total_amount":"0.25","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":7,\"qty\":1,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-18 02:00:56 - POST: {"action":"save_theme","theme":"ocean"}
2026-03-18 02:01:01 - POST: {"action":"save_theme","theme":"sunset"}
2026-03-18 02:01:26 - POST: {"action":"save_theme","theme":"citrus"}
2026-03-18 02:01:29 - POST: {"action":"save_theme","theme":"default"}
2026-03-18 02:31:31 - POST: {"action":"save_theme","theme":"sunset"}
2026-03-18 02:31:34 - POST: {"action":"save_theme","theme":"ocean"}
2026-03-18 02:31:37 - POST: {"action":"save_theme","theme":"default"}
2026-03-18 02:31:40 - POST: {"action":"save_theme","theme":"sunset"}
2026-03-18 02:31:50 - POST: {"action":"save_theme","theme":"default"}
2026-03-18 06:08:50 - POST: {"type":"customer","name":"Moosa Ali Al-Abri","email":"aalabry@gmail.com","phone":"99359472","tax_id":"","balance":"0.000","add_customer":""}
2026-03-18 06:29:34 - POST: {"settings":{"company_name":"Bahjet Al-Safa Trading","company_phone":"99359472","company_email":"aalabry@gmail.com","vat_number":"OM25418","company_address":"AL Hamra\r\nOman","timezone":"Asia\/Muscat","allow_zero_stock_sell":"1","loyalty_enabled":"0","loyalty_points_per_unit":"1","loyalty_redeem_points_per_unit":"100"},"update_settings":""}
2026-03-18 06:30:25 - POST: {"type":"sale","customer_id":"7","invoice_date":"2026-03-18","due_date":"","payment_type":"cash","status":"paid","paid_amount":"0.000","item_ids":["7"],"quantities":["5"],"prices":["0.250"],"add_invoice":""}
2026-03-18 06:31:00 - POST: {"type":"sale","customer_id":"7","invoice_date":"2026-03-18","due_date":"","payment_type":"cash","status":"unpaid","paid_amount":"0.000","item_ids":["7"],"quantities":["1"],"prices":["0.250"],"add_invoice":""}
2026-03-18 06:42:39 - POST: {"type":"sale","customer_id":"7","invoice_date":"2026-03-18","due_date":"","payment_type":"cash","status":"unpaid","paid_amount":"0.000","item_ids":["7"],"quantities":["1"],"prices":["0.250"],"add_invoice":""}
2026-03-18 06:43:05 - POST: {"invoice_id":"31","customer_id":"7","invoice_date":"2026-03-18","due_date":"","payment_type":"cash","status":"paid","paid_amount":"0.000","item_ids":["7"],"quantities":["1.00"],"prices":["0.250"],"edit_invoice":""}
2026-03-18 07:19:13 - POST: {"id":"","name":"Nizwa Outlet","phone":"","address":"","status":"active","add_outlet":""}
2026-03-18 09:46:16 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":1.25}]","total_amount":"1.25","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":7,\"qty\":5,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-18 09:47:04 - POST: {"type":"sale","customer_id":"7","invoice_date":"2026-03-18","due_date":"","payment_type":"cash","status":"unpaid","paid_amount":"0.000","item_ids":["7"],"quantities":["1"],"prices":["0.250"],"add_invoice":""}
2026-03-18 09:53:37 - POST: {"type":"sale","customer_id":"7","invoice_date":"2026-03-18","due_date":"","payment_type":"cash","status":"unpaid","paid_amount":"0.000","item_ids":["7"],"quantities":["1"],"prices":["0.250"],"add_invoice":""}
2026-03-18 09:59:41 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.25}]","total_amount":"0.25","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":7,\"qty\":1,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-18 10:32:41 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.25}]","total_amount":"0.25","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":7,\"qty\":1,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-18 14:09:03 - POST: {"action":"save_theme","theme":"dark"}
2026-03-18 14:09:06 - POST: {"action":"save_theme","theme":"ocean"}
2026-03-18 14:09:10 - POST: {"action":"save_theme","theme":"forest"}
2026-03-18 14:09:12 - POST: {"action":"save_theme","theme":"dracula"}
2026-03-18 14:09:15 - POST: {"action":"save_theme","theme":"default"}
2026-03-18 18:14:49 - POST: {"import_items":""}
2026-03-18 18:15:31 - POST: {"import_items":""}
2026-03-18 18:24:49 - POST: {"import_items":""}
2026-03-18 18:26:54 - POST: {"import_items":""}
2026-03-18 18:29:45 - POST: {"import_items":""}
2026-03-18 18:30:17 - POST: {"action":"translate","text":"LAMING RED KIDNEY BEANS ","target":"ar"}
2026-03-18 18:30:49 - POST: {"id":"62","name_en":"LAMING RED KIDNEY BEANS ","name_ar":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627\u0621 \u062d\u0645\u0631\u0627\u0621 \u0643\u064a\u062f\u0646\u064a \u0644\u0627\u0645\u064a\u0646\u063a","sku":"000023071605","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"0","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-18 18:31:04 - POST: {"action":"translate","text":"LAMING PINEAPPLE 454G","target":"ar"}
2026-03-18 18:31:15 - POST: {"id":"61","name_en":"LAMING PINEAPPLE 454G","name_ar":"\u0623\u0646\u0627\u0646\u0627\u0633 \u0644\u0627\u0645\u0646\u062c 454\u063a","sku":"000023071599","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.4","purchase_price":"0.346","stock_quantity":"0","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-18 18:31:26 - POST: {"action":"translate","text":"\u0644\u0627\u0645\u064a\u0646\u063a \u0641\u0637\u0631 \u0637\u0627\u0632\u062c \u0643\u0627\u0645\u0644425\u063a","target":"en"}
2026-03-18 18:31:34 - POST: {"id":"60","name_en":"Laming Whole Fresh Mushroom 425g","name_ar":"\u0644\u0627\u0645\u064a\u0646\u063a \u0641\u0637\u0631 \u0637\u0627\u0632\u062c \u0643\u0627\u0645\u0644425\u063a","sku":"000023071582","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.55","purchase_price":"0.425","stock_quantity":"0","min_stock_level":"0","vat_rate":"0.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-18 18:31:54 - POST: {"action":"translate","text":"LAMING FOUL MEDAMES 397G","target":"ar"}
2026-03-18 18:32:01 - POST: {"id":"59","name_en":"LAMING FOUL MEDAMES 397G","name_ar":"\u0641\u0648\u0644 \u0645\u062f\u0645\u0633 \u0644\u0627\u0645\u064a\u0646\u062c 397\u063a","sku":"000023071575","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.2","purchase_price":"0.157","stock_quantity":"0","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-18 18:32:15 - POST: {"action":"translate","text":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627 \u0644\u0627\u0645\u064a\u0646\u062c227\u062c\u0631\u0627\u0645","target":"en"}
2026-03-18 18:32:28 - POST: {"id":"58","name_en":"Leming Beans 227g","name_ar":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627 \u0644\u0627\u0645\u064a\u0646\u062c227\u062c\u0631\u0627\u0645","sku":"000023071568","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.15","purchase_price":"0.111","stock_quantity":"0","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-18 18:40:45 - POST: {"id":"62","name_en":"LAMING RED KIDNEY BEANS ","name_ar":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627\u0621 \u062d\u0645\u0631\u0627\u0621 \u0643\u064a\u062f\u0646\u064a \u0644\u0627\u0645\u064a\u0646\u063a","sku":"000023071605","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"100","min_stock_level":"0","vat_rate":"5.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-18 18:41:00 - POST: {"id":"61","name_en":"LAMING PINEAPPLE 454G","name_ar":"\u0623\u0646\u0627\u0646\u0627\u0633 \u0644\u0627\u0645\u0646\u062c 454\u063a","sku":"000023071599","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.4","purchase_price":"0.346","stock_quantity":"50","min_stock_level":"0","vat_rate":"5.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-19 01:57:45 - POST: {"import_items":""}
2026-03-19 01:58:17 - POST: {"action":"translate","text":"LAMING RED KIDNEY BEANS 425","target":"ar"}
2026-03-19 01:58:33 - POST: {"id":"117","name_en":"LAMING RED KIDNEY BEANS 425","name_ar":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627 \u062d\u0645\u0631\u0627\u0621 \u0643\u064a\u062f\u0646\u064a \u0644\u0627\u0645\u064a\u0646\u062c 425","sku":"000023071605","category_id":"","unit_id":"","supplier_id":"","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"0","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-19 01:58:51 - POST: {"action":"translate","text":"LAMING PINEAPPLE 454G","target":"ar"}
2026-03-19 01:59:00 - POST: {"id":"116","name_en":"LAMING PINEAPPLE 454G","name_ar":"\u0623\u0646\u0627\u0646\u0627\u0633 \u0644\u0627\u0645\u064a\u0646\u063a 454 \u062c\u0631\u0627\u0645","sku":"000023071599","category_id":"","unit_id":"","supplier_id":"","sale_price":"0.4","purchase_price":"0.346","stock_quantity":"55","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-19 06:12:40 - POST: {"close_register":"1","session_id":"11","cash_in_hand":"10","notes":""}
2026-03-19 06:27:02 - POST: {"open_register":"1","register_id":"3","opening_balance":"0"}
2026-03-19 06:27:40 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":25.4}]","total_amount":"25.4","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":18,\"qty\":1,\"price\":0.6,\"vat_rate\":0,\"vat_amount\":0},{\"id\":17,\"qty\":2,\"price\":0.4,\"vat_rate\":0,\"vat_amount\":0},{\"id\":16,\"qty\":1,\"price\":1.5,\"vat_rate\":0,\"vat_amount\":0},{\"id\":22,\"qty\":5,\"price\":1,\"vat_rate\":0,\"vat_amount\":0},{\"id\":23,\"qty\":1,\"price\":1.2,\"vat_rate\":0,\"vat_amount\":0},{\"id\":21,\"qty\":3,\"price\":1.6,\"vat_rate\":0,\"vat_amount\":0},{\"id\":20,\"qty\":1,\"price\":0.9,\"vat_rate\":0,\"vat_amount\":0},{\"id\":14,\"qty\":1,\"price\":0.2,\"vat_rate\":0,\"vat_amount\":0},{\"id\":8,\"qty\":1,\"price\":1.2,\"vat_rate\":0,\"vat_amount\":0},{\"id\":9,\"qty\":1,\"price\":0.15,\"vat_rate\":0,\"vat_amount\":0},{\"id\":15,\"qty\":1,\"price\":2.55,\"vat_rate\":0,\"vat_amount\":0},{\"id\":29,\"qty\":1,\"price\":2.5,\"vat_rate\":0,\"vat_amount\":0},{\"id\":53,\"qty\":1,\"price\":0.15,\"vat_rate\":0,\"vat_amount\":0},{\"id\":33,\"qty\":1,\"price\":1.3,\"vat_rate\":0,\"vat_amount\":0},{\"id\":70,\"qty\":1,\"price\":2.55,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-19 06:30:20 - POST: {"close_register":"1","redirect_to":"dashboard","session_id":"12","cash_in_hand":"33","notes":""}
2026-03-19 06:38:24 - POST: {"close_register":"1","redirect_to":"dashboard","session_id":"12","cash_in_hand":"33","notes":""}
2026-03-19 06:40:40 - POST: {"open_register":"1","register_id":"3","opening_balance":"3"}
2026-03-19 06:40:49 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.2}]","total_amount":"0.2","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":13,\"qty\":1,\"price\":0.2,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-19 06:41:01 - POST: {"close_register":"1","redirect_to":"dashboard","session_id":"13","cash_in_hand":"3","notes":""}
2026-03-19 07:58:11 - POST: {"open_register":"1","register_id":"3","opening_balance":"32"}
2026-03-19 07:58:27 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":11.25}]","total_amount":"11.249999999999998","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":8,\"qty\":1,\"price\":1.2,\"vat_rate\":0,\"vat_amount\":0},{\"id\":15,\"qty\":1,\"price\":2.55,\"vat_rate\":0,\"vat_amount\":0},{\"id\":21,\"qty\":2,\"price\":1.6,\"vat_rate\":0,\"vat_amount\":0},{\"id\":22,\"qty\":2,\"price\":1,\"vat_rate\":0,\"vat_amount\":0},{\"id\":30,\"qty\":1,\"price\":1.1,\"vat_rate\":0,\"vat_amount\":0},{\"id\":23,\"qty\":1,\"price\":1.2,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-03-19 07:59:22 - POST: {"close_register":"1","redirect_to":"dashboard","session_id":"14","cash_in_hand":"20","notes":""}
2026-03-19 09:22:52 - POST: {"action":"save_theme","theme":"ocean"}
2026-03-19 09:22:56 - POST: {"action":"save_theme","theme":"dark"}
2026-03-19 09:22:58 - POST: {"action":"save_theme","theme":"default"}
2026-03-19 09:23:00 - POST: {"action":"save_theme","theme":"dracula"}
2026-03-19 09:23:02 - POST: {"action":"save_theme","theme":"sunset"}
2026-03-19 13:46:44 - POST: {"create_backup":""}
2026-03-19 13:48:49 - POST: {"open_register":"1","register_id":"3","opening_balance":"0"}
2026-03-20 04:59:21 - POST: {"action":"translate","text":"Apple","target":"ar"}
2026-03-20 04:59:40 - POST: {"action":"translate","text":"Apple","target":"ar"}
2026-03-20 05:00:37 - POST: {"action":"translate","text":"Apple","target":"ar"}
2026-03-20 05:01:08 - POST: {"action":"translate","text":"Apple","target":"ar"}
2026-03-20 05:01:47 - POST: {"action":"translate","text":"Apple","target":"ar"}
2026-03-20 05:25:51 - POST: {"action":"save_theme","theme":"nord"}
2026-03-20 05:25:54 - POST: {"action":"save_theme","theme":"sunset"}
2026-03-20 05:25:56 - POST: {"action":"save_theme","theme":"citrus"}
2026-03-20 05:25:59 - POST: {"action":"save_theme","theme":"dark"}
2026-03-20 05:26:01 - POST: {"action":"save_theme","theme":"ocean"}
2026-03-20 05:26:03 - POST: {"action":"save_theme","theme":"default"}
2026-03-20 17:46:35 - POST: {"action":"translate","text":"LAMING TOMATO PASTE 800G","target":"ar"}
2026-03-20 17:46:43 - POST: {"id":"57","name_en":"LAMING TOMATO PASTE 800G","name_ar":"\u0645\u0639\u062c\u0648\u0646 \u0637\u0645\u0627\u0637\u0645 \u0644\u0627\u0645\u064a\u0646\u062c 800 \u062c\u0631\u0627\u0645","sku":"000023071476","category_id":"","unit_id":"","supplier_id":"","sale_price":"0.5","purchase_price":"0.392","stock_quantity":"0","min_stock_level":"0","vat_rate":"0.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-20 17:46:56 - POST: {"action":"translate","text":"TOMATO PASTE","target":"ar"}
2026-03-20 17:47:03 - POST: {"id":"56","name_en":"TOMATO PASTE","name_ar":"\u0645\u0639\u062c\u0648\u0646 \u0627\u0644\u0637\u0645\u0627\u0637\u0645","sku":"000023071469","category_id":"","unit_id":"","supplier_id":"","sale_price":"0.2","purchase_price":"0.11","stock_quantity":"0","min_stock_level":"0","vat_rate":"0.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-20 17:47:15 - POST: {"action":"translate","text":"\u0646\u0634\u0627 400\u062c\u0645","target":"en"}
2026-03-20 17:47:23 - POST: {"id":"55","name_en":"Starch 400g","name_ar":"\u0646\u0634\u0627 400\u062c\u0645","sku":"000023028982","category_id":"","unit_id":"","supplier_id":"","sale_price":"0.35","purchase_price":"0.263","stock_quantity":"0","min_stock_level":"0","vat_rate":"0.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-20 17:56:30 - POST: {"action":"translate","text":"\u0644\u064a\u0641 \u062d\u062f\u064a\u062f 3*1","target":"en"}
2026-03-20 17:56:38 - POST: {"id":"14","name_en":"Iron Wool 3*1","name_ar":"\u0644\u064a\u0641 \u062d\u062f\u064a\u062f 3*1","sku":"000001404159","category_id":"","unit_id":"","supplier_id":"","sale_price":"0.2","purchase_price":"0.125","stock_quantity":"-1","min_stock_level":"0","vat_rate":"0.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-20 17:56:47 - POST: {"action":"translate","text":"\u0644\u064a\u0641 \u062d\u062f\u064a\u062f \u0630\u0647\u0628\u064a 3*1","target":"en"}
2026-03-20 17:56:51 - POST: {"id":"13","name_en":"Gold Iron Sponge 3*1","name_ar":"\u0644\u064a\u0641 \u062d\u062f\u064a\u062f \u0630\u0647\u0628\u064a 3*1","sku":"000001404142","category_id":"","unit_id":"","supplier_id":"","sale_price":"0.2","purchase_price":"0.125","stock_quantity":"-1","min_stock_level":"0","vat_rate":"0.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-20 18:02:45 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.7}]","total_amount":"0.7","tax_amount":"0.023809523809523808","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":56,\"qty\":1,\"price\":0.2,\"vat_rate\":0,\"vat_amount\":0},{\"id\":62,\"qty\":2,\"price\":0.25,\"vat_rate\":5,\"vat_amount\":0.023809523809523808}]"}
2026-03-20 18:25:38 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":3.15}]","total_amount":"3.15","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":36,\"qty\":1,\"price\":1.45,\"vat_rate\":0,\"vat_amount\":0},{\"id\":18,\"qty\":1,\"price\":0.6,\"vat_rate\":0,\"vat_amount\":0},{\"id\":30,\"qty\":1,\"price\":1.1,\"vat_rate\":0,\"vat_amount\":0}]"}
2026-04-09 04:06:44 - POST: {"start_trial":""}
2026-04-09 04:06:58 - POST: {"username":"admin","password":"admin","login":""}
2026-04-09 04:08:44 - POST: {"username":"admin","password":"admin","login":"1"}
2026-04-09 04:46:43 - POST: {"username":"admin","password":"admin","login":""}
2026-05-01 19:22:46 - POST: {"start_trial":"1"}
2026-05-01 19:22:46 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-01 19:32:47 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 02:56:23 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 03:00:39 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 03:32:58 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:46:38 - POST: {"username":"moosa","password":"moosa123","login":""}
2026-05-02 04:46:47 - POST: {"username":"admin","password":"admin123","login":""}
2026-05-02 04:46:55 - POST: {"username":"admin","password":"admin","login":""}
2026-05-02 04:50:45 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:50:50 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:50:50 - POST: {"name_en":"Wallet","name_ar":"\u0645\u062d\u0641\u0638\u0629","add_payment_method":"1"}
2026-05-02 04:54:23 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:54:23 - POST: {"name_en":"QA Wallet","name_ar":"\u0645\u062d\u0641\u0638\u0629 \u062a\u062c\u0631\u064a\u0628\u064a\u0629","add_payment_method":"1"}
2026-05-02 04:54:32 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:54:39 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:54:45 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:54:49 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:54:54 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:55:00 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:55:44 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:55:52 - POST: {"username":"admin","password":"admin","login":"1"}
2026-05-02 04:55:52 - POST: {"name_en":"QA Wallet","name_ar":"\u0645\u062d\u0641\u0638\u0629 \u062a\u062c\u0631\u064a\u0628\u064a\u0629","add_payment_method":"1"}
2026-05-02 04:55:52 - POST: {"id":"5","name_en":"QA Wallet Updated","name_ar":"\u0645\u062d\u0641\u0638\u0629 \u0645\u062d\u062f\u062b\u0629","edit_payment_method":"1"}
2026-05-02 04:55:52 - POST: {"id":"5","delete_payment_method":"1"}
2026-05-02 04:56:04 - POST: {"username":"admin","password":"admin","login":"1"}
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"}
2026-05-02 18:40:57 - POST: {"name":"\u0645\u062d\u0627\u0633\u0628 1","add_cash_register":""}
2026-05-02 18:41:32 - POST: {"id":"1","name":"\u0627\u0644\u0641\u0631\u0639 \u0627\u0644\u0631\u0626\u064a\u0633\u064a","phone":"","address":"Head Office","status":"active","edit_outlet":""}
2026-05-02 19:20:30 - POST: {"license_key":"BACC-F7B0-A44F-2AC2","activate":"1"}
2026-05-03 02:27:31 - POST: {"seed_default_accounts":""}

View File

@ -1,10 +0,0 @@
import os
file_path = 'index.php'
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Fix the syntax error
search_bad = 'echo "<option value=\"$tz\" $selected>$tz</option>";'
replace_good = 'echo "<option value=\\\

View File

@ -1,7 +0,0 @@
2026-02-25 04:49:58 - search_items call: q=on
2026-02-25 12:57:14 - search_items call: q=on
2026-02-25 17:10:03 - search_items call: q=on
2026-03-18 10:30:10 - search_items call: q=to
2026-03-18 10:30:56 - search_items call: q=to
2026-03-18 13:46:59 - search_items call: q=to
2026-03-18 13:53:33 - search_items call: q=to

View File

@ -1,44 +0,0 @@
<?php
$_POST['import_items'] = true;
$_FILES['excel_file'] = [
'name' => 'test_import.csv',
'type' => 'text/csv',
'tmp_name' => __DIR__ . '/test_import.csv',
'error' => 0,
'size' => filesize(__DIR__ . '/test_import.csv')
];
// Define db() helper if not defined (it should be in db/config.php)
require_once __DIR__ . '/db/config.php';
// Include the logic from index.php
// I'll copy the logic here to avoid including the whole index.php which might have side effects
$tmpPath = $_FILES['excel_file']['tmp_name'];
$handle = fopen($tmpPath, "r");
$firstRow = fgetcsv($handle, 0, ","); // Skip header
$count = 0; $errors = 0;
while (($data = fgetcsv($handle, 0, ",")) !== FALSE) {
if (count($data) < 2) continue;
try {
$sku = substr(trim($data[0] ?? ''), 0, 100);
$name_en = $data[1] ?? '';
$name_ar = $data[2] ?? '';
$sale_price = str_replace(',', '', $data[3] ?? 0);
$purchase_price = str_replace(',', '', $data[4] ?? 0);
if (empty($sku) && empty($name_en)) continue;
$stmt = db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, purchase_price)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE name_en=VALUES(name_en), name_ar=VALUES(name_ar),
sale_price=VALUES(sale_price), purchase_price=VALUES(purchase_price)");
$stmt->execute([$sku, $name_en, $name_ar, (float)$sale_price, (float)$purchase_price]);
echo "Imported: $sku\n";
$count++;
} catch (Throwable $e) {
echo "Error: " . $e->getMessage() . "\n";
$errors++;
}
}
fclose($handle);
echo "Finished. Success: $count, Errors: $errors\n";

View File

@ -1,24 +0,0 @@
<?php
// Mock session
session_start();
$_SESSION['user_id'] = 1;
$_SESSION['user_role_name'] = 'Administrator';
$_SESSION['user_permissions'] = 'all';
// Simulate GET request
$_GET['page'] = 'accounting';
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$_SERVER['HTTPS'] = 'on';
// Capture output
ob_start();
try {
require 'index.php';
} catch (Throwable $e) {
echo "\nFATAL ERROR: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine() . "\n";
}
$output = ob_get_clean();
file_put_contents('output_test.html', $output);
echo "Output saved to output_test.html (" . strlen($output) . " bytes)\n";

View File

@ -1,9 +0,0 @@
<?php
$path = '/home/ubuntu/executor/.env';
$env = @parse_ini_file($path);
echo "Raw PROJECT_UUID from .env: " . ($env['PROJECT_UUID'] ?? 'MISSING') . "\n";
echo "Raw PROJECT_ID from .env: " . ($env['PROJECT_ID'] ?? 'MISSING') . "\n";
$cfg = require __DIR__ . '/ai/config.php';
echo "Final Project UUID: [" . ($cfg['project_uuid'] ?? 'MISSING') . "]\n";
echo "Final Project ID: [" . ($cfg['project_id'] ?? 'MISSING') . "]\n";

View File

@ -1,22 +0,0 @@
<?php
require_once 'db/config.php';
try {
$db = db();
echo "Testing role_groups insert...\n";
$stmt = $db->prepare("INSERT INTO role_groups (name, permissions) VALUES (?, ?)");
$stmt->execute(['Test Group', json_encode(['dashboard_view'])]);
$groupId = $db->lastInsertId();
echo "Inserted Group ID: $groupId\n";
echo "Testing users insert...\n";
$stmt = $db->prepare("INSERT INTO users (username, password, email, group_id) VALUES (?, ?, ?, ?)");
$stmt->execute(['testuser_' . time(), password_hash('password', PASSWORD_DEFAULT), 'test@example.com', $groupId]);
echo "Inserted User ID: " . $db->lastInsertId() . "\n";
echo "Cleanup...\n";
$db->prepare("DELETE FROM users WHERE username LIKE 'testuser_%'")->execute();
$db->prepare("DELETE FROM role_groups WHERE name = 'Test Group'")->execute();
echo "Done.\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}

View File

@ -1,3 +0,0 @@
SKU,English Name,Arabic Name,Sale Price,Cost Price
ITEM1,Product 1,منتج 1,10.00,5.00
ITEM2,Product 2,,15.00,7.00
1 SKU English Name Arabic Name Sale Price Cost Price
2 ITEM1 Product 1 منتج 1 10.00 5.00
3 ITEM2 Product 2 15.00 7.00

View File

@ -1,15 +0,0 @@
<?php
require_once 'db/config.php';
$q = 'Tomato';
$searchTerm = "%$q%";
$stmt = db()->prepare("SELECT * FROM stock_items WHERE name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ? LIMIT 15");
$stmt->execute([$searchTerm, $searchTerm, $searchTerm]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "Results for Tomato: " . count($results) . "\n";
print_r($results);
$q = 'INV-00001';
$searchTerm = "%$q%";
$stmt->execute([$searchTerm, $searchTerm, $searchTerm]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "Results for INV-00001: " . count($results) . "\n";