@fayz-ai/plugin-financial 0.9.0 → 0.9.2

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 (95) hide show
  1. package/README.md +3 -3
  2. package/dist/components/PersonStatementWidget.d.ts +1 -1
  3. package/dist/components/PersonStatementWidget.d.ts.map +1 -1
  4. package/dist/{FinancialContext.d.ts → context.d.ts} +4 -2
  5. package/dist/context.d.ts.map +1 -0
  6. package/dist/data/registries.d.ts.map +1 -0
  7. package/dist/index.d.ts +13 -4
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1426 -1426
  10. package/dist/index.js.map +1 -1
  11. package/dist/integrations/stripe/connectorDef.d.ts +14 -0
  12. package/dist/integrations/stripe/connectorDef.d.ts.map +1 -0
  13. package/dist/integrations/stripe/data.d.ts +43 -0
  14. package/dist/integrations/stripe/data.d.ts.map +1 -0
  15. package/dist/lib/message-events.d.ts +12 -0
  16. package/dist/lib/message-events.d.ts.map +1 -0
  17. package/dist/{quick-add.d.ts → lib/quick-add.d.ts} +1 -1
  18. package/dist/lib/quick-add.d.ts.map +1 -0
  19. package/dist/locales/en.d.ts.map +1 -1
  20. package/dist/locales/pt-BR.d.ts.map +1 -1
  21. package/dist/{FinancialPage.d.ts → views/FinancialPage.d.ts} +3 -3
  22. package/dist/views/FinancialPage.d.ts.map +1 -0
  23. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  24. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  25. package/dist/views/InvoiceListView.d.ts.map +1 -1
  26. package/dist/views/PayablesView.d.ts +1 -1
  27. package/dist/views/PayablesView.d.ts.map +1 -1
  28. package/dist/views/ReceivablesView.d.ts +1 -1
  29. package/dist/views/ReceivablesView.d.ts.map +1 -1
  30. package/dist/views/StatementsView.d.ts.map +1 -1
  31. package/dist/views/dashboardWidgets.d.ts +1 -1
  32. package/dist/views/dashboardWidgets.d.ts.map +1 -1
  33. package/package.json +12 -14
  34. package/dist/FinancialContext.d.ts.map +0 -1
  35. package/dist/FinancialPage.d.ts.map +0 -1
  36. package/dist/contract.test.d.ts +0 -2
  37. package/dist/contract.test.d.ts.map +0 -1
  38. package/dist/quick-add.d.ts.map +0 -1
  39. package/dist/registries.d.ts.map +0 -1
  40. package/src/FinancialContext.tsx +0 -59
  41. package/src/FinancialPage.tsx +0 -413
  42. package/src/README.md +0 -155
  43. package/src/components/FinancialGeneralSettings.tsx +0 -39
  44. package/src/components/FinancialOnboarding.tsx +0 -87
  45. package/src/components/FinancialSettings.tsx +0 -31
  46. package/src/components/PaymentModal.tsx +0 -369
  47. package/src/components/PersonStatementWidget.tsx +0 -133
  48. package/src/components/TransferModal.tsx +0 -150
  49. package/src/contract.test.ts +0 -58
  50. package/src/data/capability.test.ts +0 -135
  51. package/src/data/index.ts +0 -3
  52. package/src/data/mock.ts +0 -842
  53. package/src/data/supabase.ts +0 -968
  54. package/src/data/tables.ts +0 -13
  55. package/src/data/types.ts +0 -86
  56. package/src/index.ts +0 -487
  57. package/src/lib/tenant.ts +0 -3
  58. package/src/locales/en.ts +0 -493
  59. package/src/locales/index.ts +0 -7
  60. package/src/locales/pt-BR.ts +0 -493
  61. package/src/migrations/000_plg_rename.sql +0 -30
  62. package/src/migrations/001_financial_base.sql +0 -109
  63. package/src/migrations/002_chart_of_accounts.sql +0 -28
  64. package/src/migrations/003_card_brands.sql +0 -12
  65. package/src/migrations/004_order_to_cash.sql +0 -178
  66. package/src/migrations/005_seed_defaults.sql +0 -38
  67. package/src/migrations/006_rls_policies.sql +0 -37
  68. package/src/migrations/006b_extract_fee_amount.sql +0 -20
  69. package/src/migrations/007_reconciliation.sql +0 -32
  70. package/src/migrations/007b_movement_payment_method_type.sql +0 -6
  71. package/src/migrations/008_split_payment_movements.sql +0 -29
  72. package/src/migrations/009_agent_rpcs.sql +0 -165
  73. package/src/migrations/010_invoice_internal.sql +0 -133
  74. package/src/migrations/011_nfe.sql +0 -58
  75. package/src/migrations/index.ts +0 -923
  76. package/src/quick-add.ts +0 -58
  77. package/src/registries.ts +0 -308
  78. package/src/store.ts +0 -329
  79. package/src/types.ts +0 -592
  80. package/src/views/CardsView.tsx +0 -213
  81. package/src/views/CashRegistersView.tsx +0 -597
  82. package/src/views/CommissionsView.tsx +0 -18
  83. package/src/views/InvoiceDetailView.tsx +0 -616
  84. package/src/views/InvoiceFormView.tsx +0 -945
  85. package/src/views/InvoiceListView.tsx +0 -323
  86. package/src/views/NotasFiscaisView.tsx +0 -209
  87. package/src/views/PayablesView.tsx +0 -48
  88. package/src/views/QuickTransactionForm.tsx +0 -548
  89. package/src/views/ReceivablesView.tsx +0 -48
  90. package/src/views/ReconciliationView.tsx +0 -211
  91. package/src/views/StatementsView.tsx +0 -335
  92. package/src/views/SummaryView.tsx +0 -203
  93. package/src/views/dashboardWidgets.tsx +0 -437
  94. package/src/views/navReferrer.ts +0 -24
  95. /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
