update accounts
This commit is contained in:
parent
ea717c8abf
commit
9108deb2d9
9
db/migrations/20260503_seed_chart_of_accounts.php
Normal file
9
db/migrations/20260503_seed_chart_of_accounts.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once dirname(__DIR__) . '/config.php';
|
||||||
|
require_once dirname(__DIR__, 2) . '/includes/accounting_helper.php';
|
||||||
|
|
||||||
|
if (function_exists('seedDefaultAccountingAccounts')) {
|
||||||
|
seedDefaultAccountingAccounts();
|
||||||
|
}
|
||||||
@ -3,6 +3,176 @@
|
|||||||
* Accounting Helper for Automatic Journal Entries
|
* Accounting Helper for Automatic Journal Entries
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
function accountingTableExists(string $tableName): bool {
|
||||||
|
static $cache = [];
|
||||||
|
|
||||||
|
$normalized = strtolower($tableName);
|
||||||
|
if (array_key_exists($normalized, $cache)) {
|
||||||
|
return $cache[$normalized];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare(
|
||||||
|
"SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute([$tableName]);
|
||||||
|
$cache[$normalized] = (bool)$stmt->fetchColumn();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('Accounting table check failed: ' . $e->getMessage());
|
||||||
|
$cache[$normalized] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cache[$normalized];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultAccountingAccounts(): array {
|
||||||
|
return [
|
||||||
|
['code' => '1000', 'name_en' => 'Assets', 'name_ar' => 'الأصول', 'type' => 'asset', 'parent_code' => null],
|
||||||
|
['code' => '1100', 'name_en' => 'Cash on Hand', 'name_ar' => 'النقدية بالصندوق', 'type' => 'asset', 'parent_code' => '1000'],
|
||||||
|
['code' => '1110', 'name_en' => 'Petty Cash', 'name_ar' => 'العهدة النقدية', 'type' => 'asset', 'parent_code' => '1000'],
|
||||||
|
['code' => '1200', 'name_en' => 'Bank Account', 'name_ar' => 'حساب البنك', 'type' => 'asset', 'parent_code' => '1000'],
|
||||||
|
['code' => '1300', 'name_en' => 'Accounts Receivable', 'name_ar' => 'ذمم العملاء', 'type' => 'asset', 'parent_code' => '1000'],
|
||||||
|
['code' => '1400', 'name_en' => 'Inventory', 'name_ar' => 'المخزون', 'type' => 'asset', 'parent_code' => '1000'],
|
||||||
|
['code' => '1500', 'name_en' => 'VAT Input', 'name_ar' => 'ضريبة المدخلات', 'type' => 'asset', 'parent_code' => '1000'],
|
||||||
|
['code' => '1600', 'name_en' => 'Fixed Assets', 'name_ar' => 'الأصول الثابتة', 'type' => 'asset', 'parent_code' => '1000'],
|
||||||
|
['code' => '1700', 'name_en' => 'Prepaid Expenses', 'name_ar' => 'المصروفات المقدمة', 'type' => 'asset', 'parent_code' => '1000'],
|
||||||
|
['code' => '2000', 'name_en' => 'Liabilities', 'name_ar' => 'الالتزامات', 'type' => 'liability', 'parent_code' => null],
|
||||||
|
['code' => '2100', 'name_en' => 'Accounts Payable', 'name_ar' => 'ذمم الموردين', 'type' => 'liability', 'parent_code' => '2000'],
|
||||||
|
['code' => '2200', 'name_en' => 'Payroll Liabilities', 'name_ar' => 'التزامات الرواتب', 'type' => 'liability', 'parent_code' => '2000'],
|
||||||
|
['code' => '2300', 'name_en' => 'VAT Payable', 'name_ar' => 'ضريبة المخرجات', 'type' => 'liability', 'parent_code' => '2000'],
|
||||||
|
['code' => '2400', 'name_en' => 'Accrued Expenses', 'name_ar' => 'مصروفات مستحقة', 'type' => 'liability', 'parent_code' => '2000'],
|
||||||
|
['code' => '3000', 'name_en' => 'Equity', 'name_ar' => 'حقوق الملكية', 'type' => 'equity', 'parent_code' => null],
|
||||||
|
['code' => '3100', 'name_en' => 'Owner Capital', 'name_ar' => 'رأس المال', 'type' => 'equity', 'parent_code' => '3000'],
|
||||||
|
['code' => '3200', 'name_en' => 'Retained Earnings', 'name_ar' => 'الأرباح المحتجزة', 'type' => 'equity', 'parent_code' => '3000'],
|
||||||
|
['code' => '4000', 'name_en' => 'Revenue', 'name_ar' => 'الإيرادات', 'type' => 'revenue', 'parent_code' => null],
|
||||||
|
['code' => '4100', 'name_en' => 'Sales Revenue', 'name_ar' => 'إيرادات المبيعات', 'type' => 'revenue', 'parent_code' => '4000'],
|
||||||
|
['code' => '4200', 'name_en' => 'Service Revenue', 'name_ar' => 'إيرادات الخدمات', 'type' => 'revenue', 'parent_code' => '4000'],
|
||||||
|
['code' => '5000', 'name_en' => 'Expenses', 'name_ar' => 'المصروفات', 'type' => 'expense', 'parent_code' => null],
|
||||||
|
['code' => '5100', 'name_en' => 'Cost of Goods Sold', 'name_ar' => 'تكلفة البضاعة المباعة', 'type' => 'expense', 'parent_code' => '5000'],
|
||||||
|
['code' => '5200', 'name_en' => 'Operating Expenses', 'name_ar' => 'المصروفات التشغيلية', 'type' => 'expense', 'parent_code' => '5000'],
|
||||||
|
['code' => '5300', 'name_en' => 'Payroll Expenses', 'name_ar' => 'مصروفات الرواتب', 'type' => 'expense', 'parent_code' => '5000'],
|
||||||
|
['code' => '5400', 'name_en' => 'Rent Expense', 'name_ar' => 'مصروف الإيجار', 'type' => 'expense', 'parent_code' => '5000'],
|
||||||
|
['code' => '5500', 'name_en' => 'Utilities Expense', 'name_ar' => 'مصروف المرافق', 'type' => 'expense', 'parent_code' => '5000'],
|
||||||
|
['code' => '5600', 'name_en' => 'Marketing Expense', 'name_ar' => 'مصروف التسويق', 'type' => 'expense', 'parent_code' => '5000'],
|
||||||
|
['code' => '5700', 'name_en' => 'Transport Expense', 'name_ar' => 'مصروف النقل', 'type' => 'expense', 'parent_code' => '5000'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function seedDefaultAccountingAccounts(): int {
|
||||||
|
if (!accountingTableExists('acc_accounts')) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$accounts = getDefaultAccountingAccounts();
|
||||||
|
$inserted = 0;
|
||||||
|
$startedTransaction = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!$db->inTransaction()) {
|
||||||
|
$db->beginTransaction();
|
||||||
|
$startedTransaction = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectId = $db->prepare("SELECT id FROM acc_accounts WHERE code = ? LIMIT 1");
|
||||||
|
$insert = $db->prepare(
|
||||||
|
"INSERT INTO acc_accounts (code, name_en, name_ar, type, parent_id) VALUES (?, ?, ?, ?, NULL)"
|
||||||
|
);
|
||||||
|
$updateParent = $db->prepare("UPDATE acc_accounts SET parent_id = ? WHERE code = ?");
|
||||||
|
|
||||||
|
foreach ($accounts as $account) {
|
||||||
|
$selectId->execute([$account['code']]);
|
||||||
|
if (!$selectId->fetchColumn()) {
|
||||||
|
$insert->execute([
|
||||||
|
$account['code'],
|
||||||
|
$account['name_en'],
|
||||||
|
$account['name_ar'],
|
||||||
|
$account['type'],
|
||||||
|
]);
|
||||||
|
$inserted++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($accounts as $account) {
|
||||||
|
if (empty($account['parent_code'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectId->execute([$account['parent_code']]);
|
||||||
|
$parentId = $selectId->fetchColumn();
|
||||||
|
if ($parentId) {
|
||||||
|
$updateParent->execute([(int)$parentId, $account['code']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($startedTransaction) {
|
||||||
|
$db->commit();
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($startedTransaction && $db->inTransaction()) {
|
||||||
|
$db->rollBack();
|
||||||
|
}
|
||||||
|
error_log('Accounting seed failed: ' . $e->getMessage());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAccountingAccount(string $code, string $nameEn, string $nameAr, string $type, ?int $parentId = null): array {
|
||||||
|
if (!accountingTableExists('acc_accounts')) {
|
||||||
|
return ['success' => false, 'error' => 'Accounting tables are not ready yet.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = strtoupper(trim($code));
|
||||||
|
$nameEn = trim($nameEn);
|
||||||
|
$nameAr = trim($nameAr);
|
||||||
|
$allowedTypes = ['asset', 'liability', 'equity', 'revenue', 'expense'];
|
||||||
|
|
||||||
|
if ($code === '' || !preg_match('/^[A-Z0-9._-]{1,20}$/', $code)) {
|
||||||
|
return ['success' => false, 'error' => 'Please enter a valid unique account code.'];
|
||||||
|
}
|
||||||
|
if ($nameEn === '' || $nameAr === '') {
|
||||||
|
return ['success' => false, 'error' => 'English and Arabic account names are required.'];
|
||||||
|
}
|
||||||
|
if (!in_array($type, $allowedTypes, true)) {
|
||||||
|
return ['success' => false, 'error' => 'Invalid account type selected.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($parentId !== null) {
|
||||||
|
$stmtParent = $db->prepare("SELECT id, type FROM acc_accounts WHERE id = ? LIMIT 1");
|
||||||
|
$stmtParent->execute([$parentId]);
|
||||||
|
$parent = $stmtParent->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$parent) {
|
||||||
|
return ['success' => false, 'error' => 'Selected parent account was not found.'];
|
||||||
|
}
|
||||||
|
if (($parent['type'] ?? '') !== $type) {
|
||||||
|
return ['success' => false, 'error' => 'Parent and child accounts must use the same type.'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmtExists = $db->prepare("SELECT id FROM acc_accounts WHERE code = ? LIMIT 1");
|
||||||
|
$stmtExists->execute([$code]);
|
||||||
|
if ($stmtExists->fetchColumn()) {
|
||||||
|
return ['success' => false, 'error' => 'That account code already exists.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"INSERT INTO acc_accounts (code, name_en, name_ar, type, parent_id) VALUES (?, ?, ?, ?, ?)"
|
||||||
|
);
|
||||||
|
$stmt->execute([$code, $nameEn, $nameAr, $type, $parentId ?: null]);
|
||||||
|
|
||||||
|
return ['success' => true, 'id' => (int)$db->lastInsertId()];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('Accounting account create failed: ' . $e->getMessage());
|
||||||
|
return ['success' => false, 'error' => 'Unable to save the account right now.'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createJournalEntry($date, $description, $reference, $source_type, $source_id, $items) {
|
function createJournalEntry($date, $description, $reference, $source_type, $source_id, $items) {
|
||||||
$db = db();
|
$db = db();
|
||||||
try {
|
try {
|
||||||
|
|||||||
17
index.php
17
index.php
@ -5154,9 +5154,12 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<i class="fas fa-chevron-down chevron"></i>
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse <?= in_array($page, ['accounting', 'expense_report']) ? 'show' : '' ?>" id="accounting-collapse">
|
<div class="collapse <?= in_array($page, ['accounting', 'expense_report']) ? 'show' : '' ?>" id="accounting-collapse">
|
||||||
<a href="index.php?page=accounting" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'accounting' ? 'active' : '' ?>">
|
<a href="index.php?page=accounting" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'accounting' && !isset($_GET['view']) ? 'active' : '' ?>">
|
||||||
<i class="fas fa-book-open"></i> <span><?= __('accounting') ?></span>
|
<i class="fas fa-book-open"></i> <span><?= __('accounting') ?></span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="index.php?page=accounting&view=coa" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'coa' ? 'active' : '' ?>">
|
||||||
|
<i class="fas fa-sitemap"></i> <span data-en="Accounts" data-ar="الحسابات">Accounts</span>
|
||||||
|
</a>
|
||||||
<a href="index.php?page=accounting&view=trial_balance" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'trial_balance' ? 'active' : '' ?>">
|
<a href="index.php?page=accounting&view=trial_balance" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'trial_balance' ? 'active' : '' ?>">
|
||||||
<i class="fas fa-scale-balanced"></i> <span><?= __('trial_balance') ?></span>
|
<i class="fas fa-scale-balanced"></i> <span><?= __('trial_balance') ?></span>
|
||||||
</a>
|
</a>
|
||||||
@ -9148,7 +9151,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars($e['biometric_id'] ?? '---') ?></span></td>
|
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars($e['biometric_id'] ?? '---') ?></span></td>
|
||||||
<td><?= htmlspecialchars($e['dept_name'] ?? '---') ?></td>
|
<td><?= htmlspecialchars($e['dept_name'] ?? '---') ?></td>
|
||||||
<td><?= htmlspecialchars($e['position']) ?></td>
|
<td><?= htmlspecialchars($e['position']) ?></td>
|
||||||
<td>OMR <?= number_format($e['salary'], 3) ?></td>
|
<td>OMR <?= number_format((float)($e['salary'] ?? 0), 3) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge <?= $e['status'] === 'active' ? 'bg-success' : 'bg-danger' ?> text-uppercase">
|
<span class="badge <?= $e['status'] === 'active' ? 'bg-success' : 'bg-danger' ?> text-uppercase">
|
||||||
<?= $e['status'] ?>
|
<?= $e['status'] ?>
|
||||||
@ -9416,10 +9419,10 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<?php foreach ($data['payroll'] as $p): ?>
|
<?php foreach ($data['payroll'] as $p): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?= htmlspecialchars($p['emp_name']) ?></td>
|
<td><?= htmlspecialchars($p['emp_name']) ?></td>
|
||||||
<td>OMR <?= number_format($p['basic_salary'], 3) ?></td>
|
<td>OMR <?= number_format((float)($p['basic_salary'] ?? 0), 3) ?></td>
|
||||||
<td class="text-success">+ OMR <?= number_format($p['bonus'], 3) ?></td>
|
<td class="text-success">+ OMR <?= number_format((float)($p['bonus'] ?? 0), 3) ?></td>
|
||||||
<td class="text-danger">- OMR <?= number_format($p['deductions'], 3) ?></td>
|
<td class="text-danger">- OMR <?= number_format((float)($p['deductions'] ?? 0), 3) ?></td>
|
||||||
<td class="fw-bold">OMR <?= number_format($p['net_salary'], 3) ?></td>
|
<td class="fw-bold">OMR <?= number_format((float)($p['net_salary'] ?? 0), 3) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge <?= $p['status'] === 'paid' ? 'bg-success' : 'bg-warning' ?> text-uppercase">
|
<span class="badge <?= $p['status'] === 'paid' ? 'bg-success' : 'bg-warning' ?> text-uppercase">
|
||||||
<?= $p['status'] ?>
|
<?= $p['status'] ?>
|
||||||
@ -9463,7 +9466,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<select name="employee_id" class="form-select select2" required>
|
<select name="employee_id" class="form-select select2" required>
|
||||||
<option value="">--- Select ---</option>
|
<option value="">--- Select ---</option>
|
||||||
<?php foreach ($data['employees'] as $e): ?>
|
<?php foreach ($data['employees'] as $e): ?>
|
||||||
<option value="<?= $e['id'] ?>"><?= htmlspecialchars($e['name']) ?> (Basic: <?= number_format($e['salary'], 3) ?>)</option>
|
<option value="<?= $e['id'] ?>"><?= htmlspecialchars($e['name']) ?> (Basic: <?= number_format((float)($e['salary'] ?? 0), 3) ?>)</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,12 +1,53 @@
|
|||||||
<?php
|
<?php
|
||||||
// Fix pagination
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST' && function_exists('seedDefaultAccountingAccounts')) {
|
||||||
|
seedDefaultAccountingAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['seed_default_accounts'])) {
|
||||||
|
$added = function_exists('seedDefaultAccountingAccounts') ? seedDefaultAccountingAccounts() : 0;
|
||||||
|
$message = $added > 0
|
||||||
|
? "Main chart of accounts seeded successfully! {$added} account(s) added."
|
||||||
|
: 'Main chart of accounts already exists. Parent links were checked.';
|
||||||
|
|
||||||
|
redirectWithMessage($message, 'index.php?page=accounting&view=coa');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['add_account'])) {
|
||||||
|
$parentId = isset($_POST['parent_id']) && $_POST['parent_id'] !== '' ? (int)$_POST['parent_id'] : null;
|
||||||
|
$result = createAccountingAccount(
|
||||||
|
$_POST['code'] ?? '',
|
||||||
|
$_POST['name_en'] ?? '',
|
||||||
|
$_POST['name_ar'] ?? '',
|
||||||
|
$_POST['type'] ?? '',
|
||||||
|
$parentId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($result['success'])) {
|
||||||
|
redirectWithMessage('Account added successfully!', 'index.php?page=accounting&view=coa');
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectWithMessage((string)($result['error'] ?? 'Unable to save the account right now.'), 'index.php?page=accounting&view=coa');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] === 'get_entry_details') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$id = (int)($_GET['id'] ?? 0);
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
$page_num = isset($_GET['p']) && is_numeric($_GET['p']) ? (int)$_GET['p'] : 1;
|
$page_num = isset($_GET['p']) && is_numeric($_GET['p']) ? (int)$_GET['p'] : 1;
|
||||||
$limit = 50;
|
$limit = 50;
|
||||||
$offset = ($page_num - 1) * $limit;
|
$offset = ($page_num - 1) * $limit;
|
||||||
|
|
||||||
// Count total entries for pagination
|
$total_entries = (int)db()->query("SELECT COUNT(*) FROM acc_journal_entries")->fetchColumn();
|
||||||
$total_entries = db()->query("SELECT COUNT(*) FROM acc_journal_entries")->fetchColumn();
|
$data['total_pages'] = max(1, (int)ceil($total_entries / $limit));
|
||||||
$data['total_pages'] = ceil($total_entries / $limit);
|
|
||||||
$data['current_page'] = $page_num;
|
$data['current_page'] = $page_num;
|
||||||
|
|
||||||
$data['journal_entries'] = db()->query("SELECT je.*,
|
$data['journal_entries'] = db()->query("SELECT je.*,
|
||||||
@ -15,15 +56,6 @@ $data['journal_entries'] = db()->query("SELECT je.*,
|
|||||||
ORDER BY je.entry_date DESC, je.id DESC LIMIT $limit OFFSET $offset")->fetchAll();
|
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();
|
$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') {
|
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
|
$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
|
FROM acc_accounts a
|
||||||
@ -57,4 +89,10 @@ if (isset($_GET['view']) && $_GET['view'] === 'coa') {
|
|||||||
FROM acc_accounts a
|
FROM acc_accounts a
|
||||||
LEFT JOIN acc_accounts p ON a.parent_id = p.id
|
LEFT JOIN acc_accounts p ON a.parent_id = p.id
|
||||||
ORDER BY a.code ASC")->fetchAll();
|
ORDER BY a.code ASC")->fetchAll();
|
||||||
|
|
||||||
|
$data['coa_summary'] = [
|
||||||
|
'total' => count($data['coa']),
|
||||||
|
'parents' => count(array_filter($data['coa'], static fn($account) => empty($account['parent_id']))),
|
||||||
|
'children' => count(array_filter($data['coa'], static fn($account) => !empty($account['parent_id']))),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a href="index.php?page=accounting" class="btn <?= !isset($_GET['view']) ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Journal" data-ar="اليومية">Journal</a>
|
<a href="index.php?page=accounting" class="btn <?= !isset($_GET['view']) ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Journal" data-ar="اليومية">Journal</a>
|
||||||
<a href="index.php?page=accounting&view=coa" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'coa' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Accounts" data-ar="الحسابات">Accounts</a>
|
<a href="index.php?page=accounting&view=coa" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'coa' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Chart of Accounts" data-ar="شجرة الحسابات">Chart of Accounts</a>
|
||||||
<a href="index.php?page=accounting&view=trial_balance" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'trial_balance' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Trial Balance" data-ar="ميزان المراجعة">Trial Balance</a>
|
<a href="index.php?page=accounting&view=trial_balance" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'trial_balance' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Trial Balance" data-ar="ميزان المراجعة">Trial Balance</a>
|
||||||
<a href="index.php?page=accounting&view=profit_loss" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'profit_loss' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="P&L" data-ar="الأرباح">P&L</a>
|
<a href="index.php?page=accounting&view=profit_loss" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'profit_loss' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="P&L" data-ar="الأرباح">P&L</a>
|
||||||
<a href="index.php?page=accounting&view=balance_sheet" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'balance_sheet' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Balance Sheet" data-ar="الميزانية">Balance Sheet</a>
|
<a href="index.php?page=accounting&view=balance_sheet" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'balance_sheet' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Balance Sheet" data-ar="الميزانية">Balance Sheet</a>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<p class="text-muted small"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
|
<p class="text-muted small"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 text-end">
|
<div class="col-6 text-end">
|
||||||
<h2 class="text-uppercase text-muted"><?= isset($_GET['view']) ? ucwords(str_replace('_', ' ', $_GET['view'])) : 'Journal' ?></h2>
|
<h2 class="text-uppercase text-muted"><?= isset($_GET['view']) ? ($_GET['view'] === 'coa' ? 'Chart of Accounts' : ucwords(str_replace('_', ' ', $_GET['view']))) : 'Journal' ?></h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -68,18 +68,68 @@
|
|||||||
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
|
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
|
||||||
|
|
||||||
<?php elseif ($_GET['view'] === 'coa'): ?>
|
<?php elseif ($_GET['view'] === 'coa'): ?>
|
||||||
<div class="d-flex justify-content-end mb-3">
|
<div class="row g-3 mb-4">
|
||||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addAccountModal">
|
<div class="col-md-4">
|
||||||
<i class="bi bi-plus-lg"></i> <span data-en="Add Account" data-ar="إضافة حساب">Add Account</span>
|
<div class="card border-0 bg-light h-100">
|
||||||
</button>
|
<div class="card-body">
|
||||||
|
<div class="text-muted text-uppercase small mb-2">Total Accounts</div>
|
||||||
|
<div class="display-6 fw-bold mb-1"><?= number_format((int)($data['coa_summary']['total'] ?? 0)) ?></div>
|
||||||
|
<div class="small text-muted">All parent and child accounts in your chart.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="text-muted text-uppercase small mb-2">Main Parents</div>
|
||||||
|
<div class="display-6 fw-bold mb-1"><?= number_format((int)($data['coa_summary']['parents'] ?? 0)) ?></div>
|
||||||
|
<div class="small text-muted">Top-level groups like Assets, Liabilities, Revenue, and Expenses.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="text-muted text-uppercase small mb-2">Child Accounts</div>
|
||||||
|
<div class="display-6 fw-bold mb-1"><?= number_format((int)($data['coa_summary']['children'] ?? 0)) ?></div>
|
||||||
|
<div class="small text-muted">Operational accounts you can post transactions into.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-1" data-en="Chart of Accounts" data-ar="شجرة الحسابات">Chart of Accounts</h6>
|
||||||
|
<p class="text-muted small mb-0">Use this page to seed the main parent accounts, then add custom child accounts under the right parent.</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<form method="POST" onsubmit="return confirm('Seed the default main accounts and repair parent links?')">
|
||||||
|
<button type="submit" name="seed_default_accounts" class="btn btn-outline-secondary btn-sm">
|
||||||
|
<i class="bi bi-diagram-3"></i> <span data-en="Seed Main Accounts" data-ar="زرع الحسابات الرئيسية">Seed Main Accounts</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addAccountModal">
|
||||||
|
<i class="bi bi-plus-lg"></i> <span data-en="Add Account" data-ar="إضافة حساب">Add Account</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($data['coa'])): ?>
|
||||||
|
<div class="alert alert-warning border-0 shadow-sm">
|
||||||
|
<i class="bi bi-exclamation-triangle me-1"></i>
|
||||||
|
No accounts exist yet. Click <strong>Seed Main Accounts</strong> to create the default chart of accounts.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover align-middle">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th data-en="Code" data-ar="الكود">Code</th>
|
<th data-en="Code" data-ar="الكود">Code</th>
|
||||||
<th data-en="Name" data-ar="الاسم">Name</th>
|
<th data-en="Name" data-ar="الاسم">Name</th>
|
||||||
<th data-en="Type" data-ar="النوع">Type</th>
|
<th data-en="Type" data-ar="النوع">Type</th>
|
||||||
|
<th data-en="Level" data-ar="المستوى">Level</th>
|
||||||
<th data-en="Parent" data-ar="الحساب الأب">Parent</th>
|
<th data-en="Parent" data-ar="الحساب الأب">Parent</th>
|
||||||
<th data-en="Balance" data-ar="الرصيد" class="text-end">Balance</th>
|
<th data-en="Balance" data-ar="الرصيد" class="text-end">Balance</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -87,20 +137,26 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($data['coa'] as $acc): ?>
|
<?php foreach ($data['coa'] as $acc): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="fw-bold"><?= $acc['code'] ?></td>
|
<td class="fw-bold"><?= htmlspecialchars($acc['code']) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<?= htmlspecialchars($acc['name_en']) ?><br>
|
<?= htmlspecialchars($acc['name_en']) ?><br>
|
||||||
<small class="text-muted"><?= htmlspecialchars($acc['name_ar']) ?></small>
|
<small class="text-muted"><?= htmlspecialchars($acc['name_ar']) ?></small>
|
||||||
</td>
|
</td>
|
||||||
<td><span class="badge bg-light text-dark border text-uppercase"><?= $acc['type'] ?></span></td>
|
<td><span class="badge bg-light text-dark border text-uppercase"><?= htmlspecialchars($acc['type']) ?></span></td>
|
||||||
<td><?= htmlspecialchars($acc['parent_name'] ?? '---') ?></td>
|
<td>
|
||||||
<td class="text-end fw-bold"><?= number_format(getAccountBalance($acc['code']), 3) ?></td>
|
<?php if (empty($acc['parent_id'])): ?>
|
||||||
|
<span class="badge bg-light text-primary border">Main</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-light text-secondary border">Child</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?= htmlspecialchars($acc['parent_name'] ?? '— Main Account —') ?></td>
|
||||||
|
<td class="text-end fw-bold"><?= number_format((float)getAccountBalance($acc['code']), 3) ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
|
|
||||||
|
|
||||||
|
|
||||||
<?php elseif ($_GET['view'] === 'vat_report'): ?>
|
<?php elseif ($_GET['view'] === 'vat_report'): ?>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user