@fayz-ai/plugin-financial 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/FinancialContext.d.ts +44 -0
  2. package/dist/FinancialContext.d.ts.map +1 -0
  3. package/dist/FinancialPage.d.ts +18 -0
  4. package/dist/FinancialPage.d.ts.map +1 -0
  5. package/dist/components/FinancialGeneralSettings.d.ts +3 -0
  6. package/dist/components/FinancialGeneralSettings.d.ts.map +1 -0
  7. package/dist/components/FinancialOnboarding.d.ts +5 -0
  8. package/dist/components/FinancialOnboarding.d.ts.map +1 -0
  9. package/dist/components/FinancialSettings.d.ts +8 -0
  10. package/dist/components/FinancialSettings.d.ts.map +1 -0
  11. package/dist/components/PaymentModal.d.ts +8 -0
  12. package/dist/components/PaymentModal.d.ts.map +1 -0
  13. package/dist/data/index.d.ts +3 -0
  14. package/dist/data/index.d.ts.map +1 -0
  15. package/dist/data/mock.d.ts +9 -0
  16. package/dist/data/mock.d.ts.map +1 -0
  17. package/dist/data/supabase.d.ts +3 -0
  18. package/dist/data/supabase.d.ts.map +1 -0
  19. package/dist/data/types.d.ts +27 -0
  20. package/dist/data/types.d.ts.map +1 -0
  21. package/dist/index.cjs +4937 -0
  22. package/dist/index.cjs.map +1 -0
  23. package/dist/index.d.ts +91 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +4930 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lib/tenant.d.ts +3 -0
  28. package/dist/lib/tenant.d.ts.map +1 -0
  29. package/dist/locales/en.d.ts +2 -0
  30. package/dist/locales/en.d.ts.map +1 -0
  31. package/dist/locales/index.d.ts +2 -0
  32. package/dist/locales/index.d.ts.map +1 -0
  33. package/dist/locales/pt-BR.d.ts +2 -0
  34. package/dist/locales/pt-BR.d.ts.map +1 -0
  35. package/dist/registries.d.ts +3 -0
  36. package/dist/registries.d.ts.map +1 -0
  37. package/dist/store.d.ts +38 -0
  38. package/dist/store.d.ts.map +1 -0
  39. package/dist/types.d.ts +302 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/views/CardsView.d.ts +3 -0
  42. package/dist/views/CardsView.d.ts.map +1 -0
  43. package/dist/views/CashRegistersView.d.ts +3 -0
  44. package/dist/views/CashRegistersView.d.ts.map +1 -0
  45. package/dist/views/CommissionsView.d.ts +3 -0
  46. package/dist/views/CommissionsView.d.ts.map +1 -0
  47. package/dist/views/InvoiceDetailView.d.ts +9 -0
  48. package/dist/views/InvoiceDetailView.d.ts.map +1 -0
  49. package/dist/views/InvoiceFormView.d.ts +13 -0
  50. package/dist/views/InvoiceFormView.d.ts.map +1 -0
  51. package/dist/views/InvoiceListView.d.ts +8 -0
  52. package/dist/views/InvoiceListView.d.ts.map +1 -0
  53. package/dist/views/PayablesView.d.ts +7 -0
  54. package/dist/views/PayablesView.d.ts.map +1 -0
  55. package/dist/views/ReceivablesView.d.ts +7 -0
  56. package/dist/views/ReceivablesView.d.ts.map +1 -0
  57. package/dist/views/StatementsView.d.ts +3 -0
  58. package/dist/views/StatementsView.d.ts.map +1 -0
  59. package/dist/views/SummaryView.d.ts +9 -0
  60. package/dist/views/SummaryView.d.ts.map +1 -0
  61. package/dist/views/dashboardWidgets.d.ts +11 -0
  62. package/dist/views/dashboardWidgets.d.ts.map +1 -0
  63. package/package.json +55 -0
  64. package/src/FinancialContext.tsx +47 -0
  65. package/src/FinancialPage.tsx +261 -0
  66. package/src/README.md +155 -0
  67. package/src/components/FinancialGeneralSettings.tsx +26 -0
  68. package/src/components/FinancialOnboarding.tsx +87 -0
  69. package/src/components/FinancialSettings.tsx +31 -0
  70. package/src/components/PaymentModal.tsx +322 -0
  71. package/src/data/index.ts +2 -0
  72. package/src/data/mock.ts +520 -0
  73. package/src/data/supabase.ts +509 -0
  74. package/src/data/types.ts +53 -0
  75. package/src/index.ts +307 -0
  76. package/src/lib/tenant.ts +3 -0
  77. package/src/locales/en.ts +331 -0
  78. package/src/locales/index.ts +7 -0
  79. package/src/locales/pt-BR.ts +331 -0
  80. package/src/migrations/001_financial_base.sql +109 -0
  81. package/src/migrations/002_chart_of_accounts.sql +28 -0
  82. package/src/migrations/003_card_brands.sql +12 -0
  83. package/src/migrations/004_order_to_cash.sql +164 -0
  84. package/src/migrations/005_seed_defaults.sql +38 -0
  85. package/src/registries.ts +305 -0
  86. package/src/store.ts +221 -0
  87. package/src/types.ts +359 -0
  88. package/src/views/CardsView.tsx +74 -0
  89. package/src/views/CashRegistersView.tsx +597 -0
  90. package/src/views/CommissionsView.tsx +18 -0
  91. package/src/views/InvoiceDetailView.tsx +595 -0
  92. package/src/views/InvoiceFormView.tsx +900 -0
  93. package/src/views/InvoiceListView.tsx +177 -0
  94. package/src/views/PayablesView.tsx +37 -0
  95. package/src/views/ReceivablesView.tsx +37 -0
  96. package/src/views/StatementsView.tsx +123 -0
  97. package/src/views/SummaryView.tsx +12 -0
  98. package/src/views/dashboardWidgets.tsx +191 -0
