diff --git a/assets/css/custom.css b/assets/css/custom.css index 8359aed..166579e 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -104,7 +104,7 @@ body { .sidebar { width: var(--sidebar-width); height: 100vh; - background-color: var(--primary); + background-color: #0f172a; /* Updated to match system primary dark blue */ color: white; position: fixed; top: 0; @@ -145,11 +145,11 @@ body { padding: 1rem; font-size: 1.15rem; font-weight: 700; - border-bottom: 1px solid var(--primary-light); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); /* Lighter border for blue bg */ } .nav-link { - color: #cbd5e1; + color: rgba(255, 255, 255, 0.9); /* Lighter text for blue bg */ padding: 0.6rem 1rem; display: flex; align-items: center; @@ -158,7 +158,7 @@ body { } .nav-link:hover, .nav-link.active { - background-color: var(--primary-light); + background-color: rgba(255, 255, 255, 0.1); /* Subtle hover for blue bg */ color: white; } @@ -170,7 +170,7 @@ body { .nav-section-title { font-size: 0.85rem; font-weight: 600; - color: #64748b !important; + color: rgba(255, 255, 255, 0.7) !important; /* Lighter text for blue bg */ letter-spacing: 0.05em; cursor: pointer; display: flex; @@ -179,7 +179,7 @@ body { } .nav-section-title:hover { - color: #94a3b8 !important; + color: white !important; } .nav-section-title i.chevron { @@ -655,3 +655,62 @@ body:not(.theme-default) .form-select:focus { margin-top: 15px; padding-top: 15px; } + +/* Styled Modal Headers */ +.modal-header, .card-header { + background-color: var(--accent); + color: white; + border-bottom: 1px solid var(--border); +} +.modal-title, .card-title { + color: white !important; +} +.modal-header .btn-close { + filter: invert(1) grayscale(100%) brightness(200%); +} + +/* 3-Column Form Grid */ +.form-grid-3 { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} + +@media (max-width: 992px) { + .form-grid-3 { + grid-template-columns: repeat(2, 1fr); + } +} +@media (max-width: 768px) { + .form-grid-3 { + grid-template-columns: 1fr; + } +} + +.form-grid-3 > div { + width: 100% !important; /* Override bootstrap cols if present */ + max-width: 100% !important; + flex: none !important; +} + +/* Helpers to span columns in grid */ +.form-grid-3 > .span-2 { grid-column: span 2; } +.form-grid-3 > .span-3 { grid-column: span 3; } +.form-grid-3 > .full-width { grid-column: 1 / -1; } + +/* Handle existing bootstrap classes in grid */ +.form-grid-3 > .col-12, +.form-grid-3 > .col-md-12 { + grid-column: 1 / -1; +} + +.form-grid-3 > .col-md-8 { + grid-column: span 2; +} + +/* Ensure form controls take full width of their grid cell */ +.form-grid-3 .form-control, +.form-grid-3 .form-select, +.form-grid-3 .input-group { + width: 100%; +} \ No newline at end of file diff --git a/debug.log b/debug.log index 8f32168..abe5875 100644 --- a/debug.log +++ b/debug.log @@ -19,3 +19,38 @@ 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 diff --git a/debug_accounting.php b/debug_accounting.php new file mode 100644 index 0000000..11ca168 --- /dev/null +++ b/debug_accounting.php @@ -0,0 +1,18 @@ +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"; +} + diff --git a/fix_accounting_bug.php b/fix_accounting_bug.php new file mode 100644 index 0000000..2781e42 --- /dev/null +++ b/fix_accounting_bug.php @@ -0,0 +1,51 @@ +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"; + } +} \ No newline at end of file diff --git a/fix_forms.py b/fix_forms.py new file mode 100644 index 0000000..076e3ff --- /dev/null +++ b/fix_forms.py @@ -0,0 +1,149 @@ +import re + +file_path = 'index.php' + +with open(file_path, 'r') as f: + content = f.read() + +# 1. Replace
with
+# 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
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: + + + + + + + + + + diff --git a/pages/accounting_logic.php b/pages/accounting_logic.php new file mode 100644 index 0000000..bdc137a --- /dev/null +++ b/pages/accounting_logic.php @@ -0,0 +1,60 @@ +query("SELECT COUNT(*) FROM acc_journal_entries")->fetchColumn(); +$data['total_pages'] = ceil($total_entries / $limit); +$data['current_page'] = $page_num; + +$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(); +$data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll(); + +if (isset($_GET['action']) && $_GET['action'] === 'get_entry_details') { + header('Content-Type: application/json'); + $id = (int)$_GET['id']; + $stmt = db()->prepare("SELECT l.*, a.name_en, a.code FROM acc_ledger l JOIN acc_accounts a ON l.account_id = a.id WHERE l.journal_entry_id = ?"); + $stmt->execute([$id]); + echo json_encode($stmt->fetchAll()); + exit; +} + +if (isset($_GET['view']) && $_GET['view'] === 'trial_balance') { + $data['trial_balance'] = db()->query("SELECT a.code, a.name_en, SUM(l.debit) as total_debit, SUM(l.credit) as total_credit + FROM acc_accounts a + LEFT JOIN acc_ledger l ON a.id = l.account_id + GROUP BY a.id + HAVING total_debit > 0 OR total_credit > 0 + ORDER BY a.code ASC")->fetchAll(); +} + +if (isset($_GET['view']) && $_GET['view'] === 'profit_loss') { + $data['revenue_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'revenue' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll(); + $data['expense_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'expense' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll(); +} + +if (isset($_GET['view']) && $_GET['view'] === 'balance_sheet') { + $data['asset_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'asset' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll(); + $data['liability_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'liability' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll(); + $data['equity_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'equity' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll(); +} + +if (isset($_GET['view']) && $_GET['view'] === 'vat_report') { + $start = $_GET['start_date'] ?? date('Y-m-01'); + $end = $_GET['end_date'] ?? date('Y-m-d'); + $data['vat_report'] = getVatReport($start, $end); + $data['start_date'] = $start; + $data['end_date'] = $end; +} + +if (isset($_GET['view']) && $_GET['view'] === 'coa') { + $data['coa'] = db()->query("SELECT a.*, p.name_en as parent_name + FROM acc_accounts a + LEFT JOIN acc_accounts p ON a.parent_id = p.id + ORDER BY a.code ASC")->fetchAll(); +} diff --git a/pages/accounting_view.php b/pages/accounting_view.php new file mode 100644 index 0000000..309900b --- /dev/null +++ b/pages/accounting_view.php @@ -0,0 +1,363 @@ +
+
+
Accounting Module
+
+
+ +
+ + + +
+
+ +
+
+
+

+

+
+
+

+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
DateDescriptionReferenceAmountAction
+ +
+
+ + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
CodeNameTypeParentBalance
+
+ +
+
+ + + + +
+
+
+
+ + +
+ +
+
+ +
+
+ +
+
+
+
+
+

VAT Summary Report

+ + + + + + + + + + + + + +
VAT Input (Purchases)
VAT Output (Sales)
Net VAT Payable / (Refundable)
+
+ + This report calculates the difference between VAT collected on sales and VAT paid on purchases for the selected period. +
+
+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeAccount NameDebitCredit
Total
+
+ +
+
+
+
+

Profit & Loss Statement

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Revenue
Total Revenue
Expenses
Total Expenses
Net Profit / Loss
+
+
+
+
+ +
+
+
+
+

Balance Sheet

+
+
+
Assets
+ + + + + + + + + + + +
Total Assets
+
+
+
Liabilities & Equity
+ + + + + + + + + + + + + + + + + + query("SELECT code FROM acc_accounts WHERE type='revenue' AND parent_id IS NOT NULL")->fetchAll() as $a) $rev += getAccountBalance($a['code']); + $exp = 0; foreach(db()->query("SELECT code FROM acc_accounts WHERE type='expense' AND parent_id IS NOT NULL")->fetchAll() as $a) $exp += getAccountBalance($a['code']); + $earnings = $rev - $exp; + $total_equity += $earnings; + ?> + + + + + + + + + +
Liabilities
Equity
Retained Earnings (Current)
Total Liab. & Equity
+
+
+
+
+
+
+ +
+ + + + + diff --git a/post_debug.log b/post_debug.log index c7a9d0c..3a40a6a 100644 --- a/post_debug.log +++ b/post_debug.log @@ -52,3 +52,12 @@ 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"} diff --git a/test_accounting_page.php b/test_accounting_page.php new file mode 100644 index 0000000..52e9352 --- /dev/null +++ b/test_accounting_page.php @@ -0,0 +1,24 @@ +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";