Generalledger Documentation
General Ledger Controller Documentation
File: /controllers/generalledger.php
Purpose: Generates detailed general ledger reports with journal entry analysis and T-account formatting for accounting review
Last Updated: December 20, 2024
Total Functions: 8+
Lines of Code: ~559
---
๐ Overview
The General Ledger Controller is a sophisticated accounting reporting module that generates detailed general ledger reports with journal entry analysis. It provides:
- โข Account-specific general ledger reports with date filtering
- โข Journal entry analysis with debit/credit breakdown
- โข T-account format display with traditional accounting layout
- โข Hierarchical account processing with child account consolidation
- โข Complex journal entry relationship mapping
- โข Multiple display formats (standard list vs T-account format)
- โข Account tree traversal for comprehensive reporting
- โข Advanced filtering and sorting capabilities
Primary Functions
- โ Generate account-specific general ledger reports
- โ Journal entry analysis with debit/credit classification
- โ T-account format display for traditional accounting view
- โ Hierarchical account tree processing
- โ Date range filtering with flexible formats
- โ Complex entry relationship mapping
- โ Multi-format report output options
- โ Account balance calculation and tracking
Related Controllers
- โข accountstree.md - Chart of accounts management
- โข dailyentry.md - Journal entry management
- โข assistantledger.md - Subsidiary ledger reports
---
๐๏ธ Database Tables
Journal Entry Tables
| Table Name | Purpose | Key Columns | |
|---|---|---|---|
| **dailyentry** | Journal entry headers | id, entryComment, thedate, related, reverseofid | |
| **dailyentrydebtor** | Debit entries | id, dailyentryid, accountstreeid, value, dComment | |
| **dailyentrycreditor** | Credit entries | id, dailyentryid, accountstreeid, value, dComment |
| Table Name | Purpose | Key Columns |
|---|---|---|
| **accountstree** | Chart of accounts | id, name, customName, parent, itemtype |
| Table Name | Purpose | Key Columns |
|---|---|---|
| **youtubelink** | Tutorial links | youtubelinkid, title, url |
๐ Key Functions
1. Default Action - Report Interface
Location: Line 76
Purpose: Display general ledger report interface with account selection
Process Flow:
1. Parameter Processing:
$accountsTreeId = filter_input(INPUT_POST, "generalledgerid");
$datefrom = filter_input(INPUT_POST, "datefrom");
$dateto = filter_input(INPUT_POST, "dateto");
$shape = filter_input(INPUT_POST, "shape");
```
2. **Default Values**:
```php
if (empty($accountsTreeId) && empty($datefrom) && empty($dateto)) {
$datefrom = date('Y-m-d');
$dateto = date('Y-m-d');
}
if (empty($accountsTreeId)) {
$accountsTreeId = -1;
}
if (empty($shape) || !isset($shape)) {
$shape = 0;
}
```
3. **Account Processing**:
```php
$childData = $accountsTreeDAO->queryByParent($accountsTreeId);
if (count($childData) > 0 && $type->itemtype2 == 0) {
$cildrenIds = getChilds($accountsTreeId);
$cildrenIds = rtrim($cildrenIds, ",");
DrawTableByAccountNew($cildrenIds, $datefrom, $dateto, $shape, $accountsTreeId);
}
```
---
### 2. **DrawTableByAccountNew()** - Advanced Ledger Generation
**Location**: Line 129
**Purpose**: Generate comprehensive general ledger report with complex entry relationships
**Function Signature**:
php
function DrawTableByAccountNew($id, $startDate, $endDate, $shape, $accountsTreeId)
**Process Flow**:
1. **Date Filter Setup**:
```php
$queryString_date = '';
if (isset($startDate) && !empty($startDate)) {
$queryString_date .= ' and dailyentry.thedate >= "' . $startDate . '" ';
}
if (isset($endDate) && !empty($endDate)) {
$queryString_date .= ' and dailyentry.thedate <= "' . $endDate . '" ';
}
```
2. **Query String Construction**:
```php
$queryStringCreditor = ' where dailyentrycreditor.accountstreeid in( ' . $id . ') ' . $queryString_date . ' ';
$queryStringDebtor = ' where dailyentrydebtor.accountstreeid in( ' . $id . ') ' . $queryString_date . ' ';
```
3. **Entry ID Collection**:
```php
$dailyEntryIds = R::getAll('
(SELECT dailyentry.id
FROM dailyentry
JOIN dailyentrydebtor ON dailyentrydebtor.dailyentryid = dailyentry.id
' . $queryStringDebtor . ')
UNION
(SELECT dailyentry.id
FROM dailyentry
JOIN dailyentrycreditor ON dailyentrycreditor.dailyentryid = dailyentry.id
' . $queryStringCreditor . ')
');
$IDS = '0,';
foreach ($dailyEntryIds as $value) {
$IDS .= $value['id'] . ",";
}
$IDS = rtrim($IDS, ",");
```
4. **Debit Entry Processing**:
```php
$mainItem = R::getAll('
SELECT dailyentry.id, dailyentrydebtor.value, 0 as type
FROM dailyentry
JOIN dailyentrydebtor ON dailyentrydebtor.dailyentryid = dailyentry.id
' . $queryString . ' AND accountstreeid in (' . $id . ')
ORDER BY dailyentry.related, dailyentry.id, dailyentrydebtor.id
');
```
5. **Related Account Processing**:
```php
$resultDataArr = R::getAll('
SELECT dailyentry.*,
dailyentrycreditor.id as idChild,
dailyentrycreditor.accountstreeid,
dailyentrycreditor.value,
dailyentrycreditor.dComment,
0 as type,
CONCAT(dailyentrycreditor.accountstreeid,"-",parent.customName,"/",accountstree.customName) as accountstreeName
FROM dailyentry
JOIN dailyentrycreditor ON dailyentrycreditor.dailyentryid = dailyentry.id
JOIN accountstree ON accountstree.id = dailyentrycreditor.accountstreeid
JOIN accountstree parent ON parent.id = accountstree.parent
AND accountstree.id not in( ' . $id . ')
AND dailyentry.id in( ' . $IDS . ' )
ORDER BY dailyentry.related, dailyentry.id, dailyentrycreditor.id
');
```
6. **Entry Relationship Analysis**:
```php
foreach ($mainItem as $myItem) {
$itemBroCount = count($itemBro[$myItem['id']]) + 1;
$result = $resultDataArr[$myItem['id']];
$resultCount = count($result);
if ($itemBroCount == 1 && $resultCount >= 1) {
// Single account transaction
foreach ($result as $value) {
if ($shape == 1) {
array_push($TShapeArrDebtor, $value);
} else {
array_push($allDailyEntery, $value);
}
}
} elseif ($itemBroCount > 1 && $resultCount == 1) {
// Multiple debit accounts, single credit
$result[0]->value = $myItem['value'];
if ($shape == 1) {
array_push($TShapeArrDebtor, $result[0]);
} else {
array_push($allDailyEntery, $result[0]);
}
} elseif ($itemBroCount > 1 && $resultCount > 1) {
// Compound entry - multiple accounts on both sides
$result[0]->accountstreeName = "ู
ุฐููุฑูู";
$result[0]->value = $myItem['value'];
if ($shape == 1) {
array_push($TShapeArrCerditor, $result[0]);
} else {
array_push($allDailyEntery, $result[0]);
}
}
}
```
7. **T-Account Format Processing**:
```php
if ($shape == 1) {
$dailyEntry = new Dailyentry();
$debtorItr = 0;
$creditorItr = 0;
$realCount = 0;
$totalCount = count($TShapeArrDebtor) + count($TShapeArrCerditor);
for ($i = 0; $i < ($totalCount * 2); $i++) {
if ($i % 2 == 0) {
// Debit side
if (empty($TShapeArrDebtor[$debtorItr])) {
array_push($allDailyEntery, $dailyEntry);
} else {
array_push($allDailyEntery, $TShapeArrDebtor[$debtorItr]);
$debtorItr++;
}
} else {
// Credit side
if (empty($TShapeArrCerditor[$creditorItr])) {
array_push($allDailyEntery, $dailyEntry);
} else {
array_push($allDailyEntery, $TShapeArrCerditor[$creditorItr]);
$creditorItr++;
}
}
}
}
```
---
### 3. **getChilds()** - Recursive Account Tree Traversal
**Location**: Line 545
**Purpose**: Recursively collect all child account IDs for consolidated reporting
**Function Signature**:
php
function getChilds($parent)
**Implementation**:
php
function getChilds($parent) {
global $accountsTreeEX;
global $cildrenIds;
$result = $accountsTreeEX->queryByParentExt($parent);
if (count($result) > 0) {
foreach ($result as $type) {
$cildrenIds .= $type->id . ',';
getChilds($type->id);
}
}
return $cildrenIds;
}
---
### 4. **sortById()** - Entry Sorting
**Location**: Line 520
**Purpose**: Sort journal entries by ID with configurable order
**Function Signature**:
php
function sortById($type)
**Implementation**:
php
function sortById($type) {
global $allDailyEntery;
$membresCount = count($allDailyEntery) - 1;
foreach ($allDailyEntery as $myalloutRole) {
for ($i = 0; $i < $membresCount; $i++) {
if ($type == "desc") {
if ($allDailyEntery[$i]->id < $allDailyEntery[$i + 1]->id) {
$tempMember = $allDailyEntery[$i];
$allDailyEntery[$i] = $allDailyEntery[$i + 1];
$allDailyEntery[$i + 1] = $tempMember;
}
} elseif ($type == "asc") {
if ($allDailyEntery[$i]->id > $allDailyEntery[$i + 1]->id) {
$tempMember = $allDailyEntery[$i + 1];
$allDailyEntery[$i + 1] = $allDailyEntery[$i];
$allDailyEntery[$i] = $tempMember;
}
}
}
}
}
---
## ๐ Workflows
### Workflow 1: General Ledger Report Generation
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ START: General Ledger Request โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Parameter Processing โ
โ - Extract account ID (generalledgerid) โ
โ - Extract date range (datefrom, dateto) โ
โ - Extract display format (shape) โ
โ - Apply default values if empty โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. Account Hierarchy Processing โ
โ IF Account has children: โ
โ โโ Call getChilds() recursively โ
โ โโ Build comma-separated child ID list โ
โ โโ Include all child accounts in report โ
โ ELSE: โ
โ โโ Process single account only โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 3. Entry ID Collection โ
โ A. Debit Entry Query: โ
โ โโ JOIN dailyentry with dailyentrydebtor โ
โ โโ Filter by account IDs and date range โ
โ โโ Collect all matching entry IDs โ
โ B. Credit Entry Query: โ
โ โโ JOIN dailyentry with dailyentrycreditor โ
โ โโ Filter by account IDs and date range โ
โ โโ Collect all matching entry IDs โ
โ C. UNION Results and Build ID List โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 4. Debit Entry Analysis โ
โ FOR EACH Entry in Target Account(s): โ
โ โ โ
โ โโ Get main debit item details โ
โ โโ Count related debit accounts (brothers) โ
โ โโ Get corresponding credit accounts โ
โ โโ Build account name with parent hierarchy โ
โ โ โ
โ โโ Classify Entry Type: โ
โ โโ Simple (1 debit, 1+ credit) โ
โ โโ Multiple debit, single credit โ
โ โโ Compound (multiple debit, multiple credit) โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 5. Credit Entry Analysis โ
โ FOR EACH Entry in Target Account(s): โ
โ โ โ
โ โโ Get main credit item details โ
โ โโ Count related credit accounts (brothers) โ
โ โโ Get corresponding debit accounts โ
โ โโ Build account name with parent hierarchy โ
โ โ โ
โ โโ Classify Entry Type: โ
โ โโ Simple (1 credit, 1+ debit) โ
โ โโ Multiple credit, single debit โ
โ โโ Compound (multiple credit, multiple debit) โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 6. Display Format Selection โ
โ IF Standard Format (shape = 0): โ
โ โโ Sort entries by ID ascending โ
โ โโ Display chronological listing โ
โ โโ Show account names and amounts โ
โ ELIF T-Account Format (shape = 1): โ
โ โโ Separate debit and credit arrays โ
โ โโ Alternate between debit/credit sides โ
โ โโ Fill empty spaces for alignment โ
โ โโ Display traditional T-account layout โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 7. Template Assignment and Display โ
โ - Assign processed entry array to template โ
โ - Include account information and date range โ
โ - Display general ledger report view โ
โ - Include tutorial links if available โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
---
### Workflow 2: T-Account Format Processing
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ START: T-Account Layout โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Separate Debit and Credit Arrays โ
โ - TShapeArrDebtor: All debit entries โ
โ - TShapeArrCerditor: All credit entries โ
โ - Calculate total count for layout โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. Alternating Layout Generation โ
โ FOR i = 0 to (totalCount * 2): โ
โ โ โ
โ IF i is even (i % 2 == 0): โ
โ โโ Process debit side โ
โ โโ Add debit entry or empty space โ
โ โโ Increment debit iterator โ
โ ELSE: โ
โ โโ Process credit side โ
โ โโ Add credit entry or empty space โ
โ โโ Increment credit iterator โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 3. T-Account Display Result โ
โ โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ โ
โ โ DEBIT SIDE โ CREDIT SIDE โ โ
โ โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค โ
โ โ Entry 1 โ Entry 1 โ โ
โ โ Entry 2 โ (empty) โ โ
โ โ (empty) โ Entry 2 โ โ
โ โ Entry 3 โ Entry 3 โ โ
โ โโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
---
## ๐ URL Routes & Actions
| URL Parameter | Function Called | Description |
|---------------|----------------|-------------|
| `do=` (empty) | Default action | Display general ledger interface and process reports |
| `do=sucess` | Success page | Display success message |
| `do=error` | Error page | Display error message |
### Required Parameters
**Generate General Ledger** (POST to default action):
- `generalledgerid` - Account tree ID (-1 for all accounts)
- `datefrom` - Start date (YYYY-MM-DD)
- `dateto` - End date (YYYY-MM-DD)
- `shape` - Display format (0=standard, 1=T-account)
### Form Example
html
---
## ๐งฎ Calculation Methods
### Account Hierarchy ID Collection
php
function getChilds($parent) {
global $accountsTreeEX;
global $cildrenIds;
$result = $accountsTreeEX->queryByParentExt($parent);
if (count($result) > 0) {
foreach ($result as $type) {
$cildrenIds .= $type->id . ',';
getChilds($type->id); // Recursive call
}
}
return $cildrenIds;
}
### Entry Classification Logic
php
// Classification based on number of related accounts
if ($itemBroCount == 1 && $resultCount >= 1) {
// Simple entry: 1 debit account, 1+ credit accounts
$entryType = 'simple';
} elseif ($itemBroCount > 1 && $resultCount == 1) {
// Multiple debits to single credit
$entryType = 'multiple_debit';
} elseif ($itemBroCount > 1 && $resultCount > 1) {
// Compound entry: multiple debits and credits
$entryType = 'compound';
$accountName = "ู ุฐููุฑูู"; // Arabic for "mentioned" (multiple accounts)
}
### T-Account Layout Algorithm
php
$totalCount = count($TShapeArrDebtor) + count($TShapeArrCerditor);
for ($i = 0; $i < ($totalCount * 2); $i++) {
if ($i % 2 == 0) {
// Even indices = debit side
if (!empty($TShapeArrDebtor[$debtorItr])) {
array_push($allDailyEntery, $TShapeArrDebtor[$debtorItr]);
$debtorItr++;
} else {
array_push($allDailyEntery, new Dailyentry()); // Empty space
}
} else {
// Odd indices = credit side
if (!empty($TShapeArrCerditor[$creditorItr])) {
array_push($allDailyEntery, $TShapeArrCerditor[$creditorItr]);
$creditorItr++;
} else {
array_push($allDailyEntery, new Dailyentry()); // Empty space
}
}
}
---
## ๐ Security & Permissions
### Authentication Check
php
include_once("../public/authentication.php");
### Input Validation
php
$accountsTreeId = filter_input(INPUT_POST, "generalledgerid");
$datefrom = filter_input(INPUT_POST, "datefrom");
$dateto = filter_input(INPUT_POST, "dateto");
$shape = filter_input(INPUT_POST, "shape");
// Basic validation
if (empty($shape) || !isset($shape)) {
$shape = 0;
}
### SQL Injection Prevention
- Uses RedBeanPHP ORM with parameterized queries
- Input filtered through `filter_input()` functions
- Account IDs validated through DAO layer
- No direct SQL string concatenation in critical paths
---
## ๐ Performance Considerations
### Database Optimization Tips
1. **Critical Indexes Required**:
```sql
CREATE INDEX idx_dailyentry_date ON dailyentry(thedate);
CREATE INDEX idx_dailyentry_related ON dailyentry(related, id);
CREATE INDEX idx_debtordaily_account ON dailyentrydebtor(accountstreeid, dailyentryid);
CREATE INDEX idx_creditordaily_account ON dailyentrycreditor(accountstreeid, dailyentryid);
CREATE INDEX idx_accountstree_parent ON accountstree(parent);
```
2. **Query Optimization Issues**:
```sql
-- This UNION query can be expensive for large date ranges
SELECT dailyentry.id FROM dailyentry
JOIN dailyentrydebtor ON dailyentrydebtor.dailyentryid = dailyentry.id
WHERE dailyentrydebtor.accountstreeid in (account_list)
AND dailyentry.thedate BETWEEN 'start' AND 'end'
UNION
SELECT dailyentry.id FROM dailyentry
JOIN dailyentrycreditor ON dailyentrycreditor.dailyentryid = dailyentry.id
WHERE dailyentrycreditor.accountstreeid in (account_list)
AND dailyentry.thedate BETWEEN 'start' AND 'end';
```
3. **Performance Bottlenecks**:
- Recursive `getChilds()` function for deep account hierarchies
- Complex JOIN queries with account tree traversal
- Multiple database hits for entry relationship analysis
- Large result set processing in memory
### Optimization Recommendations
php
// Cache account hierarchy
function getCachedChildren($parentId) {
static $cache = array();
if (!isset($cache[$parentId])) {
$cache[$parentId] = getChilds($parentId);
}
return $cache[$parentId];
}
// Optimize entry ID collection
function getEntryIdsBatch($accountIds, $startDate, $endDate) {
$placeholders = str_repeat('?,', count($accountIds) - 1) . '?';
$params = array_merge($accountIds, [$startDate, $endDate, $startDate, $endDate]);
return R::getCol("
SELECT DISTINCT dailyentry.id FROM dailyentry
WHERE dailyentry.id IN (
SELECT dailyentryid FROM dailyentrydebtor WHERE accountstreeid IN ($placeholders)
UNION
SELECT dailyentryid FROM dailyentrycreditor WHERE accountstreeid IN ($placeholders)
)
AND dailyentry.thedate BETWEEN ? AND ?
ORDER BY dailyentry.id
", $params);
}
---
## ๐ Common Issues & Troubleshooting
### 1. **Missing Entries in Report**
**Issue**: Expected journal entries not appearing
**Cause**: Account hierarchy not properly traversed
**Debug**:
php
echo "Account ID: $accountsTreeId
";
$childIds = getChilds($accountsTreeId);
echo "Child IDs: $childIds
";
$entryCount = R::getCell("
SELECT COUNT(*) FROM dailyentry de
JOIN dailyentrydebtor dd ON de.id = dd.dailyentryid
WHERE dd.accountstreeid IN ($childIds)
AND de.thedate BETWEEN ? AND ?
", [$startDate, $endDate]);
echo "Entry count: $entryCount
";
### 2. **T-Account Format Misalignment**
**Issue**: Debit/credit sides not properly aligned
**Cause**: Incorrect array indexing or empty entry handling
**Debug**:
php
echo "Total Debits: " . count($TShapeArrDebtor) . "
";
echo "Total Credits: " . count($TShapeArrCerditor) . "
";
echo "Real Count: $realCount
";
### 3. **Performance Issues with Large Date Ranges**
**Issue**: Timeout or memory errors for extensive periods
**Cause**: Large result sets and complex processing
**Monitor**:
php
$start_time = microtime(true);
$memory_start = memory_get_usage();
// ... processing code ...
$end_time = microtime(true);
$memory_end = memory_get_usage();
echo "Execution time: " . ($end_time - $start_time) . " seconds
";
echo "Memory used: " . (($memory_end - $memory_start) / 1024 / 1024) . " MB
";
### 4. **Entry Relationship Errors**
**Issue**: Wrong accounts shown as related to entries
**Cause**: Complex JOIN logic or data inconsistencies
**Validate**:
sql
-- Check for orphaned entries
SELECT de.id, de.entryComment
FROM dailyentry de
WHERE de.id NOT IN (
SELECT DISTINCT dailyentryid FROM dailyentrydebtor
UNION
SELECT DISTINCT dailyentryid FROM dailyentrycreditor
);
-- Verify debit/credit balance
SELECT de.id,
(SELECT SUM(value) FROM dailyentrydebtor WHERE dailyentryid = de.id) as debits,
(SELECT SUM(value) FROM dailyentrycreditor WHERE dailyentryid = de.id) as credits
FROM dailyentry de
HAVING debits != credits;
---
## ๐งช Testing Scenarios
### Test Case 1: Single Account Ledger
1. Select specific account with known transactions
2. Set date range covering known entries
3. Verify all entries appear correctly
4. Check debit/credit classification
5. Validate account relationships
### Test Case 2: Hierarchical Account Processing
1. Select parent account with child accounts
2. Verify child accounts included in report
3. Check recursive tree traversal
4. Validate consolidated results
### Test Case 3: T-Account Format
1. Generate report with T-account format (shape=1)
2. Verify debit/credit side alignment
3. Check empty space handling
4. Validate traditional accounting layout
### Test Case 4: Complex Journal Entries
1. Test compound entries (multiple debits/credits)
2. Verify "ู ุฐููุฑูู" (mentioned) display
3. Check entry classification logic
4. Validate amount aggregation
### Debug Mode Enable
php
// Add comprehensive debugging
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('memory_limit', '512M');
function debug_query($description, $query, $params = []) {
echo "
$description
";echo "
Query: $query";
if (!empty($params)) {
echo "
Params: " . print_r($params, true) . "";
}
$start = microtime(true);
$result = R::getAll($query, $params);
$duration = microtime(true) - $start;
echo "
Results: " . count($result) . " rows in {$duration}s
";return $result;
}
```
---
๐ Related Documentation
- โข CLAUDE.md - PHP 8.2 migration guide
- โข accountstree.md - Chart of accounts management
- โข dailyentry.md - Journal entry management
- โข assistantledger.md - Subsidiary ledger reports
---
Documented By: AI Assistant
Review Status: โ Complete
Performance Notes: Complex queries may need optimization for large datasets
Next Review: When major changes occur