@@ -0,0 +1,520 @@
1
+ import type { FinancialDataProvider } from './types'
2
+ import type {
3
+ Invoice, InvoiceItem, FinancialMovement, BankAccount,
4
+ CashSession, PaymentMethod, PaymentMethodType, ChartOfAccountsNode,
5
+ CardTransaction,
6
+ CreateInvoiceInput, PayMovementInput,
7
+ OpenCashSessionInput, CloseCashSessionInput, CreateBankAccountInput,
8
+ InvoiceQuery, MovementQuery, StatementQuery,
9
+ PaginatedResult, FinancialSummary, StatementEntry, CashSessionSummary,
10
+ DateRange, InvoiceStatus, MovementStatus,
11
+ } from '../types'
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Helpers
15
+ // ---------------------------------------------------------------------------
16
+
17
+ let nextId = 1
18
+ function uid(): string {
19
+ return String(nextId++)
20
+ }
21
+
22
+ function now(): string {
23
+ return new Date().toISOString()
24
+ }
25
+
26
+ function today(): string {
27
+ return new Date().toISOString().slice(0, 10)
28
+ }
29
+
30
+ function matchesDateRange(dateStr: string | undefined, range?: DateRange): boolean {
31
+ if (!range || !dateStr) return true
32
+ return dateStr >= range.from && dateStr <= range.to
33
+ }
34
+
35
+ function matchesStatus<T extends string>(status: T, filter?: T | T[]): boolean {
36
+ if (!filter) return true
37
+ if (Array.isArray(filter)) return filter.includes(status)
38
+ return status === filter
39
+ }
40
+
41
+ function paginate<T>(items: T[], page?: number, pageSize?: number): PaginatedResult<T> {
42
+ const p = page ?? 1
43
+ const ps = pageSize ?? 50
44
+ const start = (p - 1) * ps
45
+ return { data: items.slice(start, start + ps), total: items.length }
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // In-memory store
50
+ // ---------------------------------------------------------------------------
51
+
52
+ interface MockStore {
53
+ invoices: Invoice[]
54
+ invoiceItems: InvoiceItem[]
55
+ movements: FinancialMovement[]
56
+ bankAccounts: BankAccount[]
57
+ cashSessions: CashSession[]
58
+ paymentMethods: PaymentMethod[]
59
+ paymentMethodTypes: PaymentMethodType[]
60
+ chartOfAccounts: ChartOfAccountsNode[]
61
+ cardTransactions: CardTransaction[]
62
+ }
63
+
64
+ function createStore(seedPaymentMethodTypes?: Array<{ name: string; transactionType?: string }>): MockStore {
65
+ const tenantId = 'mock-tenant'
66
+
67
+ const defaultPMTypes: PaymentMethodType[] = (seedPaymentMethodTypes ?? [
68
+ { name: 'Cash', transactionType: 'cash' },
69
+ { name: 'Credit Card', transactionType: 'credit_card' },
70
+ { name: 'Debit Card', transactionType: 'debit_card' },
71
+ { name: 'PIX', transactionType: 'pix' },
72
+ { name: 'Bank Transfer', transactionType: 'transfer' },
73
+ ]).map((t) => ({
74
+ id: uid(),
75
+ name: t.name,
76
+ transactionType: t.transactionType,
77
+ isActive: true,
78
+ tenantId,
79
+ createdAt: now(),
80
+ }))
81
+
82
+ const cashAccount: BankAccount = {
83
+ id: uid(),
84
+ name: 'Main Cash Register',
85
+ accountType: 'cash_register',
86
+ currentBalance: 0,
87
+ initialBalance: 0,
88
+ isActive: true,
89
+ tenantId,
90
+ createdAt: now(),
91
+ updatedAt: now(),
92
+ }
93
+
94
+ const bankAccount: BankAccount = {
95
+ id: uid(),
96
+ name: 'Business Checking',
97
+ accountType: 'bank_account',
98
+ bankName: 'Main Bank',
99
+ currentBalance: 0,
100
+ initialBalance: 0,
101
+ isActive: true,
102
+ tenantId,
103
+ createdAt: now(),
104
+ updatedAt: now(),
105
+ }
106
+
107
+ return {
108
+ invoices: [],
109
+ invoiceItems: [],
110
+ movements: [],
111
+ bankAccounts: [cashAccount, bankAccount],
112
+ cashSessions: [],
113
+ paymentMethods: [],
114
+ paymentMethodTypes: defaultPMTypes,
115
+ chartOfAccounts: [],
116
+ cardTransactions: [],
117
+ }
118
+ }
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // Mock provider factory
122
+ // ---------------------------------------------------------------------------
123
+
124
+ export interface MockFinancialProviderOptions {
125
+ paymentMethodTypes?: Array<{ name: string; transactionType?: string }>
126
+ }
127
+
128
+ export function createMockFinancialProvider(options?: MockFinancialProviderOptions): FinancialDataProvider {
129
+ const store = createStore(options?.paymentMethodTypes)
130
+ const tenantId = 'mock-tenant'
131
+
132
+ function recalcInvoiceStatus(invoice: Invoice): void {
133
+ if (invoice.paidAmount >= invoice.totalAmount) {
134
+ invoice.status = 'paid'
135
+ } else if (invoice.paidAmount > 0) {
136
+ invoice.status = 'partial'
137
+ } else {
138
+ // Check if any movement is overdue
139
+ const movements = store.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === 'bill')
140
+ const hasOverdue = movements.some((m) => m.status === 'overdue' || (m.status === 'pending' && m.dueDate < today()))
141
+ invoice.status = hasOverdue ? 'overdue' : 'open'
142
+ }
143
+ invoice.updatedAt = now()
144
+ }
145
+
146
+ const provider: FinancialDataProvider = {
147
+ // --- Invoices ---
148
+ async getInvoices(query: InvoiceQuery): Promise<PaginatedResult<Invoice>> {
149
+ let results = [...store.invoices]
150
+ if (query.direction) results = results.filter((i) => i.direction === query.direction)
151
+ if (query.status) results = results.filter((i) => matchesStatus(i.status, query.status as InvoiceStatus | InvoiceStatus[]))
152
+ if (query.contactId) results = results.filter((i) => i.contactId === query.contactId)
153
+ if (query.dateRange) results = results.filter((i) => matchesDateRange(i.invoiceDate, query.dateRange))
154
+ if (query.search) {
155
+ const s = query.search.toLowerCase()
156
+ results = results.filter((i) =>
157
+ i.contactName?.toLowerCase().includes(s) ||
158
+ i.observations?.toLowerCase().includes(s) ||
159
+ i.fiscalNumber?.toLowerCase().includes(s)
160
+ )
161
+ }
162
+ results.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
163
+ return paginate(results, query.page, query.pageSize)
164
+ },
165
+
166
+ async getInvoiceById(id: string): Promise<Invoice | null> {
167
+ return store.invoices.find((i) => i.id === id) ?? null
168
+ },
169
+
170
+ async getInvoiceItems(invoiceId: string): Promise<InvoiceItem[]> {
171
+ return store.invoiceItems.filter((i) => i.invoiceId === invoiceId)
172
+ },
173
+
174
+ async createInvoice(input: CreateInvoiceInput): Promise<Invoice> {
175
+ const invoiceId = uid()
176
+ const totalAmount = input.items.reduce((sum, item) => {
177
+ const itemTotal = item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0)
178
+ return sum + itemTotal
179
+ }, 0)
180
+
181
+ const itemsSummary = input.items.map((i) => i.description).filter(Boolean).join(', ')
182
+
183
+ const invoice: Invoice = {
184
+ id: invoiceId,
185
+ direction: input.direction,
186
+ invoiceDate: input.invoiceDate,
187
+ fiscalNumber: input.fiscalNumber,
188
+ totalAmount,
189
+ paidAmount: 0,
190
+ status: 'open',
191
+ totalInstallments: input.installments.length || 1,
192
+ contactId: input.contactId,
193
+ contactName: input.contactName,
194
+ cashSessionId: input.cashSessionId,
195
+ unitId: input.unitId,
196
+ observations: input.observations,
197
+ itemsSummary: itemsSummary || undefined,
198
+ metadata: input.metadata,
199
+ tenantId,
200
+ createdAt: now(),
201
+ updatedAt: now(),
202
+ }
203
+ store.invoices.push(invoice)
204
+
205
+ // Create items
206
+ for (const itemInput of input.items) {
207
+ const itemTotal = itemInput.quantity * itemInput.unitPrice - (itemInput.discount ?? 0) + (itemInput.surcharge ?? 0)
208
+ const item: InvoiceItem = {
209
+ id: uid(),
210
+ invoiceId,
211
+ itemKind: itemInput.itemKind,
212
+ referenceId: itemInput.referenceId,
213
+ description: itemInput.description,
214
+ quantity: itemInput.quantity,
215
+ unitPrice: itemInput.unitPrice,
216
+ totalAmount: itemTotal,
217
+ discount: itemInput.discount ?? 0,
218
+ surcharge: itemInput.surcharge ?? 0,
219
+ accountId: itemInput.accountId,
220
+ costCenterId: itemInput.costCenterId,
221
+ metadata: itemInput.metadata,
222
+ createdAt: now(),
223
+ }
224
+ store.invoiceItems.push(item)
225
+ }
226
+
227
+ // Create installment movements
228
+ for (const movInput of input.installments) {
229
+ const movement: FinancialMovement = {
230
+ id: uid(),
231
+ invoiceId,
232
+ direction: input.direction,
233
+ movementKind: movInput.movementKind,
234
+ amount: movInput.amount,
235
+ paidAmount: 0,
236
+ status: 'pending',
237
+ dueDate: movInput.dueDate,
238
+ installmentNumber: movInput.installmentNumber,
239
+ paymentMethodId: movInput.paymentMethodId,
240
+ bankAccountId: movInput.bankAccountId,
241
+ notes: movInput.notes,
242
+ metadata: movInput.metadata,
243
+ tenantId,
244
+ createdAt: now(),
245
+ updatedAt: now(),
246
+ }
247
+ store.movements.push(movement)
248
+ }
249
+
250
+ return invoice
251
+ },
252
+
253
+ async updateInvoice(id: string, data: Partial<Invoice>): Promise<Invoice> {
254
+ const invoice = store.invoices.find((i) => i.id === id)
255
+ if (!invoice) throw new Error(`Invoice ${id} not found`)
256
+ Object.assign(invoice, data, { updatedAt: now() })
257
+ return invoice
258
+ },
259
+
260
+ async cancelInvoice(id: string): Promise<void> {
261
+ const invoice = store.invoices.find((i) => i.id === id)
262
+ if (!invoice) throw new Error(`Invoice ${id} not found`)
263
+ invoice.status = 'cancelled'
264
+ invoice.updatedAt = now()
265
+ // Cancel all pending movements
266
+ store.movements
267
+ .filter((m) => m.invoiceId === id && m.status === 'pending')
268
+ .forEach((m) => { m.status = 'cancelled'; m.updatedAt = now() })
269
+ },
270
+
271
+ // --- Movements ---
272
+ async getMovements(query: MovementQuery): Promise<PaginatedResult<FinancialMovement>> {
273
+ let results = [...store.movements]
274
+ if (query.direction) results = results.filter((m) => m.direction === query.direction)
275
+ if (query.status) results = results.filter((m) => matchesStatus(m.status, query.status as MovementStatus | MovementStatus[]))
276
+ if (query.bankAccountId) results = results.filter((m) => m.bankAccountId === query.bankAccountId)
277
+ if (query.cashSessionId) results = results.filter((m) => m.cashSessionId === query.cashSessionId)
278
+ if (query.dateRange) results = results.filter((m) => matchesDateRange(m.dueDate, query.dateRange))
279
+ results.sort((a, b) => a.dueDate.localeCompare(b.dueDate))
280
+ return paginate(results, query.page, query.pageSize)
281
+ },
282
+
283
+ async payMovement(input: PayMovementInput): Promise<FinancialMovement> {
284
+ const movement = store.movements.find((m) => m.id === input.movementId)
285
+ if (!movement) throw new Error(`Movement ${input.movementId} not found`)
286
+
287
+ movement.paidAmount += input.amount
288
+ movement.paymentDate = input.paymentDate
289
+ movement.paymentMethodId = input.paymentMethodId ?? movement.paymentMethodId
290
+ movement.bankAccountId = input.bankAccountId ?? movement.bankAccountId
291
+ movement.cashSessionId = input.cashSessionId ?? movement.cashSessionId
292
+ movement.cardBrand = input.cardBrand
293
+ movement.cardInstallments = input.cardInstallments
294
+ movement.status = movement.paidAmount >= movement.amount ? 'paid' : 'partial'
295
+ movement.updatedAt = now()
296
+
297
+ // Update bank account balance
298
+ if (movement.bankAccountId) {
299
+ const account = store.bankAccounts.find((a) => a.id === movement.bankAccountId)
300
+ if (account) {
301
+ if (movement.direction === 'credit') {
302
+ account.currentBalance += input.amount
303
+ } else {
304
+ account.currentBalance -= input.amount
305
+ }
306
+ account.updatedAt = now()
307
+ }
308
+ }
309
+
310
+ // Recalculate invoice status
311
+ if (movement.invoiceId) {
312
+ const invoice = store.invoices.find((i) => i.id === movement.invoiceId)
313
+ if (invoice) {
314
+ const invoiceMovements = store.movements.filter((m) => m.invoiceId === invoice.id && m.movementKind === 'bill')
315
+ invoice.paidAmount = invoiceMovements.reduce((sum, m) => sum + m.paidAmount, 0)
316
+ recalcInvoiceStatus(invoice)
317
+ }
318
+ }
319
+
320
+ return movement
321
+ },
322
+
323
+ async cancelMovement(id: string): Promise<void> {
324
+ const movement = store.movements.find((m) => m.id === id)
325
+ if (!movement) throw new Error(`Movement ${id} not found`)
326
+ movement.status = 'cancelled'
327
+ movement.updatedAt = now()
328
+ },
329
+
330
+ // --- Bank Accounts ---
331
+ async getBankAccounts(): Promise<BankAccount[]> {
332
+ return store.bankAccounts.filter((a) => a.isActive)
333
+ },
334
+
335
+ async createBankAccount(data: CreateBankAccountInput): Promise<BankAccount> {
336
+ const account: BankAccount = {
337
+ id: uid(),
338
+ name: data.name,
339
+ accountType: data.accountType,
340
+ bankName: data.bankName,
341
+ accountNumber: data.accountNumber,
342
+ agencyNumber: data.agencyNumber,
343
+ currentBalance: data.initialBalance ?? 0,
344
+ initialBalance: data.initialBalance ?? 0,
345
+ creditLimit: data.creditLimit,
346
+ dueDay: data.dueDay,
347
+ closingDay: data.closingDay,
348
+ isActive: true,
349
+ unitId: data.unitId,
350
+ tenantId,
351
+ createdAt: now(),
352
+ updatedAt: now(),
353
+ }
354
+ store.bankAccounts.push(account)
355
+ return account
356
+ },
357
+
358
+ async updateBankAccount(id: string, data: Partial<BankAccount>): Promise<BankAccount> {
359
+ const account = store.bankAccounts.find((a) => a.id === id)
360
+ if (!account) throw new Error(`Bank account ${id} not found`)
361
+ Object.assign(account, data, { updatedAt: now() })
362
+ return account
363
+ },
364
+
365
+ // --- Cash Sessions ---
366
+ async getCashSessions(bankAccountId?: string): Promise<CashSession[]> {
367
+ let results = [...store.cashSessions]
368
+ if (bankAccountId) results = results.filter((s) => s.bankAccountId === bankAccountId)
369
+ return results.sort((a, b) => b.openedAt.localeCompare(a.openedAt))
370
+ },
371
+
372
+ async getOpenSession(bankAccountId: string): Promise<CashSession | null> {
373
+ return store.cashSessions.find((s) => s.bankAccountId === bankAccountId && s.status === 'open') ?? null
374
+ },
375
+
376
+ async openCashSession(input: OpenCashSessionInput): Promise<CashSession> {
377
+ const account = store.bankAccounts.find((a) => a.id === input.bankAccountId)
378
+ const session: CashSession = {
379
+ id: uid(),
380
+ bankAccountId: input.bankAccountId,
381
+ bankAccountName: account?.name,
382
+ status: 'open',
383
+ openedAt: now(),
384
+ openedByUserId: input.openedByUserId,
385
+ openedByName: input.openedByName,
386
+ openingBalance: input.openingBalance,
387
+ unitId: input.unitId,
388
+ tenantId,
389
+ createdAt: now(),
390
+ }
391
+ store.cashSessions.push(session)
392
+ return session
393
+ },
394
+
395
+ async closeCashSession(input: CloseCashSessionInput): Promise<CashSession> {
396
+ const session = store.cashSessions.find((s) => s.id === input.sessionId)
397
+ if (!session) throw new Error(`Cash session ${input.sessionId} not found`)
398
+
399
+ // Calculate expected balance
400
+ const sessionMovements = store.movements.filter((m) => m.cashSessionId === session.id && m.status === 'paid')
401
+ const inflow = sessionMovements.filter((m) => m.direction === 'credit').reduce((s, m) => s + m.paidAmount, 0)
402
+ const outflow = sessionMovements.filter((m) => m.direction === 'debit').reduce((s, m) => s + m.paidAmount, 0)
403
+ const expectedBalance = session.openingBalance + inflow - outflow
404
+
405
+ session.status = 'closed'
406
+ session.closedAt = now()
407
+ session.closedByUserId = input.closedByUserId
408
+ session.closedByName = input.closedByName
409
+ session.closingBalance = input.closingBalance
410
+ session.expectedBalance = expectedBalance
411
+ session.difference = input.closingBalance - expectedBalance
412
+ session.notes = input.notes
413
+
414
+ return session
415
+ },
416
+
417
+ async getCashSessionSummary(sessionId: string): Promise<CashSessionSummary> {
418
+ const session = store.cashSessions.find((s) => s.id === sessionId)
419
+ if (!session) throw new Error(`Cash session ${sessionId} not found`)
420
+ const sessionMovements = store.movements.filter((m) => m.cashSessionId === sessionId && m.status === 'paid')
421
+ return {
422
+ session,
423
+ movementCount: sessionMovements.length,
424
+ totalInflow: sessionMovements.filter((m) => m.direction === 'credit').reduce((s, m) => s + m.paidAmount, 0),
425
+ totalOutflow: sessionMovements.filter((m) => m.direction === 'debit').reduce((s, m) => s + m.paidAmount, 0),
426
+ }
427
+ },
428
+
429
+ // --- Payment Methods ---
430
+ async getPaymentMethods(): Promise<PaymentMethod[]> {
431
+ return store.paymentMethods.filter((pm) => pm.isActive)
432
+ },
433
+
434
+ async getPaymentMethodTypes(): Promise<PaymentMethodType[]> {
435
+ return store.paymentMethodTypes.filter((t) => t.isActive)
436
+ },
437
+
438
+ // --- Chart of Accounts ---
439
+ async getChartOfAccounts(): Promise<ChartOfAccountsNode[]> {
440
+ return store.chartOfAccounts.filter((n) => n.isActive)
441
+ },
442
+
443
+ // --- Card Transactions ---
444
+ async getCardTransactions(dateRange?: DateRange): Promise<CardTransaction[]> {
445
+ let results = [...store.cardTransactions]
446
+ if (dateRange) results = results.filter((t) => matchesDateRange(t.expectedDate, dateRange))
447
+ return results.sort((a, b) => a.expectedDate.localeCompare(b.expectedDate))
448
+ },
449
+
450
+ // --- Statements ---
451
+ async getStatement(query: StatementQuery): Promise<StatementEntry[]> {
452
+ const account = store.bankAccounts.find((a) => a.id === query.bankAccountId)
453
+ if (!account) return []
454
+
455
+ const movements = store.movements
456
+ .filter((m) =>
457
+ m.bankAccountId === query.bankAccountId &&
458
+ m.status === 'paid' &&
459
+ matchesDateRange(m.paymentDate, query.dateRange)
460
+ )
461
+ .sort((a, b) => (a.paymentDate ?? '').localeCompare(b.paymentDate ?? ''))
462
+
463
+ let runningBalance = account.initialBalance
464
+ const entries: StatementEntry[] = []
465
+
466
+ for (const movement of movements) {
467
+ if (movement.direction === 'credit') {
468
+ runningBalance += movement.paidAmount
469
+ } else {
470
+ runningBalance -= movement.paidAmount
471
+ }
472
+ const invoice = movement.invoiceId
473
+ ? store.invoices.find((i) => i.id === movement.invoiceId)
474
+ : undefined
475
+ entries.push({ movement, invoice, runningBalance })
476
+ }
477
+
478
+ return entries
479
+ },
480
+
481
+ // --- Summary ---
482
+ async getSummary(dateRange?: DateRange): Promise<FinancialSummary> {
483
+ const totalBalance = store.bankAccounts
484
+ .filter((a) => a.isActive)
485
+ .reduce((sum, a) => sum + a.currentBalance, 0)
486
+
487
+ const pendingReceivable = store.movements.filter((m) =>
488
+ m.direction === 'credit' && m.movementKind === 'bill' && ['pending', 'partial'].includes(m.status)
489
+ )
490
+ const pendingPayable = store.movements.filter((m) =>
491
+ m.direction === 'debit' && m.movementKind === 'bill' && ['pending', 'partial'].includes(m.status)
492
+ )
493
+
494
+ const todayStr = today()
495
+ const monthStart = todayStr.slice(0, 7) + '-01'
496
+ const monthEnd = todayStr
497
+
498
+ const monthMovements = store.movements.filter((m) =>
499
+ m.status === 'paid' && matchesDateRange(m.paymentDate, dateRange ?? { from: monthStart, to: monthEnd })
500
+ )
501
+
502
+ const overdueReceivable = pendingReceivable.filter((m) => m.dueDate < todayStr)
503
+ const overduePayable = pendingPayable.filter((m) => m.dueDate < todayStr)
504
+
505
+ return {
506
+ totalBalance,
507
+ totalReceivable: pendingReceivable.reduce((s, m) => s + (m.amount - m.paidAmount), 0),
508
+ totalPayable: pendingPayable.reduce((s, m) => s + (m.amount - m.paidAmount), 0),
509
+ monthlyInflow: monthMovements.filter((m) => m.direction === 'credit').reduce((s, m) => s + m.paidAmount, 0),
510
+ monthlyOutflow: monthMovements.filter((m) => m.direction === 'debit').reduce((s, m) => s + m.paidAmount, 0),
511
+ overdueReceivableCount: overdueReceivable.length,
512
+ overdueReceivableAmount: overdueReceivable.reduce((s, m) => s + (m.amount - m.paidAmount), 0),
513
+ overduePayableCount: overduePayable.length,
514
+ overduePayableAmount: overduePayable.reduce((s, m) => s + (m.amount - m.paidAmount), 0),
515
+ }
516
+ },
517
+ }
518
+
519
+ return provider
520
+ }