@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.
- package/dist/FinancialContext.d.ts +44 -0
- package/dist/FinancialContext.d.ts.map +1 -0
- package/dist/FinancialPage.d.ts +18 -0
- package/dist/FinancialPage.d.ts.map +1 -0
- package/dist/components/FinancialGeneralSettings.d.ts +3 -0
- package/dist/components/FinancialGeneralSettings.d.ts.map +1 -0
- package/dist/components/FinancialOnboarding.d.ts +5 -0
- package/dist/components/FinancialOnboarding.d.ts.map +1 -0
- package/dist/components/FinancialSettings.d.ts +8 -0
- package/dist/components/FinancialSettings.d.ts.map +1 -0
- package/dist/components/PaymentModal.d.ts +8 -0
- package/dist/components/PaymentModal.d.ts.map +1 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/mock.d.ts +9 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/supabase.d.ts +3 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/types.d.ts +27 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/index.cjs +4937 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4930 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tenant.d.ts +3 -0
- package/dist/lib/tenant.d.ts.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts.map +1 -0
- package/dist/registries.d.ts +3 -0
- package/dist/registries.d.ts.map +1 -0
- package/dist/store.d.ts +38 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +302 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views/CardsView.d.ts +3 -0
- package/dist/views/CardsView.d.ts.map +1 -0
- package/dist/views/CashRegistersView.d.ts +3 -0
- package/dist/views/CashRegistersView.d.ts.map +1 -0
- package/dist/views/CommissionsView.d.ts +3 -0
- package/dist/views/CommissionsView.d.ts.map +1 -0
- package/dist/views/InvoiceDetailView.d.ts +9 -0
- package/dist/views/InvoiceDetailView.d.ts.map +1 -0
- package/dist/views/InvoiceFormView.d.ts +13 -0
- package/dist/views/InvoiceFormView.d.ts.map +1 -0
- package/dist/views/InvoiceListView.d.ts +8 -0
- package/dist/views/InvoiceListView.d.ts.map +1 -0
- package/dist/views/PayablesView.d.ts +7 -0
- package/dist/views/PayablesView.d.ts.map +1 -0
- package/dist/views/ReceivablesView.d.ts +7 -0
- package/dist/views/ReceivablesView.d.ts.map +1 -0
- package/dist/views/StatementsView.d.ts +3 -0
- package/dist/views/StatementsView.d.ts.map +1 -0
- package/dist/views/SummaryView.d.ts +9 -0
- package/dist/views/SummaryView.d.ts.map +1 -0
- package/dist/views/dashboardWidgets.d.ts +11 -0
- package/dist/views/dashboardWidgets.d.ts.map +1 -0
- package/package.json +55 -0
- package/src/FinancialContext.tsx +47 -0
- package/src/FinancialPage.tsx +261 -0
- package/src/README.md +155 -0
- package/src/components/FinancialGeneralSettings.tsx +26 -0
- package/src/components/FinancialOnboarding.tsx +87 -0
- package/src/components/FinancialSettings.tsx +31 -0
- package/src/components/PaymentModal.tsx +322 -0
- package/src/data/index.ts +2 -0
- package/src/data/mock.ts +520 -0
- package/src/data/supabase.ts +509 -0
- package/src/data/types.ts +53 -0
- package/src/index.ts +307 -0
- package/src/lib/tenant.ts +3 -0
- package/src/locales/en.ts +331 -0
- package/src/locales/index.ts +7 -0
- package/src/locales/pt-BR.ts +331 -0
- package/src/migrations/001_financial_base.sql +109 -0
- package/src/migrations/002_chart_of_accounts.sql +28 -0
- package/src/migrations/003_card_brands.sql +12 -0
- package/src/migrations/004_order_to_cash.sql +164 -0
- package/src/migrations/005_seed_defaults.sql +38 -0
- package/src/registries.ts +305 -0
- package/src/store.ts +221 -0
- package/src/types.ts +359 -0
- package/src/views/CardsView.tsx +74 -0
- package/src/views/CashRegistersView.tsx +597 -0
- package/src/views/CommissionsView.tsx +18 -0
- package/src/views/InvoiceDetailView.tsx +595 -0
- package/src/views/InvoiceFormView.tsx +900 -0
- package/src/views/InvoiceListView.tsx +177 -0
- package/src/views/PayablesView.tsx +37 -0
- package/src/views/ReceivablesView.tsx +37 -0
- package/src/views/StatementsView.tsx +123 -0
- package/src/views/SummaryView.tsx +12 -0
- package/src/views/dashboardWidgets.tsx +191 -0
package/src/data/mock.ts
ADDED
|
@@ -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
|
+
}
|