@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.
- package/README.md +3 -3
- package/dist/components/PersonStatementWidget.d.ts +1 -1
- package/dist/components/PersonStatementWidget.d.ts.map +1 -1
- package/dist/{FinancialContext.d.ts → context.d.ts} +4 -2
- package/dist/context.d.ts.map +1 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1426 -1426
- package/dist/index.js.map +1 -1
- package/dist/integrations/stripe/connectorDef.d.ts +14 -0
- package/dist/integrations/stripe/connectorDef.d.ts.map +1 -0
- package/dist/integrations/stripe/data.d.ts +43 -0
- package/dist/integrations/stripe/data.d.ts.map +1 -0
- package/dist/lib/message-events.d.ts +12 -0
- package/dist/lib/message-events.d.ts.map +1 -0
- package/dist/{quick-add.d.ts → lib/quick-add.d.ts} +1 -1
- package/dist/lib/quick-add.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/{FinancialPage.d.ts → views/FinancialPage.d.ts} +3 -3
- package/dist/views/FinancialPage.d.ts.map +1 -0
- package/dist/views/InvoiceDetailView.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/dist/views/PayablesView.d.ts +1 -1
- package/dist/views/PayablesView.d.ts.map +1 -1
- package/dist/views/ReceivablesView.d.ts +1 -1
- package/dist/views/ReceivablesView.d.ts.map +1 -1
- package/dist/views/StatementsView.d.ts.map +1 -1
- package/dist/views/dashboardWidgets.d.ts +1 -1
- package/dist/views/dashboardWidgets.d.ts.map +1 -1
- package/package.json +12 -14
- package/dist/FinancialContext.d.ts.map +0 -1
- package/dist/FinancialPage.d.ts.map +0 -1
- package/dist/contract.test.d.ts +0 -2
- package/dist/contract.test.d.ts.map +0 -1
- package/dist/quick-add.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/FinancialContext.tsx +0 -59
- package/src/FinancialPage.tsx +0 -413
- package/src/README.md +0 -155
- package/src/components/FinancialGeneralSettings.tsx +0 -39
- package/src/components/FinancialOnboarding.tsx +0 -87
- package/src/components/FinancialSettings.tsx +0 -31
- package/src/components/PaymentModal.tsx +0 -369
- package/src/components/PersonStatementWidget.tsx +0 -133
- package/src/components/TransferModal.tsx +0 -150
- package/src/contract.test.ts +0 -58
- package/src/data/capability.test.ts +0 -135
- package/src/data/index.ts +0 -3
- package/src/data/mock.ts +0 -842
- package/src/data/supabase.ts +0 -968
- package/src/data/tables.ts +0 -13
- package/src/data/types.ts +0 -86
- package/src/index.ts +0 -487
- package/src/lib/tenant.ts +0 -3
- package/src/locales/en.ts +0 -493
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -493
- package/src/migrations/000_plg_rename.sql +0 -30
- package/src/migrations/001_financial_base.sql +0 -109
- package/src/migrations/002_chart_of_accounts.sql +0 -28
- package/src/migrations/003_card_brands.sql +0 -12
- package/src/migrations/004_order_to_cash.sql +0 -178
- package/src/migrations/005_seed_defaults.sql +0 -38
- package/src/migrations/006_rls_policies.sql +0 -37
- package/src/migrations/006b_extract_fee_amount.sql +0 -20
- package/src/migrations/007_reconciliation.sql +0 -32
- package/src/migrations/007b_movement_payment_method_type.sql +0 -6
- package/src/migrations/008_split_payment_movements.sql +0 -29
- package/src/migrations/009_agent_rpcs.sql +0 -165
- package/src/migrations/010_invoice_internal.sql +0 -133
- package/src/migrations/011_nfe.sql +0 -58
- package/src/migrations/index.ts +0 -923
- package/src/quick-add.ts +0 -58
- package/src/registries.ts +0 -308
- package/src/store.ts +0 -329
- package/src/types.ts +0 -592
- package/src/views/CardsView.tsx +0 -213
- package/src/views/CashRegistersView.tsx +0 -597
- package/src/views/CommissionsView.tsx +0 -18
- package/src/views/InvoiceDetailView.tsx +0 -616
- package/src/views/InvoiceFormView.tsx +0 -945
- package/src/views/InvoiceListView.tsx +0 -323
- package/src/views/NotasFiscaisView.tsx +0 -209
- package/src/views/PayablesView.tsx +0 -48
- package/src/views/QuickTransactionForm.tsx +0 -548
- package/src/views/ReceivablesView.tsx +0 -48
- package/src/views/ReconciliationView.tsx +0 -211
- package/src/views/StatementsView.tsx +0 -335
- package/src/views/SummaryView.tsx +0 -203
- package/src/views/dashboardWidgets.tsx +0 -437
- package/src/views/navReferrer.ts +0 -24
- /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
package/src/FinancialPage.tsx
DELETED
|
@@ -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
|
-
}
|