@@ -1,413 +0,0 @@
1
- import React, { useState, useCallback, useMemo, useEffect } from 'react'
2
- import type { StoreApi } from 'zustand/vanilla'
3
- import { useTranslation } from '@fayz-ai/core'
4
- import { ModulePage, PageTransition, type ModuleNavItem } from '@fayz-ai/ui'
5
- import { FinancialContextProvider, type ResolvedFinancialConfig } from './FinancialContext'
6
- import type { FinancialDataProvider } from './data/types'
7
- import type { FinancialUIState } from './store'
8
- import type { PluginRegistryDef, PluginQuickAction } from '@fayz-ai/core'
9
- import { useModuleNavigation, ModuleActionBar, parseViewId, PluginSettingsPanel, EntitlementGate, UpgradePrompt, useModuleNavAccess, PermissionGate, usePermissionOptional } from '@fayz-ai/saas'
10
- import { SummaryView } from './views/SummaryView'
11
- import { QuickTransactionForm } from './views/QuickTransactionForm'
12
- import { usePendingQuickAdd, consumeQuickAdd } from './quick-add'
13
- import type { QuickTransactionType } from './types'
14
- import { Plus } from 'lucide-react'
15
- import { Button } from '@fayz-ai/ui'
16
- import { PayablesView } from './views/PayablesView'
17
- import { ReceivablesView } from './views/ReceivablesView'
18
- import { CashRegistersView } from './views/CashRegistersView'
19
- import { StatementsView } from './views/StatementsView'
20
- import { CommissionsView } from './views/CommissionsView'
21
- import { CardsView } from './views/CardsView'
22
- import { ReconciliationView } from './views/ReconciliationView'
23
- import { NotasFiscaisView } from './views/NotasFiscaisView'
24
- import { FinancialGeneralSettings } from './components/FinancialGeneralSettings'
25
- import { FinancialOnboarding } from './components/FinancialOnboarding'
26
-
27
- // ---------------------------------------------------------------------------
28
- // View intent — what subpage + mode to show
29
- // ---------------------------------------------------------------------------
30
-
31
- export interface ViewIntent {
32
- view: string
33
- mode?: 'list' | 'new' | 'edit' | 'detail'
34
- editId?: string
35
- }
36
-
37
- function parseIntent(activeView: string): ViewIntent {
38
- // `base` collapses 'payables-detail' / 'payables-edit' etc; `id` is the canonical :id param.
39
- const { base, id } = parseViewId(activeView)
40
- const m = base.match(/^(payables|receivables)-(new|list|detail|edit)$/)
41
- if (m) return { view: m[1], mode: m[2] as ViewIntent['mode'], editId: id }
42
- return { view: activeView }
43
- }
44
-
45
- // ---------------------------------------------------------------------------
46
- // Nav builder
47
- // ---------------------------------------------------------------------------
48
-
49
- function buildNav(
50
- config: ResolvedFinancialConfig,
51
- activeView: string,
52
- navigate: (view: string, hash?: string) => void,
53
- t: (key: string, params?: Record<string, string | number>) => string,
54
- ): ModuleNavItem[] {
55
- const items: ModuleNavItem[] = [
56
- {
57
- id: 'summary',
58
- label: t('financial.nav.summary'),
59
- icon: 'BarChart3',
60
- active: activeView === 'summary',
61
- onClick: () => navigate('summary'),
62
- },
63
- ]
64
-
65
- if (config.modules.payables) {
66
- items.push({
67
- id: 'payables',
68
- label: t('financial.nav.payables'),
69
- icon: 'ArrowUpCircle',
70
- active: activeView.startsWith('payables'),
71
- children: [
72
- { id: 'payables-new', label: t('financial.nav.new'), active: activeView === 'payables-new', onClick: () => navigate('payables-new') },
73
- { id: 'payables-list', label: t('financial.nav.list'), active: activeView === 'payables-list', onClick: () => navigate('payables-list') },
74
- { id: 'payables-recurring', label: t('financial.nav.recurringExpenses'), active: activeView === 'payables-recurring', onClick: () => navigate('payables-recurring') },
75
- ],
76
- })
77
- }
78
-
79
- if (config.modules.receivables) {
80
- items.push({
81
- id: 'receivables',
82
- label: t('financial.nav.receivables'),
83
- icon: 'ArrowDownCircle',
84
- active: activeView.startsWith('receivables'),
85
- children: [
86
- { id: 'receivables-new', label: t('financial.nav.new'), active: activeView === 'receivables-new', onClick: () => navigate('receivables-new') },
87
- { id: 'receivables-list', label: t('financial.nav.list'), active: activeView === 'receivables-list', onClick: () => navigate('receivables-list') },
88
- ],
89
- })
90
- }
91
-
92
- if (config.modules.cashRegisters) {
93
- items.push({
94
- id: 'cash-registers',
95
- label: t('financial.nav.cashRegisters'),
96
- icon: 'Landmark',
97
- active: activeView === 'cash-registers',
98
- onClick: () => navigate('cash-registers'),
99
- })
100
- }
101
-
102
- // Statements (extract) is core to the financial module — always shown, not gated per app.
103
- items.push({
104
- id: 'statements',
105
- label: t('financial.nav.statements'),
106
- icon: 'Receipt',
107
- active: activeView === 'statements',
108
- onClick: () => navigate('statements'),
109
- })
110
-
111
- if (config.modules.reconciliation) {
112
- items.push({
113
- id: 'reconciliation',
114
- label: t('financial.nav.reconciliation'),
115
- icon: 'ArrowLeftRight',
116
- active: activeView === 'reconciliation',
117
- onClick: () => navigate('reconciliation'),
118
- })
119
- }
120
-
121
- if (config.modules.notasFiscais) {
122
- items.push({
123
- id: 'notas-fiscais',
124
- label: t('financial.nav.notasFiscais'),
125
- icon: 'FileText',
126
- active: activeView === 'notas-fiscais',
127
- onClick: () => navigate('notas-fiscais'),
128
- })
129
- }
130
-
131
- if (config.modules.commissions) {
132
- items.push({
133
- id: 'commissions',
134
- label: t('financial.nav.commissions'),
135
- icon: 'Users',
136
- children: [
137
- { id: 'commissions-overview', label: t('financial.nav.overview'), active: activeView === 'commissions', onClick: () => navigate('commissions') },
138
- { id: 'commissions-rules', label: t('financial.nav.rules'), active: activeView === 'commissions', onClick: () => navigate('commissions') },
139
- ],
140
- })
141
- }
142
-
143
- if (config.modules.cards) {
144
- items.push({
145
- id: 'cards',
146
- label: t('financial.nav.cards'),
147
- icon: 'CreditCard',
148
- children: [
149
- { id: 'cards-overview', label: t('financial.nav.overview'), active: activeView === 'cards', onClick: () => navigate('cards') },
150
- { id: 'cards-reconciliation', label: t('financial.nav.reconciliation'), active: activeView === 'cards', onClick: () => navigate('cards') },
151
- ],
152
- })
153
- }
154
-
155
- // Tag each nav item with its access feature id (item id → feature via the
156
- // resolved navFeatureMap). `useModuleNavAccess` then hides role-denied links
157
- // and Crown-badges plan-denied ones; ids absent from the map stay ungated.
158
- for (const item of items) {
159
- const feature = config.navFeatureMap[item.id]
160
- if (feature) item.feature = feature
161
- }
162
-
163
- return items
164
- }
165
-
166
- // ---------------------------------------------------------------------------
167
- // Main page component
168
- // ---------------------------------------------------------------------------
169
-
170
- export function FinancialPage({ config, provider, store, registries }: {
171
- config: ResolvedFinancialConfig
172
- provider: FinancialDataProvider
173
- store: StoreApi<FinancialUIState>
174
- registries?: PluginRegistryDef[]
175
- }) {
176
- const { view, direction, navigate, previousView, back } = useModuleNavigation('/financial', {
177
- summary: 0,
178
- 'payables-list': 0, 'payables-new': 1, 'receivables-list': 0, 'receivables-new': 1,
179
- 'cash-registers': 0, statements: 0, reconciliation: 0, commissions: 0, cards: 0,
180
- 'notas-fiscais': 0,
181
- settings: 1,
182
- })
183
-
184
- const [onboardingComplete, setOnboardingComplete] = useState(() => {
185
- try { return localStorage.getItem('saas-core:financial-onboarded') === 'true' } catch { return false }
186
- })
187
-
188
- // Skip the welcome wizard when the provider already has transactions — a user
189
- // (or a seeded demo/dogfood provider) with existing data should land on the
190
- // populated Summary, not a "let's get started" flow.
191
- useEffect(() => {
192
- if (onboardingComplete) return
193
- let cancelled = false
194
- void provider.getMovements({ pageSize: 1 })
195
- .then((res) => { if (!cancelled && res.total > 0) setOnboardingComplete(true) })
196
- .catch(() => { /* leave onboarding as-is on error */ })
197
- return () => { cancelled = true }
198
- }, [onboardingComplete, provider])
199
-
200
- const t = useTranslation()
201
- // Role-side create gate (QA: create affordances in custom plugin UIs must honor
202
- // the role's `create` action, not just the route-level `read` guard). The
203
- // financial module owns a single `/financial` route gated on read, so every
204
- // in-page "new" affordance below is otherwise ungated.
205
- const can = usePermissionOptional()
206
- const canCreate = can('financial', 'create')
207
- const intent = parseIntent(view)
208
- const isSettings = view === 'settings'
209
- // Access-aware sub-nav: role denial hides the link, plan denial badges it with
210
- // a Crown (freemium discovery). The click still navigates — the gated CONTENT
211
- // renders the UpgradePrompt (see renderView's EntitlementGates below).
212
- const nav = useModuleNavAccess(buildNav(config, view, navigate, t))
213
-
214
- // FAY-1225 "log money in a few taps": a reachable quick-add that opens the
215
- // Mobills-grade transaction sheet (default = expense). Prominent primary
216
- // button on desktop + a thumb-reachable FAB on mobile.
217
- const [quickAddOpen, setQuickAddOpen] = useState(false)
218
- const [quickAddType, setQuickAddType] = useState<QuickTransactionType>('expense')
219
- // FAY-1226 "snap a receipt": when true, the sheet foregrounds the attachment
220
- // step + auto-opens the camera/gallery picker.
221
- const [quickAddReceipt, setQuickAddReceipt] = useState(false)
222
- const openQuickAdd = useCallback((type: QuickTransactionType = 'expense') => {
223
- setQuickAddType(type)
224
- setQuickAddReceipt(false)
225
- setQuickAddOpen(true)
226
- }, [])
227
-
228
- // FAY-1242: open the sheet on a global quick-add request (the app shell's
229
- // elevated center "+" button). One-shot — consumed so it never re-fires.
230
- const pendingQuickAdd = usePendingQuickAdd()
231
- useEffect(() => {
232
- if (!pendingQuickAdd) return
233
- const req = consumeQuickAdd()
234
- if (!req) return
235
- // Defense in depth: the app-shell global "+" can request a quick-add even for
236
- // a read-only member — swallow it when the role lacks `create`.
237
- if (!canCreate) return
238
- setQuickAddType(req.type)
239
- setQuickAddReceipt(req.receipt)
240
- setQuickAddOpen(true)
241
- }, [pendingQuickAdd])
242
-
243
- // One-primary-action rule (FAY-1247): each module surfaces exactly ONE primary
244
- // add affordance and config picks its face — never both. When quickAdd is the
245
- // primary action (B2C, e.g. norman), suppress the ERP "+ New" menu here so the
246
- // header shows only the quick-add "Nova transação" button. Payables are still
247
- // covered: the quick-add sheet's "Pago: Não" creates an unpaid conta a pagar.
248
- // When quickAdd is off (ERP default, beauty) this returns the payable/receivable/
249
- // cash actions and no quick-add buttons render — exactly today's behavior.
250
- const quickActions = useMemo<PluginQuickAction[]>(() => {
251
- if (config.quickAdd) return []
252
- // No `create` action → surface no "new" quick actions (payable/receivable/cash
253
- // are all create affordances under the one-primary-action rule).
254
- if (!canCreate) return []
255
- const actions: PluginQuickAction[] = []
256
- if (config.modules.payables) {
257
- actions.push({
258
- id: 'new-payable',
259
- label: t('financial.quickActions.newPayable'),
260
- icon: 'ArrowUpCircle',
261
- description: t('financial.quickActions.newPayableDesc'),
262
- action: () => navigate('payables-new'),
263
- })
264
- }
265
- if (config.modules.receivables) {
266
- actions.push({
267
- id: 'new-receivable',
268
- label: t('financial.quickActions.newReceivable'),
269
- icon: 'ArrowDownCircle',
270
- description: t('financial.quickActions.newReceivableDesc'),
271
- action: () => navigate('receivables-new'),
272
- })
273
- }
274
- if (config.modules.cashRegisters) {
275
- actions.push({
276
- id: 'open-cash',
277
- label: t('financial.quickActions.openCashRegister'),
278
- icon: 'Landmark',
279
- description: t('financial.quickActions.openCashRegisterDesc'),
280
- action: () => navigate('cash-registers'),
281
- })
282
- }
283
- return actions
284
- }, [config.modules, config.quickAdd, canCreate, navigate, t])
285
-
286
- function handleOnboardingComplete() {
287
- setOnboardingComplete(true)
288
- try { localStorage.setItem('saas-core:financial-onboarded', 'true') } catch {}
289
- }
290
-
291
- // Show onboarding on first visit
292
- if (!onboardingComplete) {
293
- return (
294
- <FinancialContextProvider config={config} provider={provider} store={store}>
295
- <FinancialOnboarding onComplete={handleOnboardingComplete} />
296
- </FinancialContextProvider>
297
- )
298
- }
299
-
300
- // Settings view
301
- if (isSettings && registries && registries.length > 0) {
302
- return (
303
- <FinancialContextProvider config={config} provider={provider} store={store}>
304
- <PageTransition transitionKey="settings" direction={direction}>
305
- <div style={{ padding: '24px' }}>
306
- <PluginSettingsPanel
307
- title={t('financial.settingsPage.title')}
308
- subtitle={t('financial.settingsPage.subtitle')}
309
- generalSettings={<FinancialGeneralSettings />}
310
- registries={registries}
311
- hostPluginId="financial"
312
- routeBase="/settings/financial"
313
- />
314
- </div>
315
- </PageTransition>
316
- </FinancialContextProvider>
317
- )
318
- }
319
-
320
- function renderView() {
321
- switch (intent.view) {
322
- case 'payables':
323
- return <PayablesView intent={intent} onNavigate={navigate} previousView={previousView} back={back} />
324
- case 'receivables':
325
- return <ReceivablesView intent={intent} onNavigate={navigate} previousView={previousView} back={back} />
326
- case 'cash-registers':
327
- return <CashRegistersView />
328
- case 'statements':
329
- return <StatementsView onNavigate={navigate} />
330
- case 'reconciliation':
331
- return <ReconciliationView />
332
- case 'commissions':
333
- return <CommissionsView />
334
- case 'cards':
335
- return <CardsView />
336
- case 'notas-fiscais':
337
- return <NotasFiscaisView />
338
- default:
339
- return <SummaryView />
340
- }
341
- }
342
-
343
- // Content side of the sub-module paywall. A sub-module is premium ONLY when
344
- // the app mapped its nav id to a feature (`navFeatureMap`) — the plugin ships
345
- // no default, so an unmapped view renders ungated. Symmetric with the nav
346
- // above: same map drives the Crown on the link and the gate on the content,
347
- // so an app can never end up with a free-looking link over locked content.
348
- // The gate must live here because an internal tab is not a shell route — the
349
- // route guard never sees it (QA finding B35). Full UpgradePrompt (not the
350
- // inline banner) fills the whole module panel.
351
- function renderGatedView() {
352
- const feature = config.navFeatureMap[intent.view]
353
- if (!feature) return renderView()
354
- return (
355
- <EntitlementGate feature={feature} fallback={<UpgradePrompt feature={feature} />}>
356
- {renderView()}
357
- </EntitlementGate>
358
- )
359
- }
360
-
361
- return (
362
- <FinancialContextProvider config={config} provider={provider} store={store}>
363
- <ModulePage
364
- title={config.labels.pageTitle}
365
- subtitle={config.labels.pageSubtitle}
366
- nav={nav}
367
- showHeader={intent.view === 'summary' || view === 'summary'}
368
- viewKey={view}
369
- direction={direction}
370
- headerAction={
371
- <div className="flex items-center gap-2">
372
- {config.quickAdd && (
373
- // One-primary-action rule (FAY-1247): quick-add is the single primary
374
- // add affordance in B2C mode. The receipt entry point lives inside the
375
- // sheet's "Fotografar ou anexar recibo" attachment step, so no separate
376
- // "Enviar recibo" button is needed here.
377
- // Gated on the role's `create` action so read-only members don't see it.
378
- <PermissionGate feature="financial" action="create">
379
- <Button
380
- size="sm"
381
- className="hidden md:inline-flex"
382
- onClick={() => openQuickAdd('expense')}
383
- >
384
- <Plus className="h-4 w-4" />
385
- {t('financial.quickTx.newTransaction')}
386
- </Button>
387
- </PermissionGate>
388
- )}
389
- <ModuleActionBar
390
- quickActions={quickActions}
391
- settingsPath={registries && registries.length > 0 ? '/settings/financial' : undefined}
392
- settingsLabel={t('financial.settingsPage.title')}
393
- />
394
- </div>
395
- }
396
- >
397
- {renderGatedView()}
398
-
399
- {/* FAY-1242: the mobile quick-add + receipt FAB stack was removed — the
400
- app shell's elevated center "+" bottom-nav button now owns global
401
- quick-add (via openQuickAdd), and receipts stay reachable through the
402
- form's Anexo step. Desktop keeps its header buttons above. */}
403
-
404
- <QuickTransactionForm
405
- open={quickAddOpen}
406
- onOpenChange={setQuickAddOpen}
407
- defaultType={quickAddType}
408
- focusReceipt={quickAddReceipt}
409
- />
410
- </ModulePage>
411
- </FinancialContextProvider>
412
- )
413
- }
package/src/README.md DELETED
@@ -1,155 +0,0 @@
1
- # Financial Plugin
2
-
3
- Accounts payable/receivable, cash registers, bank statements, and financial reporting for any SaaS vertical.
4
-
5
- ## Quick Start
6
-
7
- ```typescript
8
- import { createFinancialPlugin } from '@fayz-ai/saas-core/plugins/financial'
9
-
10
- // In your defineSaas config:
11
- plugins: [
12
- createFinancialPlugin({
13
- currency: { code: 'BRL', locale: 'pt-BR', symbol: 'R$' },
14
- }),
15
- ]
16
- ```
17
-
18
- ## Configuration
19
-
20
- | Option | Type | Default | Description |
21
- |--------|------|---------|-------------|
22
- | `modules.payables` | boolean | true | Accounts payable |
23
- | `modules.receivables` | boolean | true | Accounts receivable |
24
- | `modules.cashRegisters` | boolean | true | Cash register sessions |
25
- | `modules.statements` | boolean | true | Bank statements |
26
- | `modules.commissions` | boolean | true | Staff commissions |
27
- | `modules.cards` | boolean | true | Card transactions |
28
- | `currency` | object | BRL/pt-BR | Currency code, locale, symbol |
29
- | `itemTypes` | array | service, product, other | Invoice line item types |
30
- | `enableServiceExecution` | boolean | false | Track service execution on items |
31
- | `contactEntity` | object | client | Person archetype kind for contacts |
32
- | `locations` | array | [] | Business units (shows unit picker when 1+) |
33
- | `dataProvider` | FinancialDataProvider | mock | Custom data provider |
34
- | `navPosition` | number | 6 | Navigation order |
35
-
36
- ## Architecture
37
-
38
- ```
39
- src/plugins/financial/
40
- index.ts # createFinancialPlugin() factory
41
- types.ts # Pure TS domain types (zero deps)
42
- registries.ts # CRUD entities: payment methods, chart of accounts, etc.
43
- store.ts # Zustand UI state + provider actions
44
- FinancialContext.tsx # React contexts: config, provider, store
45
- FinancialPage.tsx # Main page with ModulePage layout
46
- data/
47
- types.ts # FinancialDataProvider interface
48
- mock.ts # In-memory mock provider
49
- views/ # All sub-views (Summary, Invoices, Cash, etc.)
50
- components/ # Settings, onboarding
51
- migrations/ # SQL migration files
52
- ```
53
-
54
- ### Data Flow
55
-
56
- ```
57
- createFinancialPlugin(options)
58
- → resolves config (labels, currency, modules, itemTypes)
59
- → creates provider (mock or custom)
60
- → creates zustand store (bound to provider)
61
- → returns PluginManifest with route, navigation, registries, settings tab
62
-
63
- FinancialPage (rendered by plugin route)
64
- → wraps children in FinancialContextProvider (config + provider + store)
65
- → useModuleNavigation handles view switching + hash routing + animations
66
- → each view calls useFinancialStore(selector) to read/write data
67
- ```
68
-
69
- ## Type System
70
-
71
- Core entities (in `types.ts`):
72
-
73
- | Entity | Purpose |
74
- |--------|---------|
75
- | `Invoice` | Header: direction (debit/credit), date, contact, total, status |
76
- | `InvoiceItem` | Line item: kind, description, qty, price, discount |
77
- | `FinancialMovement` | Installments & payments: amount, due date, status |
78
- | `BankAccount` | Bank, cash register, credit card, digital wallet |
79
- | `CashSession` | Open/close sessions with balance reconciliation |
80
- | `PaymentMethod` | Configured methods with discount/interest rules |
81
- | `ChartOfAccountsNode` | Hierarchical ledger accounts |
82
- | `CardTransaction` | Credit card installment tracking |
83
-
84
- Status enums: `InvoiceStatus`, `MovementStatus`, `TransactionDirection`, `MovementKind`
85
-
86
- ## Registries (Plugin Settings)
87
-
88
- Accessible via gear icon in Financial module or in global Settings > Financial tab:
89
-
90
- | Registry | Editable | Seed Data |
91
- |----------|----------|-----------|
92
- | Payment Method | Yes | — |
93
- | Ledger Account | Yes | Default chart of accounts |
94
- | Cost Center | Yes | "General" |
95
- | Bank Account | Yes | — |
96
- | Payment Type | Read-only | Cash, PIX, Credit Card, Debit Card, Transfer, Check |
97
- | Card Brand | Read-only | Visa, Mastercard, Amex, Elo, etc. |
98
-
99
- ## Database Migrations
100
-
101
- Run in order:
102
-
103
- 1. `001_financial_base.sql` — Core tables: plg_financial_payment_method_types, plg_financial_payment_methods, plg_financial_bank_accounts, plg_financial_cash_register_sessions, invoices, invoice_items, plg_financial_movements
104
- 2. `002_chart_of_accounts.sql` — plg_financial_chart_of_accounts (self-referencing tree), plg_financial_cost_centers
105
- 3. `003_card_brands.sql` — plg_financial_card_brands
106
-
107
- All tables use `tenant_id` referencing `public.tenants(id)` for multi-tenant isolation.
108
-
109
- ## Views
110
-
111
- | View | Route suffix | Description |
112
- |------|-------------|-------------|
113
- | Summary | `/financial` | Dashboard with KPIs, cash flow, overdue alerts |
114
- | Payables List | `/financial/payables/list` | Filterable invoice list (debit) |
115
- | Payables New | `/financial/payables/new` | Create payable invoice |
116
- | Receivables List | `/financial/receivables/list` | Filterable invoice list (credit) |
117
- | Cash Registers | `/financial/cash/registers` | Open/close sessions |
118
- | Statements | `/financial/statements` | Bank account statement viewer |
119
- | Cards | `/financial/cards` | Card transaction reconciliation |
120
- | Settings | `/financial/settings/*` | Registry CRUD management |
121
-
122
- ## Extending
123
-
124
- ### Custom Data Provider
125
-
126
- ```typescript
127
- import type { FinancialDataProvider } from '@fayz-ai/saas-core/plugins/financial'
128
-
129
- const supabaseProvider: FinancialDataProvider = {
130
- async getInvoices(query) { /* Supabase query */ },
131
- async createInvoice(input) { /* Supabase insert */ },
132
- // ... implement all methods
133
- }
134
-
135
- createFinancialPlugin({ dataProvider: supabaseProvider })
136
- ```
137
-
138
- ### Vertical Customization
139
-
140
- ```typescript
141
- // Restaurant — no commissions, custom item types
142
- createFinancialPlugin({
143
- modules: { commissions: false },
144
- itemTypes: [
145
- { value: 'menu_item', label: 'Menu Item' },
146
- { value: 'beverage', label: 'Beverage' },
147
- ],
148
- })
149
-
150
- // Beauty salon — with service execution tracking
151
- createFinancialPlugin({
152
- enableServiceExecution: true,
153
- contactEntity: { archetypeKind: 'client', label: 'Client' },
154
- })
155
- ```
@@ -1,39 +0,0 @@
1
- import React from 'react'
2
- import { SettingsGroup, ToggleRow, useTenantPluginSettings } from '@fayz-ai/saas'
3
- import { useTranslation } from '@fayz-ai/core'
4
-
5
- // Per-tenant defaults = the values that used to be hardcoded here. Persisted +
6
- // re-hydrated per tenant; behavioural consumption is a follow-up.
7
- const FINANCIAL_DEFAULTS = {
8
- autoInstallments: true,
9
- requireDoc: false,
10
- allowPartial: true,
11
- requireOpening: true,
12
- autoClose: false,
13
- overdueAlerts: true,
14
- dailySummary: false,
15
- }
16
-
17
- export function FinancialGeneralSettings() {
18
- const t = useTranslation()
19
- const s = useTenantPluginSettings('financial', FINANCIAL_DEFAULTS)
20
- return (
21
- <div className="space-y-4">
22
- <SettingsGroup title={t('financial.settings.invoices')} description={t('financial.settings.invoicesDesc')}>
23
- <ToggleRow label={t('financial.settings.autoInstallments')} description={t('financial.settings.autoInstallmentsDesc')} checked={s.get('autoInstallments')} onChange={(v) => s.set('autoInstallments', v)} />
24
- <ToggleRow label={t('financial.settings.requireDoc')} description={t('financial.settings.requireDocDesc')} checked={s.get('requireDoc')} onChange={(v) => s.set('requireDoc', v)} />
25
- <ToggleRow label={t('financial.settings.allowPartial')} description={t('financial.settings.allowPartialDesc')} checked={s.get('allowPartial')} onChange={(v) => s.set('allowPartial', v)} />
26
- </SettingsGroup>
27
-
28
- <SettingsGroup title={t('financial.settings.cashRegister')} description={t('financial.settings.cashRegisterDesc')}>
29
- <ToggleRow label={t('financial.settings.requireOpening')} description={t('financial.settings.requireOpeningDesc')} checked={s.get('requireOpening')} onChange={(v) => s.set('requireOpening', v)} />
30
- <ToggleRow label={t('financial.settings.autoClose')} description={t('financial.settings.autoCloseDesc')} checked={s.get('autoClose')} onChange={(v) => s.set('autoClose', v)} />
31
- </SettingsGroup>
32
-
33
- <SettingsGroup title={t('financial.settings.notifications')} description={t('financial.settings.notificationsDesc')}>
34
- <ToggleRow label={t('financial.settings.overdueAlerts')} description={t('financial.settings.overdueAlertsDesc')} checked={s.get('overdueAlerts')} onChange={(v) => s.set('overdueAlerts', v)} />
35
- <ToggleRow label={t('financial.settings.dailySummary')} description={t('financial.settings.dailySummaryDesc')} checked={s.get('dailySummary')} onChange={(v) => s.set('dailySummary', v)} />
36
- </SettingsGroup>
37
- </div>
38
- )
39
- }