Overview
Cash expenses record operational costs paid directly from cash registers, card terminals, or bank accounts during the operating day. They reduce the session’s closing balance and provide visibility into daily spending.
Expense Fields
From CashExpense.php:13-28 :
Field Type Required Description cash_session_idForeign Key Yes Links to the daily session tender_typeEnum Yes CASH, CARD, or TRANSFERamountDecimal Yes Expense amount (4 decimal places) categoryString Yes Expense category (e.g., “Supplies”, “Maintenance”) vendorString No Vendor or supplier name referenceString No Invoice number or receipt ID notesText No Additional context card_terminal_idForeign Key If CARD Terminal used for payment bank_account_idForeign Key If TRANSFER Account used for payment incurred_atTimestamp Yes When the expense occurred created_byForeign Key Yes User who recorded the expense posted_byForeign Key No User who approved (nullable until posted) posted_atTimestamp No When finalized (nullable until posted) metaJSON No Custom metadata
Tender Types
From CashExpense.php:37-42 :
public const TENDER_CASH = 'CASH' ;
public const TENDER_CARD = 'CARD' ;
public const TENDER_TRANSFER = 'TRANSFER' ;
Type Description Required Field CASHPaid from register cash drawer None CARDCharged to card terminal card_terminal_idTRANSFERPaid from bank account bank_account_id
Expenses decrease the session’s closing balance regardless of tender type.
Create an Expense
Endpoint
POST /api/v1/cash-expenses
Cash Expense Example
{
"cash_session_id" : 1 ,
"tender_type" : "CASH" ,
"amount" : 150.00 ,
"category" : "Supplies" ,
"vendor" : "Local Supply Store" ,
"reference" : "INV-2026-123" ,
"notes" : "Emergency cleaning supplies" ,
"incurred_at" : "2026-03-06T14:30:00-06:00"
}
Card Expense Example
{
"cash_session_id" : 1 ,
"tender_type" : "CARD" ,
"amount" : 250.00 ,
"category" : "Maintenance" ,
"vendor" : "Plumbing Service Co." ,
"reference" : "INV-PLUMB-456" ,
"card_terminal_id" : 2 ,
"notes" : "Urgent sink repair" ,
"incurred_at" : "2026-03-06T11:00:00-06:00"
}
Transfer Expense Example
{
"cash_session_id" : 1 ,
"tender_type" : "TRANSFER" ,
"amount" : 500.00 ,
"category" : "Utilities" ,
"vendor" : "Electric Company" ,
"reference" : "BILL-MARCH-2026" ,
"bank_account_id" : 1 ,
"notes" : "Monthly electricity payment" ,
"incurred_at" : "2026-03-06T09:00:00-06:00"
}
When tender_type is CARD, card_terminal_id is required
When tender_type is TRANSFER, bank_account_id is required
When tender_type is CASH, both fields should be null
Post an Expense
Posting finalizes the expense and includes it in the session’s closing balance calculation.
Endpoint
POST /api/v1/cash-expenses/{id}/post
// Post the expense
const response = await api . post ( `/cash-expenses/ ${ expenseId } /post` );
// Response includes posted_by and posted_at
{
"message" : "Expense posted successfully" ,
"data" : {
"id" : 1 ,
"amount" : "150.0000" ,
"category" : "Supplies" ,
"posted_by" : 5 ,
"posted_at" : "2026-03-06T23:00:00Z"
}
}
Only posted expenses are included in the session’s calculateClosingBalance() method.
Query Scopes
From CashExpense.php:86-122 :
// Filter by status
CashExpense :: posted () -> get ();
CashExpense :: draft () -> get ();
// Filter by category
CashExpense :: byCategory ( 'Supplies' ) -> get ();
// Filter by tender type
CashExpense :: byTenderType ( 'CASH' ) -> get ();
// Filter by date range
CashExpense :: byDateRange ( '2026-03-01' , '2026-03-31' ) -> get ();
Helper Methods
Status Check
From CashExpense.php:126-130 :
$expense -> isPosted (); // Returns true if posted_at is not null
Tender Type Checks
From CashExpense.php:133-155 :
$expense -> isCash (); // true if tender_type === CASH
$expense -> isCard (); // true if tender_type === CARD
$expense -> isTransfer (); // true if tender_type === TRANSFER
Relationships
From CashExpense.php:47-82 :
cashSession : Parent session
cardTerminal : Terminal for CARD expenses (nullable)
bankAccount : Account for TRANSFER expenses (nullable)
createdBy : User who recorded the expense
postedBy : User who approved the expense (nullable until posted)
Expense Categories
Common expense categories:
Category Description Examples SuppliesOperating supplies Cleaning products, paper goods, packaging MaintenanceEquipment and facility repairs Plumbing, HVAC, appliance repair UtilitiesRegular utility bills Electricity, water, internet DeliveryDelivery-related costs Gas, vehicle maintenance, courier fees MarketingPromotional expenses Flyers, local advertising MiscellaneousOther operational costs Licenses, permits, small equipment
Categories are free-form strings. Consider defining a standard list in your application config.
Workflow Diagram
Use Cases
Emergency Supply Purchase
Pay cash from register for urgent supplies:
{
"cash_session_id" : 1 ,
"tender_type" : "CASH" ,
"amount" : 75.00 ,
"category" : "Supplies" ,
"vendor" : "Local Hardware Store" ,
"reference" : "RECEIPT-789" ,
"notes" : "Emergency light bulbs - main dining area" ,
"incurred_at" : "2026-03-06T15:00:00-06:00"
}
Service Charge via Terminal
Pay for repair using card terminal:
{
"cash_session_id" : 1 ,
"tender_type" : "CARD" ,
"amount" : 320.00 ,
"category" : "Maintenance" ,
"vendor" : "HVAC Specialists" ,
"reference" : "SVC-2026-045" ,
"card_terminal_id" : 1 ,
"notes" : "AC unit maintenance - kitchen area" ,
"incurred_at" : "2026-03-06T10:30:00-06:00"
}
Utility Payment via Bank
Pay monthly bill from business account:
{
"cash_session_id" : 1 ,
"tender_type" : "TRANSFER" ,
"amount" : 650.00 ,
"category" : "Utilities" ,
"vendor" : "City Water Department" ,
"reference" : "BILL-MARCH-2026" ,
"bank_account_id" : 1 ,
"notes" : "Monthly water bill" ,
"incurred_at" : "2026-03-06T09:00:00-06:00"
}
Impact on Session Balance
Expenses reduce the closing balance:
// Session with opening balance
const session = {
opening_balance: 500.00 ,
status: 'DRAFT'
};
// Record income adjustment
await api . post ( '/cash-adjustments' , {
cash_session_id: session . id ,
direction: 'INFLOW' ,
lines: [
{ tender_type: 'CASH' , amount: 2000.00 }
]
});
// Record expense
await api . post ( '/cash-expenses' , {
cash_session_id: session . id ,
tender_type: 'CASH' ,
amount: 150.00 ,
category: 'Supplies'
});
// Closing balance calculation:
// 500 (opening) + 2000 (inflows) - 0 (outflows) - 150 (expenses) = 2350
Best Practices
Always capture invoice/receipt numbers in the reference field for audit trails
Record expenses when they occur (use incurred_at), even if posting happens later
Use consistent vendor names to enable spending analysis by supplier
Define standard categories in your application to ensure consistent reporting
Keep expenses as drafts until reviewed by a manager, then post with their user ID
Match tender type to actual payment method - use CARD for terminal charges, not CASH
Batch Recording
Record multiple expenses at once:
const expenses = [
{
tender_type: 'CASH' ,
amount: 50.00 ,
category: 'Supplies' ,
vendor: 'Office Depot' ,
notes: 'Printer paper'
},
{
tender_type: 'CASH' ,
amount: 25.00 ,
category: 'Supplies' ,
vendor: 'Local Market' ,
notes: 'Coffee for staff room'
},
{
tender_type: 'CARD' ,
amount: 180.00 ,
category: 'Maintenance' ,
vendor: 'Locksmith' ,
card_terminal_id: 1 ,
notes: 'Replace back door lock'
}
];
// Create all expenses
const created = await Promise . all (
expenses . map ( expense =>
api . post ( '/cash-expenses' , {
cash_session_id: sessionId ,
incurred_at: new Date (). toISOString (),
... expense
})
)
);
// Post all after review
await Promise . all (
created . map (({ data }) =>
api . post ( `/cash-expenses/ ${ data . id } /post` )
)
);
Reporting Queries
Expenses by Category
SELECT
category,
COUNT ( * ) as expense_count,
SUM (amount) as total_amount
FROM cash_expenses
WHERE posted_at IS NOT NULL
AND incurred_at >= '2026-03-01'
AND incurred_at < '2026-04-01'
GROUP BY category
ORDER BY total_amount DESC ;
Expenses by Vendor
SELECT
vendor,
category,
COUNT ( * ) as transaction_count,
SUM (amount) as total_spent
FROM cash_expenses
WHERE posted_at IS NOT NULL
AND vendor IS NOT NULL
GROUP BY vendor, category
ORDER BY total_spent DESC ;
Daily Expense Trend
SELECT
DATE (incurred_at) as expense_date,
tender_type,
COUNT ( * ) as expense_count,
SUM (amount) as total_amount
FROM cash_expenses
WHERE posted_at IS NOT NULL
AND incurred_at >= '2026-03-01'
GROUP BY DATE (incurred_at), tender_type
ORDER BY expense_date DESC , tender_type;
Error Handling
Missing Terminal ID
Already Posted
Invalid Session
Negative Amount
{
"message" : "Validation error" ,
"errors" : {
"card_terminal_id" : [
"The card_terminal_id field is required when tender_type is CARD"
]
}
}
Next Steps
Cash Sessions See how expenses affect session closing balance
Bank Accounts Configure accounts for transfer expenses