@fayz-ai/plugin-financial 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) 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 +10 -4
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1208 -1368
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/message-events.d.ts +12 -0
  12. package/dist/lib/message-events.d.ts.map +1 -0
  13. package/dist/{quick-add.d.ts → lib/quick-add.d.ts} +1 -1
  14. package/dist/lib/quick-add.d.ts.map +1 -0
  15. package/dist/locales/en.d.ts.map +1 -1
  16. package/dist/locales/pt-BR.d.ts.map +1 -1
  17. package/dist/{FinancialPage.d.ts → views/FinancialPage.d.ts} +3 -3
  18. package/dist/views/FinancialPage.d.ts.map +1 -0
  19. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  20. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  21. package/dist/views/InvoiceListView.d.ts.map +1 -1
  22. package/dist/views/PayablesView.d.ts +1 -1
  23. package/dist/views/PayablesView.d.ts.map +1 -1
  24. package/dist/views/ReceivablesView.d.ts +1 -1
  25. package/dist/views/ReceivablesView.d.ts.map +1 -1
  26. package/dist/views/StatementsView.d.ts.map +1 -1
  27. package/dist/views/dashboardWidgets.d.ts +1 -1
  28. package/dist/views/dashboardWidgets.d.ts.map +1 -1
  29. package/package.json +12 -14
  30. package/dist/FinancialContext.d.ts.map +0 -1
  31. package/dist/FinancialPage.d.ts.map +0 -1
  32. package/dist/contract.test.d.ts +0 -2
  33. package/dist/contract.test.d.ts.map +0 -1
  34. package/dist/quick-add.d.ts.map +0 -1
  35. package/dist/registries.d.ts.map +0 -1
  36. package/src/FinancialContext.tsx +0 -59
  37. package/src/FinancialPage.tsx +0 -413
  38. package/src/README.md +0 -155
  39. package/src/components/FinancialGeneralSettings.tsx +0 -39
  40. package/src/components/FinancialOnboarding.tsx +0 -87
  41. package/src/components/FinancialSettings.tsx +0 -31
  42. package/src/components/PaymentModal.tsx +0 -369
  43. package/src/components/PersonStatementWidget.tsx +0 -133
  44. package/src/components/TransferModal.tsx +0 -150
  45. package/src/contract.test.ts +0 -58
  46. package/src/data/capability.test.ts +0 -135
  47. package/src/data/index.ts +0 -3
  48. package/src/data/mock.ts +0 -842
  49. package/src/data/supabase.ts +0 -968
  50. package/src/data/tables.ts +0 -13
  51. package/src/data/types.ts +0 -86
  52. package/src/index.ts +0 -487
  53. package/src/lib/tenant.ts +0 -3
  54. package/src/locales/en.ts +0 -493
  55. package/src/locales/index.ts +0 -7
  56. package/src/locales/pt-BR.ts +0 -493
  57. package/src/migrations/000_plg_rename.sql +0 -30
  58. package/src/migrations/001_financial_base.sql +0 -109
  59. package/src/migrations/002_chart_of_accounts.sql +0 -28
  60. package/src/migrations/003_card_brands.sql +0 -12
  61. package/src/migrations/004_order_to_cash.sql +0 -178
  62. package/src/migrations/005_seed_defaults.sql +0 -38
  63. package/src/migrations/006_rls_policies.sql +0 -37
  64. package/src/migrations/006b_extract_fee_amount.sql +0 -20
  65. package/src/migrations/007_reconciliation.sql +0 -32
  66. package/src/migrations/007b_movement_payment_method_type.sql +0 -6
  67. package/src/migrations/008_split_payment_movements.sql +0 -29
  68. package/src/migrations/009_agent_rpcs.sql +0 -165
  69. package/src/migrations/010_invoice_internal.sql +0 -133
  70. package/src/migrations/011_nfe.sql +0 -58
  71. package/src/migrations/index.ts +0 -923
  72. package/src/quick-add.ts +0 -58
  73. package/src/registries.ts +0 -308
  74. package/src/store.ts +0 -329
  75. package/src/types.ts +0 -592
  76. package/src/views/CardsView.tsx +0 -213
  77. package/src/views/CashRegistersView.tsx +0 -597
  78. package/src/views/CommissionsView.tsx +0 -18
  79. package/src/views/InvoiceDetailView.tsx +0 -616
  80. package/src/views/InvoiceFormView.tsx +0 -945
  81. package/src/views/InvoiceListView.tsx +0 -323
  82. package/src/views/NotasFiscaisView.tsx +0 -209
  83. package/src/views/PayablesView.tsx +0 -48
  84. package/src/views/QuickTransactionForm.tsx +0 -548
  85. package/src/views/ReceivablesView.tsx +0 -48
  86. package/src/views/ReconciliationView.tsx +0 -211
  87. package/src/views/StatementsView.tsx +0 -335
  88. package/src/views/SummaryView.tsx +0 -203
  89. package/src/views/dashboardWidgets.tsx +0 -437
  90. package/src/views/navReferrer.ts +0 -24
  91. /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
