Guides implementation of the Wallet module for the WorkOS Flutter app — per-collection finances, expense recording with line-item fabric tracking, per-line-item collection allocation splits, receivables, payables, parent organization wallet, and P&L exports. Use when building wallet screens, adding expense forms, implementing collection allocation logic, working on financial reports, or implementing Screen 5, 6, or 7 from new_feature.md.
Implemented in the current codebase. Use this skill for feature refinements, allocation enhancements, and reporting/export improvements.
Read sections "SCREEN 5 — Wallet", "SCREEN 6 — Add Expense", and "SCREEN 7 — Parent Wallet" in new_feature.md.
organizations/{orgId}/collections/{colId}/wallet/transactions/{txId}
type: 'sale' | 'expense' | 'return' | 'payment_received'
amount (number) ← positive = income, negative = outflow
description (string)
referenceId? ← orderId or expenseId
category? (string)
method? (string)
createdBy, createdByName, createdAt
organizations/{orgId}/expenses/{expId} ← GLOBAL expense record
title, category, totalAmount, date,
vendor, paymentStatus ('paid'|'pending'),
paidOn?, method?, reference?,
dueDate? (for pending),
receiptUrl?, notes?,
isRecurring, frequency?, recurringEndDate?,
createdBy, createdAt, updatedAt
organizations/{orgId}/expenses/{expId}/lineItems/{lineId}
description, unit, quantity, ratePerUnit, lineTotal
organizations/{orgId}/expenses/{expId}/allocations/{allocId}
collectionId (or 'overhead'),
collectionName,
lineItemId? (null = whole-expense allocation),
amount, percentage,
createdAt
Revenue and expense entries in transactions/{txId} are the source of truth for per-collection P&L. Allocation records drive how global expenses appear in each collection wallet.
One allocation doc per target:
// Allocate entire expense to one collection
allocations.add({
'collectionId': selectedCollectionId,
'lineItemId': null, // null = whole expense
'amount': totalAmount,
'percentage': 100,
});
One allocation per (lineItem × collection) combination:
// Lawn Print #4 split: 50% Eid 2026, 50% Summer 2026
for (final split in lineSplits) {
allocations.add({
'collectionId': split.collectionId,
'lineItemId': lineItem.id,
'amount': split.amount,
'percentage': split.percentage,
});
}
Validation: Sum of allocation amounts must equal totalAmount. Show live "Rs. X unallocated" indicator.
// Query all transactions for this collection
double totalRevenue = txs.where((t) => t.type == 'sale' || t.type == 'payment_received')
.fold(0, (s, t) => s + t.amount);
double totalExpenses = txs.where((t) => t.type == 'expense')
.fold(0, (s, t) => s + t.amount.abs());
double netProfit = totalRevenue - totalExpenses;
double margin = totalRevenue > 0 ? (netProfit / totalRevenue) * 100 : 0;
Pending receivables = sum of orders where payment.balanceDue > 0.
Pending payables = sum of expenses where paymentStatus == 'pending' allocated to this collection.
Section 1: Basic Details (title, category, amount, date, vendor, payment status)
↓
Section 2: Line Items toggle (optional — "This expense has multiple items")
↓
Section 3: Collection Allocation
→ Mode A if no line items OR toggle to simple
→ Mode B (per-line-item) if line items enabled
↓
Section 4: Recurring (optional toggle)
↓
Save → validate → write batch (expense + lineItems + allocations + wallet transactions)
On save, for each unique collectionId in allocations, create a wallet transaction doc in that collection's transactions sub-collection.
Current implementation note:
This is handled by the orders module on order confirm and additional payment recording. The wallet module only reads these transactions — it does not create them.
Stream orders from this collection where payment.balanceDue > 0:
db.collection('organizations/$orgId/collections/$colId/orders')
.where('payment.balanceDue', isGreaterThan: 0)
.orderBy('payment.dueDate')
.snapshots()
Overdue = dueDate != null && dueDate.isBefore(DateTime.now())
Aggregates across all collections. Do NOT re-query all transactions from scratch — maintain a running organizations/{orgId}/financialSummary document updated by Cloud Functions or batch writes:
financialSummary:
totalRevenue, totalExpenses, netProfit,
totalReceivables, totalPayables,
lastUpdated
byCollection: {
[colId]: { revenue, expenses, profit, margin }
}
Alternatively for MVP: query each collection's transactions on demand with Promise.all in a Cloud Function.
Current implementation note:
WalletProvider + WalletService and shared using share_plus + path_provider.Use csv package (already in pubspec) for CSV exports. Use pdf package for P&L PDF.
// P&L PDF structure
final doc = pw.Document();
doc.addPage(pw.Page(build: (ctx) => pw.Column(children: [
pw.Header(text: 'Profit & Loss Statement'),
pw.Text('Period: $startDate — $endDate'),
revenueSection(transactions),
expensesSection(transactions),
netProfitRow(netProfit, margin),
])));
Current implementation references:
WalletService.generatePnLPdf(...)WalletService.generateExpensesCsv(...)WalletService.generateTransactionsCsv(...)ParentWalletScreen invokes export/share actions.bool canViewWallet = user.role == 'admin'
|| user.role == 'superAdmin'
|| (user.brandModuleAccess?.walletView == true);
bool canAddExpense = user.role == 'admin' || user.role == 'superAdmin';
bool canEditExpense = user.role == 'superAdmin'
|| (user.role == 'admin' && expense.createdBy == user.uid);
// Employees can NEVER add or edit expenses
bool canViewParentWallet = user.role == 'superAdmin'
|| (user.role == 'admin' && !user.parentWalletRestricted);
| Screen | Notes |
|---|---|
wallet_screen.dart | Implemented collection wallet (overview/transactions/expenses/receivables surfaces) |
add_expense_screen.dart | Implemented expense create/edit with line items + single-collection allocation flow |
parent_wallet_screen.dart | Implemented org-level wallet summary + export actions |
expense_detail_screen.dart | Implemented expense detail + edit/delete actions |
WalletProvider is implemented with:
bind(orgId, collectionId)saveExpenseWithAllocations(...)saveExpense(...)deleteExpense(...)generatePnLPdf(...)generateTransactionsCsv(...)generateExpensesCsv(...)WalletSummaryVm from wallet_service.dart.