@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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import React, { useState, useCallback, useMemo } 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 } from '@fayz-ai/saas'
|
|
10
|
+
import { SummaryView } from './views/SummaryView'
|
|
11
|
+
import { PayablesView } from './views/PayablesView'
|
|
12
|
+
import { ReceivablesView } from './views/ReceivablesView'
|
|
13
|
+
import { CashRegistersView } from './views/CashRegistersView'
|
|
14
|
+
import { StatementsView } from './views/StatementsView'
|
|
15
|
+
import { CommissionsView } from './views/CommissionsView'
|
|
16
|
+
import { CardsView } from './views/CardsView'
|
|
17
|
+
import { FinancialGeneralSettings } from './components/FinancialGeneralSettings'
|
|
18
|
+
import { FinancialOnboarding } from './components/FinancialOnboarding'
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// View intent — what subpage + mode to show
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export interface ViewIntent {
|
|
25
|
+
view: string
|
|
26
|
+
mode?: 'list' | 'new' | 'edit' | 'detail'
|
|
27
|
+
editId?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseIntent(activeView: string): ViewIntent {
|
|
31
|
+
// `base` collapses 'payables-detail' / 'payables-edit' etc; `id` is the canonical :id param.
|
|
32
|
+
const { base, id } = parseViewId(activeView)
|
|
33
|
+
const m = base.match(/^(payables|receivables)-(new|list|detail|edit)$/)
|
|
34
|
+
if (m) return { view: m[1], mode: m[2] as ViewIntent['mode'], editId: id }
|
|
35
|
+
return { view: activeView }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Nav builder
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
function buildNav(
|
|
43
|
+
config: ResolvedFinancialConfig,
|
|
44
|
+
activeView: string,
|
|
45
|
+
navigate: (view: string, hash?: string) => void,
|
|
46
|
+
t: (key: string, params?: Record<string, string | number>) => string,
|
|
47
|
+
): ModuleNavItem[] {
|
|
48
|
+
const items: ModuleNavItem[] = [
|
|
49
|
+
{
|
|
50
|
+
id: 'summary',
|
|
51
|
+
label: t('financial.nav.summary'),
|
|
52
|
+
icon: 'BarChart3',
|
|
53
|
+
active: activeView === 'summary',
|
|
54
|
+
onClick: () => navigate('summary'),
|
|
55
|
+
},
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
if (config.modules.payables) {
|
|
59
|
+
items.push({
|
|
60
|
+
id: 'payables',
|
|
61
|
+
label: t('financial.nav.payables'),
|
|
62
|
+
icon: 'ArrowUpCircle',
|
|
63
|
+
active: activeView.startsWith('payables'),
|
|
64
|
+
children: [
|
|
65
|
+
{ id: 'payables-new', label: t('financial.nav.new'), active: activeView === 'payables-new', onClick: () => navigate('payables-new') },
|
|
66
|
+
{ id: 'payables-list', label: t('financial.nav.list'), active: activeView === 'payables-list', onClick: () => navigate('payables-list') },
|
|
67
|
+
{ id: 'payables-recurring', label: t('financial.nav.recurringExpenses'), active: activeView === 'payables-recurring', onClick: () => navigate('payables-recurring') },
|
|
68
|
+
],
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (config.modules.receivables) {
|
|
73
|
+
items.push({
|
|
74
|
+
id: 'receivables',
|
|
75
|
+
label: t('financial.nav.receivables'),
|
|
76
|
+
icon: 'ArrowDownCircle',
|
|
77
|
+
active: activeView.startsWith('receivables'),
|
|
78
|
+
children: [
|
|
79
|
+
{ id: 'receivables-new', label: t('financial.nav.new'), active: activeView === 'receivables-new', onClick: () => navigate('receivables-new') },
|
|
80
|
+
{ id: 'receivables-list', label: t('financial.nav.list'), active: activeView === 'receivables-list', onClick: () => navigate('receivables-list') },
|
|
81
|
+
],
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (config.modules.cashRegisters) {
|
|
86
|
+
items.push({
|
|
87
|
+
id: 'cash-registers',
|
|
88
|
+
label: t('financial.nav.cashRegisters'),
|
|
89
|
+
icon: 'Landmark',
|
|
90
|
+
active: activeView === 'cash-registers',
|
|
91
|
+
onClick: () => navigate('cash-registers'),
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (config.modules.statements) {
|
|
96
|
+
items.push({
|
|
97
|
+
id: 'statements',
|
|
98
|
+
label: t('financial.nav.statements'),
|
|
99
|
+
icon: 'Receipt',
|
|
100
|
+
active: activeView === 'statements',
|
|
101
|
+
onClick: () => navigate('statements'),
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (config.modules.commissions) {
|
|
106
|
+
items.push({
|
|
107
|
+
id: 'commissions',
|
|
108
|
+
label: t('financial.nav.commissions'),
|
|
109
|
+
icon: 'Users',
|
|
110
|
+
children: [
|
|
111
|
+
{ id: 'commissions-overview', label: t('financial.nav.overview'), active: activeView === 'commissions', onClick: () => navigate('commissions') },
|
|
112
|
+
{ id: 'commissions-rules', label: t('financial.nav.rules'), active: activeView === 'commissions', onClick: () => navigate('commissions') },
|
|
113
|
+
],
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (config.modules.cards) {
|
|
118
|
+
items.push({
|
|
119
|
+
id: 'cards',
|
|
120
|
+
label: t('financial.nav.cards'),
|
|
121
|
+
icon: 'CreditCard',
|
|
122
|
+
children: [
|
|
123
|
+
{ id: 'cards-overview', label: t('financial.nav.overview'), active: activeView === 'cards', onClick: () => navigate('cards') },
|
|
124
|
+
{ id: 'cards-reconciliation', label: t('financial.nav.reconciliation'), active: activeView === 'cards', onClick: () => navigate('cards') },
|
|
125
|
+
],
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return items
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Main page component
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
export function FinancialPage({ config, provider, store, registries }: {
|
|
137
|
+
config: ResolvedFinancialConfig
|
|
138
|
+
provider: FinancialDataProvider
|
|
139
|
+
store: StoreApi<FinancialUIState>
|
|
140
|
+
registries?: PluginRegistryDef[]
|
|
141
|
+
}) {
|
|
142
|
+
const { view, direction, navigate } = useModuleNavigation('/financial', {
|
|
143
|
+
summary: 0,
|
|
144
|
+
'payables-list': 0, 'payables-new': 1, 'receivables-list': 0, 'receivables-new': 1,
|
|
145
|
+
'cash-registers': 0, statements: 0, commissions: 0, cards: 0,
|
|
146
|
+
settings: 1,
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const [onboardingComplete, setOnboardingComplete] = useState(() => {
|
|
150
|
+
try { return localStorage.getItem('saas-core:financial-onboarded') === 'true' } catch { return false }
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const t = useTranslation()
|
|
154
|
+
const intent = parseIntent(view)
|
|
155
|
+
const isSettings = view === 'settings'
|
|
156
|
+
const nav = buildNav(config, view, navigate, t)
|
|
157
|
+
|
|
158
|
+
const quickActions = useMemo<PluginQuickAction[]>(() => {
|
|
159
|
+
const actions: PluginQuickAction[] = []
|
|
160
|
+
if (config.modules.payables) {
|
|
161
|
+
actions.push({
|
|
162
|
+
id: 'new-payable',
|
|
163
|
+
label: t('financial.quickActions.newPayable'),
|
|
164
|
+
icon: 'ArrowUpCircle',
|
|
165
|
+
description: t('financial.quickActions.newPayableDesc'),
|
|
166
|
+
action: () => navigate('payables-new'),
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
if (config.modules.receivables) {
|
|
170
|
+
actions.push({
|
|
171
|
+
id: 'new-receivable',
|
|
172
|
+
label: t('financial.quickActions.newReceivable'),
|
|
173
|
+
icon: 'ArrowDownCircle',
|
|
174
|
+
description: t('financial.quickActions.newReceivableDesc'),
|
|
175
|
+
action: () => navigate('receivables-new'),
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
if (config.modules.cashRegisters) {
|
|
179
|
+
actions.push({
|
|
180
|
+
id: 'open-cash',
|
|
181
|
+
label: t('financial.quickActions.openCashRegister'),
|
|
182
|
+
icon: 'Landmark',
|
|
183
|
+
description: t('financial.quickActions.openCashRegisterDesc'),
|
|
184
|
+
action: () => navigate('cash-registers'),
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
return actions
|
|
188
|
+
}, [config.modules])
|
|
189
|
+
|
|
190
|
+
function handleOnboardingComplete() {
|
|
191
|
+
setOnboardingComplete(true)
|
|
192
|
+
try { localStorage.setItem('saas-core:financial-onboarded', 'true') } catch {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Show onboarding on first visit
|
|
196
|
+
if (!onboardingComplete) {
|
|
197
|
+
return (
|
|
198
|
+
<FinancialContextProvider config={config} provider={provider} store={store}>
|
|
199
|
+
<FinancialOnboarding onComplete={handleOnboardingComplete} />
|
|
200
|
+
</FinancialContextProvider>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Settings view
|
|
205
|
+
if (isSettings && registries && registries.length > 0) {
|
|
206
|
+
return (
|
|
207
|
+
<FinancialContextProvider config={config} provider={provider} store={store}>
|
|
208
|
+
<PageTransition transitionKey="settings" direction={direction}>
|
|
209
|
+
<div style={{ padding: '24px' }}>
|
|
210
|
+
<div style={{ marginBottom: '16px' }}>
|
|
211
|
+
<h1 style={{ fontSize: '20px', fontWeight: 600, margin: 0 }}>{t('financial.settingsPage.title')}</h1>
|
|
212
|
+
<p style={{ color: 'var(--muted-foreground, #6b7280)', margin: '4px 0 0', fontSize: '14px' }}>{t('financial.settingsPage.subtitle')}</p>
|
|
213
|
+
</div>
|
|
214
|
+
<FinancialGeneralSettings />
|
|
215
|
+
</div>
|
|
216
|
+
</PageTransition>
|
|
217
|
+
</FinancialContextProvider>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function renderView() {
|
|
222
|
+
switch (intent.view) {
|
|
223
|
+
case 'payables':
|
|
224
|
+
return <PayablesView intent={intent} onNavigate={navigate} />
|
|
225
|
+
case 'receivables':
|
|
226
|
+
return <ReceivablesView intent={intent} onNavigate={navigate} />
|
|
227
|
+
case 'cash-registers':
|
|
228
|
+
return <CashRegistersView />
|
|
229
|
+
case 'statements':
|
|
230
|
+
return <StatementsView />
|
|
231
|
+
case 'commissions':
|
|
232
|
+
return <CommissionsView />
|
|
233
|
+
case 'cards':
|
|
234
|
+
return <CardsView />
|
|
235
|
+
default:
|
|
236
|
+
return <SummaryView />
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<FinancialContextProvider config={config} provider={provider} store={store}>
|
|
242
|
+
<ModulePage
|
|
243
|
+
title={config.labels.pageTitle}
|
|
244
|
+
subtitle={config.labels.pageSubtitle}
|
|
245
|
+
nav={nav}
|
|
246
|
+
showHeader={intent.view === 'summary' || view === 'summary'}
|
|
247
|
+
viewKey={view}
|
|
248
|
+
direction={direction}
|
|
249
|
+
headerAction={
|
|
250
|
+
<ModuleActionBar
|
|
251
|
+
quickActions={quickActions}
|
|
252
|
+
settingsPath={registries && registries.length > 0 ? '/settings/financial' : undefined}
|
|
253
|
+
settingsLabel={t('financial.settingsPage.title')}
|
|
254
|
+
/>
|
|
255
|
+
}
|
|
256
|
+
>
|
|
257
|
+
{renderView()}
|
|
258
|
+
</ModulePage>
|
|
259
|
+
</FinancialContextProvider>
|
|
260
|
+
)
|
|
261
|
+
}
|
package/src/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
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 createSaasApp 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: payment_method_types, payment_methods, bank_accounts, cash_register_sessions, invoices, invoice_items, financial_movements
|
|
104
|
+
2. `002_chart_of_accounts.sql` — chart_of_accounts (self-referencing tree), cost_centers
|
|
105
|
+
3. `003_card_brands.sql` — card_brands
|
|
106
|
+
|
|
107
|
+
All tables use `tenant_id` referencing `saas_core.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
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { SettingsGroup, ToggleRow } from '@fayz-ai/saas'
|
|
3
|
+
import { useTranslation } from '@fayz-ai/core'
|
|
4
|
+
|
|
5
|
+
export function FinancialGeneralSettings() {
|
|
6
|
+
const t = useTranslation()
|
|
7
|
+
return (
|
|
8
|
+
<div className="space-y-4">
|
|
9
|
+
<SettingsGroup title={t('financial.settings.invoices')} description={t('financial.settings.invoicesDesc')}>
|
|
10
|
+
<ToggleRow label={t('financial.settings.autoInstallments')} description={t('financial.settings.autoInstallmentsDesc')} checked={true} onChange={() => {}} />
|
|
11
|
+
<ToggleRow label={t('financial.settings.requireDoc')} description={t('financial.settings.requireDocDesc')} checked={false} onChange={() => {}} />
|
|
12
|
+
<ToggleRow label={t('financial.settings.allowPartial')} description={t('financial.settings.allowPartialDesc')} checked={true} onChange={() => {}} />
|
|
13
|
+
</SettingsGroup>
|
|
14
|
+
|
|
15
|
+
<SettingsGroup title={t('financial.settings.cashRegister')} description={t('financial.settings.cashRegisterDesc')}>
|
|
16
|
+
<ToggleRow label={t('financial.settings.requireOpening')} description={t('financial.settings.requireOpeningDesc')} checked={true} onChange={() => {}} />
|
|
17
|
+
<ToggleRow label={t('financial.settings.autoClose')} description={t('financial.settings.autoCloseDesc')} checked={false} onChange={() => {}} />
|
|
18
|
+
</SettingsGroup>
|
|
19
|
+
|
|
20
|
+
<SettingsGroup title={t('financial.settings.notifications')} description={t('financial.settings.notificationsDesc')}>
|
|
21
|
+
<ToggleRow label={t('financial.settings.overdueAlerts')} description={t('financial.settings.overdueAlertsDesc')} checked={true} onChange={() => {}} />
|
|
22
|
+
<ToggleRow label={t('financial.settings.dailySummary')} description={t('financial.settings.dailySummaryDesc')} checked={false} onChange={() => {}} />
|
|
23
|
+
</SettingsGroup>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { DollarSign, Landmark, CreditCard, BookOpen, ChevronRight, Check } from 'lucide-react'
|
|
3
|
+
import { useTranslation } from '@fayz-ai/core'
|
|
4
|
+
import { Button } from '@fayz-ai/ui'
|
|
5
|
+
|
|
6
|
+
const STEPS = [
|
|
7
|
+
{ id: 'welcome', icon: DollarSign, titleKey: 'financial.onboarding.welcome', descKey: 'financial.onboarding.description' },
|
|
8
|
+
{ id: 'accounts', icon: Landmark, titleKey: 'financial.onboarding.accounts.title', descKey: 'financial.onboarding.accounts.description' },
|
|
9
|
+
{ id: 'payments', icon: CreditCard, titleKey: 'financial.onboarding.payments.title', descKey: 'financial.onboarding.payments.description' },
|
|
10
|
+
{ id: 'chart', icon: BookOpen, titleKey: 'financial.onboarding.chart.title', descKey: 'financial.onboarding.chart.description' },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export function FinancialOnboarding({ onComplete }: { onComplete: () => void }) {
|
|
14
|
+
const t = useTranslation()
|
|
15
|
+
const [step, setStep] = useState(0)
|
|
16
|
+
const current = STEPS[step]
|
|
17
|
+
const isLast = step === STEPS.length - 1
|
|
18
|
+
const Icon = current.icon
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex items-center justify-center py-12">
|
|
22
|
+
<div className="w-full max-w-lg">
|
|
23
|
+
{/* Progress */}
|
|
24
|
+
<div className="flex items-center gap-1 mb-8">
|
|
25
|
+
{STEPS.map((s, i) => (
|
|
26
|
+
<div
|
|
27
|
+
key={s.id}
|
|
28
|
+
className={`h-1 flex-1 rounded-full transition-colors ${
|
|
29
|
+
i <= step ? 'bg-primary' : 'bg-muted'
|
|
30
|
+
}`}
|
|
31
|
+
/>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
{/* Card */}
|
|
36
|
+
<div className="rounded-card border bg-card p-8 text-center space-y-6 shadow-sm">
|
|
37
|
+
<div className="flex justify-center">
|
|
38
|
+
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-primary/10">
|
|
39
|
+
<Icon className="h-8 w-8 text-primary" />
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div>
|
|
44
|
+
<h2 className="text-xl font-bold">{t(current.titleKey)}</h2>
|
|
45
|
+
<p className="text-sm text-muted-foreground mt-2 max-w-sm mx-auto">{t(current.descKey)}</p>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{/* Step-specific hint */}
|
|
49
|
+
{step > 0 && step < STEPS.length - 1 && (
|
|
50
|
+
<div className="rounded-lg bg-muted/30 px-4 py-3 text-xs text-muted-foreground">
|
|
51
|
+
{t('financial.onboarding.configureHint')}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
{/* Actions */}
|
|
56
|
+
<div className="flex items-center justify-center gap-3 pt-2">
|
|
57
|
+
{step > 0 && (
|
|
58
|
+
<Button variant="outline" size="lg" onClick={() => setStep(step - 1)}>
|
|
59
|
+
{t('financial.onboarding.back')}
|
|
60
|
+
</Button>
|
|
61
|
+
)}
|
|
62
|
+
{isLast ? (
|
|
63
|
+
<Button variant="default" size="lg" onClick={onComplete}>
|
|
64
|
+
<Check className="h-4 w-4" /> {t('financial.onboarding.startUsing')}
|
|
65
|
+
</Button>
|
|
66
|
+
) : (
|
|
67
|
+
<Button variant="default" size="lg" onClick={() => setStep(step + 1)}>
|
|
68
|
+
{step === 0 ? t('financial.onboarding.getStarted') : t('financial.onboarding.next')}
|
|
69
|
+
<ChevronRight className="h-4 w-4" />
|
|
70
|
+
</Button>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* Skip */}
|
|
75
|
+
{!isLast && step === 0 && (
|
|
76
|
+
<button
|
|
77
|
+
onClick={onComplete}
|
|
78
|
+
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
79
|
+
>
|
|
80
|
+
{t('financial.onboarding.skip')}
|
|
81
|
+
</button>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { ChevronLeft } from 'lucide-react'
|
|
3
|
+
import { PluginRegistryManager } from '@fayz-ai/saas'
|
|
4
|
+
import type { PluginRegistryDef } from '@fayz-ai/core'
|
|
5
|
+
import { useTranslation } from '@fayz-ai/core'
|
|
6
|
+
|
|
7
|
+
export function FinancialSettings({ registries, routeBase, onClose }: {
|
|
8
|
+
registries: PluginRegistryDef[]
|
|
9
|
+
routeBase: string
|
|
10
|
+
onClose: () => void
|
|
11
|
+
}) {
|
|
12
|
+
const t = useTranslation()
|
|
13
|
+
return (
|
|
14
|
+
<div className="space-y-4">
|
|
15
|
+
<div className="flex items-center gap-3">
|
|
16
|
+
<button
|
|
17
|
+
onClick={onClose}
|
|
18
|
+
className="flex h-8 w-8 items-center justify-center rounded-lg border hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors"
|
|
19
|
+
>
|
|
20
|
+
<ChevronLeft className="h-4 w-4" />
|
|
21
|
+
</button>
|
|
22
|
+
<div>
|
|
23
|
+
<h2 className="text-lg font-bold">{t('financial.settingsPage.title')}</h2>
|
|
24
|
+
<p className="text-xs text-muted-foreground">{t('financial.settingsPage.subtitle')}</p>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<PluginRegistryManager registries={registries} routeBase={routeBase} />
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|