@@ -1,150 +0,0 @@
1
- import React, { useState, useEffect } from 'react'
2
- import { createPortal } from 'react-dom'
3
- import { X, ArrowRightLeft } from 'lucide-react'
4
- import { useFinancialConfig, useFinancialStore } from '../FinancialContext'
5
- import { useTranslation } from '@fayz-ai/core'
6
- import { CurrencyInput, DatePicker, Button } from '@fayz-ai/ui'
7
-
8
- export function TransferModal({ onClose, onDone, defaultFromId }: {
9
- onClose: () => void
10
- onDone: () => void
11
- defaultFromId?: string
12
- }) {
13
- const t = useTranslation()
14
- const { currency } = useFinancialConfig()
15
- const bankAccounts = useFinancialStore((s) => s.bankAccounts)
16
- const createTransfer = useFinancialStore((s) => s.createTransfer)
17
- const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts)
18
-
19
- const [fromAccountId, setFromAccountId] = useState(defaultFromId ?? '')
20
- const [toAccountId, setToAccountId] = useState('')
21
- const [amount, setAmount] = useState(0)
22
- const [date, setDate] = useState(new Date().toISOString().slice(0, 10))
23
- const [notes, setNotes] = useState('')
24
- const [saving, setSaving] = useState(false)
25
- const [visible, setVisible] = useState(false)
26
-
27
- useEffect(() => {
28
- fetchBankAccounts()
29
- requestAnimationFrame(() => setVisible(true))
30
- }, [])
31
-
32
- function handleClose() {
33
- setVisible(false)
34
- setTimeout(onClose, 200)
35
- }
36
-
37
- const sameAccount = !!fromAccountId && fromAccountId === toAccountId
38
- const canSubmit = !!fromAccountId && !!toAccountId && !sameAccount && amount > 0 && !saving
39
-
40
- // Enter anywhere in the modal submits (unless typing in a textarea / Shift+Enter).
41
- function handleKeyDown(e: React.KeyboardEvent) {
42
- if (e.key !== 'Enter' || e.shiftKey) return
43
- if ((e.target as HTMLElement).tagName === 'TEXTAREA') return
44
- e.preventDefault()
45
- if (canSubmit) void handleSubmit()
46
- }
47
-
48
- async function handleSubmit() {
49
- if (!canSubmit) return
50
- setSaving(true)
51
- try {
52
- await createTransfer({ fromAccountId, toAccountId, amount, date, notes: notes || undefined })
53
- setVisible(false)
54
- setTimeout(onDone, 200)
55
- } catch {
56
- // toast handled by store
57
- } finally {
58
- setSaving(false)
59
- }
60
- }
61
-
62
- const selectClass = 'w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm'
63
-
64
- return createPortal(
65
- <div
66
- className="fixed inset-0 z-50 flex items-center justify-center transition-colors duration-200"
67
- style={{ backgroundColor: visible ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0)' }}
68
- onClick={handleClose}
69
- >
70
- <div
71
- className="w-full max-w-md rounded-modal border bg-card shadow-lg mx-4 transition-all duration-200 max-h-[90vh] overflow-y-auto"
72
- style={{ opacity: visible ? 1 : 0, transform: visible ? 'scale(1) translateY(0)' : 'scale(0.95) translateY(8px)' }}
73
- onClick={(e) => e.stopPropagation()}
74
- onKeyDown={handleKeyDown}
75
- >
76
- {/* Header */}
77
- <div className="flex items-center justify-between px-5 py-4 border-b">
78
- <div className="flex items-center gap-2">
79
- <ArrowRightLeft className="h-4 w-4 text-warning" />
80
- <h3 className="text-base font-semibold">{t('financial.transfer.title')}</h3>
81
- </div>
82
- <button onClick={handleClose} className="p-1 text-muted-foreground hover:text-foreground transition-colors">
83
- <X className="h-4 w-4" />
84
- </button>
85
- </div>
86
-
87
- <div className="px-5 py-4 space-y-4">
88
- <div className="grid grid-cols-2 gap-3">
89
- <div>
90
- <label className="text-xs font-medium text-muted-foreground">{t('financial.transfer.from')}</label>
91
- <select value={fromAccountId} onChange={(e) => setFromAccountId(e.target.value)} className={selectClass}>
92
- <option value="">{t('financial.transfer.selectAccount')}</option>
93
- {bankAccounts.map((a) => <option key={a.id} value={a.id}>{a.name}</option>)}
94
- </select>
95
- </div>
96
- <div>
97
- <label className="text-xs font-medium text-muted-foreground">{t('financial.transfer.to')}</label>
98
- <select value={toAccountId} onChange={(e) => setToAccountId(e.target.value)} className={selectClass}>
99
- <option value="">{t('financial.transfer.selectAccount')}</option>
100
- {bankAccounts.map((a) => <option key={a.id} value={a.id}>{a.name}</option>)}
101
- </select>
102
- </div>
103
- </div>
104
-
105
- {sameAccount && (
106
- <p className="text-xs text-destructive">{t('financial.transfer.sameAccount')}</p>
107
- )}
108
-
109
- <div className="grid grid-cols-2 gap-3">
110
- <CurrencyInput
111
- label={t('financial.transfer.amount')}
112
- value={amount}
113
- onChange={setAmount}
114
- symbol={currency.symbol}
115
- locale={currency.locale}
116
- currencyCode={currency.code}
117
- />
118
- <div>
119
- <label className="text-xs font-medium text-muted-foreground">{t('financial.transfer.date')}</label>
120
- <DatePicker value={date} onChange={setDate} className="mt-1" />
121
- </div>
122
- </div>
123
-
124
- <div>
125
- <label className="text-xs font-medium text-muted-foreground">{t('financial.transfer.notes')}</label>
126
- <input
127
- type="text"
128
- value={notes}
129
- onChange={(e) => setNotes(e.target.value)}
130
- placeholder={t('financial.transfer.optionalNotes')}
131
- className="w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
132
- />
133
- </div>
134
- </div>
135
-
136
- {/* Actions */}
137
- <div className="flex justify-end gap-2 px-5 py-4 border-t">
138
- <Button variant="outline" size="sm" onClick={handleClose}>
139
- {t('financial.transfer.cancel')}
140
- </Button>
141
- <Button variant="default" size="sm" onClick={handleSubmit} disabled={!canSubmit}>
142
- <ArrowRightLeft className="h-3.5 w-3.5" />
143
- {saving ? t('financial.transfer.processing') : t('financial.transfer.submit')}
144
- </Button>
145
- </div>
146
- </div>
147
- </div>,
148
- document.body,
149
- )
150
- }
@@ -1,58 +0,0 @@
1
- // Integrity test for the shared plugin-contract assertions (@fayz-ai/core/testing).
2
- // Exercises the gate's rules with inline manifests (no UI import) so the tool
3
- // itself is proven: a conformant manifest passes; each malformed shape throws.
4
- // Real plugins call assertPluginManifestContract(theirManifest) in an env where
5
- // their UI bundle loads (app-level / jsdom). See docs/PLUGIN-PATTERNS.md → integrity.
6
- import { describe, it, expect } from 'vitest'
7
- import { assertPluginManifestContract, PluginContractError } from '@fayz-ai/core/testing'
8
- import type { PluginManifest } from '@fayz-ai/core'
9
-
10
- const Dummy = () => null
11
-
12
- function manifest(overrides: Partial<PluginManifest> = {}): PluginManifest {
13
- return {
14
- id: 'demo', name: 'Demo', icon: 'Box', version: '1.0.0',
15
- navigation: [{ section: 'main', label: 'Demo', route: '/demo', icon: 'Box' } as any],
16
- routes: [{ path: '/demo', component: Dummy } as any],
17
- ...overrides,
18
- }
19
- }
20
-
21
- describe('assertPluginManifestContract', () => {
22
- it('passes a conformant page plugin', () => {
23
- expect(() => assertPluginManifestContract(manifest())).not.toThrow()
24
- })
25
-
26
- it('passes a settings-only plugin (nav/routes empty, one settings tab)', () => {
27
- expect(() => assertPluginManifestContract(manifest({
28
- navigation: [], routes: [],
29
- settings: [{ id: 'demo', label: 'Demo', icon: 'Box', component: Dummy } as any],
30
- }))).not.toThrow()
31
- })
32
-
33
- it('rejects a missing id', () => {
34
- expect(() => assertPluginManifestContract(manifest({ id: '' }))).toThrow(PluginContractError)
35
- })
36
-
37
- it('rejects a nav entry pointing at an undeclared route', () => {
38
- expect(() => assertPluginManifestContract(manifest({
39
- navigation: [{ section: 'main', label: 'X', route: '/nowhere', icon: 'Box' } as any],
40
- }))).toThrow(/no matching route/)
41
- })
42
-
43
- it('rejects duplicate route paths', () => {
44
- expect(() => assertPluginManifestContract(manifest({
45
- routes: [{ path: '/demo', component: Dummy } as any, { path: '/demo', component: Dummy } as any],
46
- }))).toThrow(/duplicate route/)
47
- })
48
-
49
- it('rejects a settings tab without a component', () => {
50
- expect(() => assertPluginManifestContract(manifest({
51
- settings: [{ id: 'demo', label: 'Demo' } as any],
52
- }))).toThrow(/component/)
53
- })
54
-
55
- it('rejects a plugin that contributes nothing mountable', () => {
56
- expect(() => assertPluginManifestContract(manifest({ navigation: [], routes: [], settings: [] }))).toThrow(/nothing/)
57
- })
58
- })
@@ -1,135 +0,0 @@
1
- // Capability + contract test for plugin-financial (Plugin Capability Contract).
2
- // Proves the data slice end-to-end against the mock provider — including the
3
- // ⚖️ reconciliation (conciliação) feature: import bank lines → list unreconciled
4
- // → suggest a match → reconcile → assert it leaves the pending set. Rows are
5
- // created manually (mock provider), which is exactly how a capability is proven
6
- // before/without a live backend. Mirrors plugins/plugin-tasks capability.test.ts.
7
- import { describe, it, expect } from 'vitest'
8
- import { createMockFinancialProvider } from './mock'
9
-
10
- describe('plugin-financial · invoice/movement slice (mock provider)', () => {
11
- it('persists an invoice and its installment movements', async () => {
12
- const provider = createMockFinancialProvider()
13
- const invoice = await provider.createInvoice({
14
- direction: 'credit',
15
- invoiceDate: '2026-06-01',
16
- contactName: 'Acme',
17
- items: [{ itemKind: 'other', description: 'Consulting', quantity: 1, unitPrice: 300 }],
18
- installments: [
19
- { direction: 'credit', movementKind: 'bill', amount: 300, dueDate: '2026-06-10', installmentNumber: 1 },
20
- ],
21
- })
22
- expect(invoice.id).toBeTruthy()
23
- expect(invoice.totalAmount).toBe(300)
24
-
25
- const movs = await provider.getMovements({ direction: 'credit' })
26
- expect(movs.data.some((m) => m.invoiceId === invoice.id && m.amount === 300)).toBe(true)
27
- })
28
-
29
- it('filters invoices by item account and cost center assignments', async () => {
30
- const provider = createMockFinancialProvider()
31
- const target = await provider.createInvoice({
32
- direction: 'debit',
33
- invoiceDate: '2026-06-01',
34
- contactName: 'Supplier A',
35
- items: [{
36
- itemKind: 'other',
37
- description: 'Supplies',
38
- quantity: 1,
39
- unitPrice: 120,
40
- accountId: 'coa-expenses',
41
- costCenterId: 'cc-salon',
42
- }],
43
- installments: [
44
- { direction: 'debit', movementKind: 'bill', amount: 120, dueDate: '2026-06-10', installmentNumber: 1 },
45
- ],
46
- })
47
- await provider.createInvoice({
48
- direction: 'debit',
49
- invoiceDate: '2026-06-01',
50
- contactName: 'Supplier B',
51
- items: [{
52
- itemKind: 'other',
53
- description: 'Marketing',
54
- quantity: 1,
55
- unitPrice: 80,
56
- accountId: 'coa-marketing',
57
- costCenterId: 'cc-marketing',
58
- }],
59
- installments: [
60
- { direction: 'debit', movementKind: 'bill', amount: 80, dueDate: '2026-06-10', installmentNumber: 1 },
61
- ],
62
- })
63
-
64
- const byAccount = await provider.getInvoices({ direction: 'debit', accountId: 'coa-expenses' })
65
- expect(byAccount.data.map((invoice) => invoice.id)).toEqual([target.id])
66
-
67
- const byCostCenter = await provider.getInvoices({ direction: 'debit', costCenterId: 'cc-salon' })
68
- expect(byCostCenter.data.map((invoice) => invoice.id)).toEqual([target.id])
69
-
70
- const byBoth = await provider.getInvoices({ direction: 'debit', accountId: 'coa-expenses', costCenterId: 'cc-marketing' })
71
- expect(byBoth.data).toHaveLength(0)
72
- })
73
- })
74
-
75
- describe('plugin-financial · ⚖️ reconciliation capability (mock provider)', () => {
76
- it('imports bank lines idempotently', async () => {
77
- const provider = createMockFinancialProvider()
78
- const lines = [
79
- { externalId: 'tx-1', externalSource: 'plugbank', direction: 'credit' as const, amount: 150, date: '2026-06-10', description: 'PIX recebido' },
80
- { externalId: 'tx-2', externalSource: 'plugbank', direction: 'debit' as const, amount: 80, date: '2026-06-11', description: 'Tarifa' },
81
- ]
82
- const first = await provider.importBankTransactions!({ lines })
83
- expect(first.imported).toBe(2)
84
- expect(first.duplicates).toBe(0)
85
-
86
- // Re-importing the same lines must not duplicate (idempotency by external id).
87
- const second = await provider.importBankTransactions!({ lines })
88
- expect(second.imported).toBe(0)
89
- expect(second.duplicates).toBe(2)
90
-
91
- const pending = await provider.getUnreconciled!()
92
- expect(pending).toHaveLength(2)
93
- expect(pending.every((m) => m.externalSource === 'plugbank' && !m.reconciledAt)).toBe(true)
94
- })
95
-
96
- it('suggests and matches an internal movement, leaving the pending set', async () => {
97
- const provider = createMockFinancialProvider()
98
- // An internal receivable the bank line should reconcile against.
99
- await provider.createInvoice({
100
- direction: 'credit',
101
- invoiceDate: '2026-06-08',
102
- contactName: 'Acme',
103
- items: [{ itemKind: 'other', description: 'Service', quantity: 1, unitPrice: 150 }],
104
- installments: [{ direction: 'credit', movementKind: 'bill', amount: 150, dueDate: '2026-06-10', installmentNumber: 1 }],
105
- })
106
- // The imported bank line (same amount, same direction, near date).
107
- await provider.importBankTransactions!({
108
- lines: [{ externalId: 'tx-1', externalSource: 'plugbank', direction: 'credit', amount: 150, date: '2026-06-10', description: 'PIX Acme' }],
109
- })
110
-
111
- const [bankLine] = await provider.getUnreconciled!()
112
- expect(bankLine).toBeTruthy()
113
-
114
- const candidates = await provider.suggestReconciliation!(bankLine.id)
115
- expect(candidates.length).toBeGreaterThan(0)
116
- expect(candidates[0].movement.amount).toBe(150)
117
- expect(candidates[0].score).toBeGreaterThan(0.5)
118
-
119
- await provider.reconcileMovement!({ bankMovementId: bankLine.id, matchedMovementId: candidates[0].movement.id })
120
-
121
- // The line is gone from the pending set and both sides are marked reconciled.
122
- const stillPending = await provider.getUnreconciled!()
123
- expect(stillPending.some((m) => m.id === bankLine.id)).toBe(false)
124
- })
125
-
126
- it('accepts a bank line standalone when there is no internal counterpart', async () => {
127
- const provider = createMockFinancialProvider()
128
- await provider.importBankTransactions!({
129
- lines: [{ externalId: 'tx-9', externalSource: 'plugbank', direction: 'debit', amount: 42, date: '2026-06-12', description: 'Taxa avulsa' }],
130
- })
131
- const [line] = await provider.getUnreconciled!()
132
- await provider.reconcileMovement!({ bankMovementId: line.id }) // no match → accept standalone
133
- expect(await provider.getUnreconciled!()).toHaveLength(0)
134
- })
135
- })
package/src/data/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export type { FinancialDataProvider } from './types'
2
- export { createMockFinancialProvider } from './mock'
3
- export type { MockFinancialSeed, MockFinancialProviderOptions } from './mock'