Cash Save Flow Controller Documentation
File: /controllers/cashSaveFlowController.php
Purpose: Generates comprehensive cash flow reports for safes and bank accounts
Last Updated: December 20, 2024
Total Functions: 3
Lines of Code: ~829
---
๐ Overview
The Cash Save Flow Controller is a sophisticated financial reporting module that provides detailed cash flow analysis for both physical safes and bank accounts. It handles:
- โข Daily cash flow tracking across multiple safes
- โข Bank account movement analysis
- โข Transaction categorization and summarization
- โข Sales and purchase aggregation
- โข Multi-currency and time zone support
- โข User permission-based data filtering
- โข Comprehensive financial audit trails
Primary Functions
- โ Display today's automatic cash flow report
- โ Generate custom date range cash flow reports
- โ Track safe balance changes by transaction type
- โ Monitor bank account movements
- โ Categorize transactions by source (sales, purchases, expenses, etc.)
- โ Calculate running balances and totals
- โ Support multi-safe and multi-account reporting
- โ Apply user permission restrictions
Transaction Categories Tracked
Safe Transactions:
- โข Sales bills (sellbillController.php)
- โข Purchase bills (buyBillController.php)
- โข Returns (returnsellbillController.php, returnBuyBillController.php)
- โข Combined bills (sellbillandruternController.php)
- โข Client payments (clientPayedDeptController.php)
- โข Supplier payments (supplierPayedDeptController.php)
- โข Employee expenses (employeePersonalController.php)
- โข General expenses (expensesController.php)
- โข Income entries (incomeController.php)
- โข Partner withdrawals (partnerwithdrawalController.php)
Bank Transactions:
- โข Check deposits (depositcheckController.php)
- โข Check withdrawals (checkwithdrawalController.php)
- โข Cash transfers (cashTransferController.php)
- โข Dated checks (datedCheckedController.php)
- โข Sales receipts (sellbillController.php)
Related Controllers
- โข sellbillController.php - Sales operations
- โข buyBillController.php - Purchase operations
- โข clientPayedDeptController.php - Customer payments
- โข expensesController.php - Expense management
- โข incomeController.php - Income tracking
---
๐๏ธ Database Tables
Primary Tables (Direct Operations)
| Table Name | Purpose | Key Columns | |
|---|---|---|---|
| **savedaily** | Safe transaction log | savedailyid, saveid, savedailychangeamount, savedailychangetype, tablename, processname, savedailysavebefore, savedailysaveafter | |
| **save** | Safe/cash register master | saveid, savename, savecurrentvalue, conditions | |
| **accountmovement** | Bank account transactions | accountmovementid, accountid, accountmovementamount, accountmovementtype, tablename, accountmovementbefore, accountmovementafter |
| Table Name | Purpose | Key Columns | |
|---|---|---|---|
| **bankaccount** | Bank account master | accountid, accountname, accountbeginingbalance | |
| **bank** | Bank master data | bankid, bankname | |
| **client** | Customer information | clientid, clientname | |
| **supplier** | Supplier information | supplierid, suppliername | |
| **user** | System users | userid, username, searchinonesave, saveid |
| Table Name | Purpose | Key Columns | |
|---|---|---|---|
| **sellbill** | Sales bills | sellbillid, sellbillclientid, sellbilltotalpayed | |
| **buybill** | Purchase bills | buybillid, buybillsupplierid, buybilltotalpayed | |
| **clientdebtchange** | Customer debt tracking | clientdebtchangeid, clientid, clientdebtchangeamount | |
| **supplierdebtchange** | Supplier debt tracking | supplierdebtchangeid, supplierid, supplierdebtchangeamount | |
| **expenses** | Expense records | expensesid, expensesname | |
| **income** | Income records | incomeId, incomeName | |
| **checkdeposit** | Check deposits | checkdepositid, clientid, checkamount | |
| **checkwithdrawal** | Check withdrawals | checkwithdrawalid, supplierid, checkamount |
๐ Key Functions
1. Default Action - Today's Cash Flow Report
Location: Lines 323-376
Purpose: Display automatic cash flow report for current day
Function Signature:
// Triggered when: empty($do) or no action specified
if (empty($do)) {
Process Flow:
1. Permission Check: Include authentication
2. Load Configuration:
- Get user save privileges
- Load program settings for time zone handling
- Load YouTube tutorial links
3. Date Calculation:
- Calculate today's date range
- Apply program setting hour offsets for time zones
- Determine if current time is before/after daily cutoff
4. Data Loading:
- Load available safes based on user permissions
- Load available bank accounts
- Call search() function with calculated parameters
Time Zone Logic:
if (isset($Programsetting->reportsPlusHours) && !empty($Programsetting->reportsPlusHours)) {
$reportsPlusHours = $Programsetting->reportsPlusHours + 24;
$endToday = date('Y-m-d H:i:s', strtotime('+' . $reportsPlusHours . ' hour', strtotime($today)));
$startToday = date('Y-m-d H:i:s', strtotime('+' . $Programsetting->reportsPlusHours . ' hour', strtotime($today)));
}
// Determine which day's data to show based on current hour
if (date('H') < $Programsetting->reportsPlusHours) {
$startDate = $startYesterday; // Show yesterday's data
$endDate = $endYesterday;
} else {
$startDate = $startToday; // Show today's data
$endDate = $endToday;
}
---
2. show - Custom Date Range Report
Location: Lines 377-438
Purpose: Generate cash flow report for user-specified criteria
Function Signature:
// Triggered when: do=show
elseif ($do == "show") {
Process Flow:
1. Parameter Processing:
- Parse save/account selection: $_REQUEST['saveId'] format: {type}_{id}
- Extract date range: $_REQUEST['from'], $_REQUEST['to']
- Handle search type: dateOnly vs exact datetime
2. Message Building:
- Build descriptive report title
- Include safe name or bank account name
- Include date range information
3. Date Range Processing:
if ($search == "dateOnly") {
if (isset($Programsetting->reportsPlusHours)) {
$endDate = date('Y-m-d H:i:s', strtotime('+' . $reportsPlusHours . ' hour', strtotime($endDate)));
$startDate = date('Y-m-d H:i:s', strtotime('+' . $Programsetting->reportsPlusHours . ' hour', strtotime($startDate)));
} else {
$endDate = $endDate . ' 23:59:59';
$startDate = $startDate . " 00:00:00";
}
}
4. Report Generation: Call search() with processed parameters
Input Parameters:
- โข
saveId: Formatsave_{id}orbank_{id} - โข
from: Start date (YYYY-MM-DD) - โข
to: End date (YYYY-MM-DD) - โข
search: Search type (dateOnlyor exact)
---
3. loadSaveByUserPrivileg() - User Permission Filtering
Location: Lines 456-475
Purpose: Load safes based on user access privileges
Function Signature:
function loadSaveByUserPrivileg()
Process Flow:
1. Load current user data
2. Check searchinonesave permission:
- 0: User can see all safes
- 1: User restricted to assigned safe only
3. Return appropriate safe list
Permission Logic:
$userData = $myUserRecord->load($_SESSION['userid']);
if ($userData->searchinonesave == 0) {
$saveData = $mySaveRecord->queryByConditions(0); // All safes
} else {
$saveData = array();
$userSave = $mySaveRecord->load($userData->saveid); // Only assigned safe
array_push($saveData, $userSave);
}
---
4. search() - Core Report Generation Engine
Location: Lines 478-829
Purpose: Complex report generation with transaction analysis
Function Signature:
function search($saveid, $startDate, $endDate, $accountId)
Process Flow:
Phase 1: Parameter Processing
- โข Determine report scope (all, save-specific, or account-specific)
- โข Build dynamic SQL WHERE clauses
- โข Apply user permission restrictions
Phase 2: Safe Transaction Processing
if ($all == 1 || $type == 'save') {
$savedailyData = $mySavedailyEx->searchInAdsindexWithUsername($queryString, $order);
// Initialize aggregation objects
$allSell = []; // Sales totals by safe
$allBuy = []; // Purchase totals by safe
foreach ($savedailyData as $data) {
// Process each transaction
}
}
Phase 3: Transaction Categorization
Sales Transactions:
if (in_array($data->tablename, ["sellbillController.php", "sellbillandruternController.php", "returnsellbillController.php"])) {
if ($data->savedailychangetype == 1) { // Decrease (refund)
$data->savedailychangeamount = $data->savedailychangeamount * -1;
$inSum = $data->savedailychangeamount;
} else { // Increase (sale)
$inSum = $data->savedailychangeamount;
}
$allSell[$data->saveid]->savedailychangeamount += $data->savedailychangeamount;
}
Purchase Transactions:
elseif (in_array($data->tablename, ["buyBillController.php", "returnBuyBillController.php"])) {
if ($data->savedailychangetype == 1) { // Decrease (purchase)
$outSum = $data->savedailychangeamount;
} else { // Increase (return)
$data->savedailychangeamount = $data->savedailychangeamount * -1;
$outSum = $data->savedailychangeamount;
}
$allBuy[$data->saveid]->savedailychangeamount += $data->savedailychangeamount;
}
Other Transactions (Expenses, Income, Payments):
else {
switch ($data->tablename) {
case "supplierPayedDeptController.php":
$data->savecurrentvalue = $supplierDebtChangeExt->getSupplierName($data->savedailymodelid)->suppliername;
break;
case "clientPayedDeptController.php":
$data->savecurrentvalue = $clientDeptChangeExt->getClientName($data->savedailymodelid)->clientname;
break;
case "expensesController.php":
$data->savecurrentvalue = R::getRow('SELECT expensesname FROM expenses WHERE expensesid =' . $data->savedailymodelid)['expensesname'];
break;
// ... more cases
}
if ($data->savedailychangetype == 1) { // Outflow
$outData[] = $data;
} else { // Inflow
$inData[] = $data;
}
}
Phase 4: Bank Account Processing
if ($all == 1 || $type == 'bank') {
$allMovements = $accountMovementExt->queryAllMovements($accQueryString);
foreach ($allMovements as $movement) {
// Process bank movements by transaction type
if ($movement->tablename == "depositcheckController.php") {
$depositData = $CheckdepositEX->loadEX($movement->accountmovementmodelid);
$movement->clientname = $depositData->clientname;
}
// ... handle other bank transaction types
}
}
Phase 5: Balance Calculations
// For each safe
if (!array_key_exists($data->saveid, $savesData)) {
$saveData = $mySaveRecord->load($data->saveid);
$savesData[$data->saveid] = [
'savename' => $data->savename,
'balance' => $data->savedailysavebefore, // Starting balance
'currentBalance' => $saveData->savecurrentvalue, // Current balance
'inSum' => $inSum, // Total inflow
'outSum' => $outSum // Total outflow
];
}
---
๐ Workflows
Workflow 1: Automatic Daily Report Generation
---
Workflow 2: Custom Report Generation
---
Workflow 3: Transaction Processing Engine
---
๐ URL Routes & Actions
| URL Parameter | Function Called | Description | |
|---|---|---|---|
| (no parameters) | Default action | Today's automatic cash flow report | |
| `do=show` | Custom report | User-specified date range and criteria | |
| `do=sucess` | Success page | Display success message | |
| `do=error` | Error page | Display error message |
Today's Report (no parameters):
- โข No parameters required
- โข Uses system date and user permissions
Custom Report (do=show):
- โข
saveId- Format:save_{saveid}orbank_{accountid}(optional) - โข
from- Start date (YYYY-MM-DD) - โข
to- End date (YYYY-MM-DD) - โข
search- Search type:dateOnlyor exact timestamps
---
๐งฎ Transaction Analysis & Calculations
Transaction Type Determination
// Sales and returns affect cash positively/negatively
if (in_array($data->tablename, ["sellbillController.php", "sellbillandruternController.php", "returnsellbillController.php"])) {
if ($data->savedailychangetype == 1) { // Decrease (refund/return)
$inSum = $data->savedailychangeamount * -1; // Convert to negative inflow
} else { // Increase (sale)
$inSum = $data->savedailychangeamount; // Positive inflow
}
}
// Purchases affect cash negatively/positively
elseif (in_array($data->tablename, ["buyBillController.php", "returnBuyBillController.php"])) {
if ($data->savedailychangetype == 1) { // Decrease (purchase payment)
$outSum = $data->savedailychangeamount; // Outflow
} else { // Increase (return refund)
$outSum = $data->savedailychangeamount * -1; // Negative outflow
}
}
Balance Calculations
// Calculate totals for each safe
$savesData[$data->saveid] = [
'savename' => $data->savename,
'balance' => $data->savedailysavebefore, // Starting balance for period
'currentBalance' => $saveData->savecurrentvalue, // Current actual balance
'inSum' => $inSum, // Total period inflow
'outSum' => $outSum // Total period outflow
];
// Net change = inSum - outSum
// Expected ending balance = balance + (inSum - outSum)
Entity Name Resolution
switch ($data->tablename) {
case "supplierPayedDeptController.php":
$data->savecurrentvalue = $supplierDebtChangeExt->getSupplierName($data->savedailymodelid)->suppliername;
break;
case "clientPayedDeptController.php":
$data->savecurrentvalue = $clientDeptChangeExt->getClientName($data->savedailymodelid)->clientname;
break;
case "expensesController.php":
$data->savecurrentvalue = R::getRow('SELECT expensesname FROM expenses WHERE expensesid =' . $data->savedailymodelid)['expensesname'];
break;
case "incomeController.php":
$data->savecurrentvalue = R::getRow('SELECT incomeName FROM income WHERE incomeId =' . $data->savedailymodelid)['incomeName'];
break;
}
---
๐ Security & Permissions
User Permission System
// Load user data to check permissions
$userData = $myUserRecord->load($_SESSION['userid']);
// Safe access control
if ($userData->searchinonesave == 0) {
// User can access all safes
if ($_SESSION['saveids'] != 0) {
$queryString .= ' savedaily.saveid in (' . $_SESSION['saveids'] . ') AND';
}
} else {
// User restricted to specific safe
$queryString .= ' savedaily.saveid = ' . $_SESSION['saveid'] . ' AND';
}
Input Validation
- โข Date parameters validated through framework
- โข Save/account IDs filtered and parsed
- โข SQL injection prevented by DAO layer
- โข Session-based user authentication
Data Access Control
- โข Users only see transactions for safes they have permission to access
- โข Bank account access controlled by user privileges
- โข Transaction details filtered by user group permissions
---
๐ Performance Considerations
Database Optimization Tips
1. Critical Indexes:
- savedaily(saveid, savedailydate) - For date range queries
- accountmovement(accountid, accountmovementdate) - For bank queries
- savedaily(tablename, savedailymodelid) - For transaction linking
2. Query Optimization:
- Uses complex date range filtering
- Could benefit from materialized views for common aggregations
- N+1 query issues when loading entity names
3. Memory Management:
- Large date ranges can return thousands of records
- Consider pagination for very active periods
- Template variables accumulate across all transactions
Performance Issues
-- This query pattern appears frequently and could be slow
SELECT * FROM savedaily
WHERE saveid IN (1,2,3,4,5)
AND savedailydate >= '2024-01-01 00:00:00'
AND savedailydate <= '2024-01-31 23:59:59'
ORDER BY savedailydate ASC;
-- Then for each record, loads entity names individually:
SELECT expensesname FROM expenses WHERE expensesid = ?;
SELECT incomeName FROM income WHERE incomeId = ?;
-- etc...
Optimization Approach:
-- Better to use JOINs where possible
SELECT sd.*, e.expensesname, i.incomeName, c.clientname, s.suppliername
FROM savedaily sd
LEFT JOIN expenses e ON sd.tablename = 'expensesController.php' AND e.expensesid = sd.savedailymodelid
LEFT JOIN income i ON sd.tablename = 'incomeController.php' AND i.incomeId = sd.savedailymodelid
-- etc...
---
๐ Common Issues & Troubleshooting
1. Incorrect Time Zone Handling
Issue: Report shows wrong day's data
Cause: reportsPlusHours setting not properly configured
Debug:
// Check current hour vs setting
echo "Current hour: " . date('H') . "<br>";
echo "Reports plus hours: " . $Programsetting->reportsPlusHours . "<br>";
echo "Calculated start: " . $startDate . "<br>";
echo "Calculated end: " . $endDate . "<br>";
2. Missing Transactions
Issue: Some transactions don't appear in report
Cause: Permission restrictions or date range issues
Debug:
-- Check user permissions
SELECT userid, searchinonesave, saveid FROM user WHERE userid = [USER_ID];
-- Check transaction dates
SELECT COUNT(*), MIN(savedailydate), MAX(savedailydate)
FROM savedaily WHERE saveid = [SAVE_ID];
3. Incorrect Balance Calculations
Issue: Totals don't match expected values
Cause: Transaction type logic or sign handling
Debug:
-- Verify transaction types and amounts
SELECT tablename, savedailychangetype, SUM(savedailychangeamount)
FROM savedaily
WHERE saveid = [SAVE_ID] AND savedailydate BETWEEN '[START]' AND '[END]'
GROUP BY tablename, savedailychangetype;
4. Performance Issues
Issue: Report takes long time to load
Cause: Large date ranges or missing indexes
Solutions:
- โข Add appropriate database indexes
- โข Implement date range limits
- โข Consider caching for frequently accessed reports
- โข Optimize entity name loading queries
---
๐งช Testing Scenarios
Test Case 1: Daily Report Accuracy
1. Record test transactions in different safes
2. Access controller without parameters
3. Verify correct day's data is shown
4. Check time zone handling
5. Confirm permission filtering works
Test Case 2: Custom Date Range
1. Select specific safe and date range
2. Verify all transactions in range appear
3. Check transaction categorization
4. Confirm totals are accurate
5. Test bank account reporting
Test Case 3: Permission Restrictions
1. Login as restricted user (searchinonesave = 1)
2. Verify only assigned safe appears
3. Test with unrestricted user
4. Confirm all accessible safes show
Test Case 4: Transaction Type Handling
1. Create transactions of each type
2. Verify proper categorization (in/out)
3. Check entity name resolution
4. Confirm aggregation accuracy
---
๐ Related Documentation
- โข CLAUDE.md - PHP 8.2 migration guide
- โข sellbillController.md - Sales operations
- โข buyBillController.php - Purchase operations
- โข Financial Reports Documentation - Other financial reports
---
Documented By: AI Assistant
Review Status: โ Complete
Next Review: When major changes occur