@fayz-ai/plugin-banking-br 0.10.32 → 0.10.34
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/connectors/connectorDef.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -11
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectorDef.d.ts","sourceRoot":"","sources":["../../src/connectors/connectorDef.tsx"],"names":[],"mappings":"AAgBA,OAAO,
|
|
1
|
+
{"version":3,"file":"connectorDef.d.ts","sourceRoot":"","sources":["../../src/connectors/connectorDef.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAA2D,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAA;AA4HjH,eAAO,MAAM,oBAAoB,EAAE,mBAsGlC,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { PluginManifest } from '@fayz-ai/core';
|
|
|
2
2
|
export { createOpenBankingProvider } from './data/supabase';
|
|
3
3
|
export { openBankingConnector } from './connectors/connectorDef';
|
|
4
4
|
export { buildBankingDomainEvents, MOVEMENT_IMPORTED_FIELDS } from './lib/banking-events';
|
|
5
|
-
export
|
|
6
|
-
export * from './types';
|
|
5
|
+
export type { BankIntegration, BankLine, BankSyncRun, FetchStatementResult, ImportResult, SaveIntegrationInput, SyncRun, } from './types';
|
|
7
6
|
export declare function createOpenBankingPlugin(): PluginManifest;
|
|
8
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAKnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAKnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAEzF,YAAY,EACV,eAAe,EACf,QAAQ,EACR,WAAW,EACX,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACpB,OAAO,GACR,MAAM,SAAS,CAAA;AAKhB,wBAAgB,uBAAuB,IAAI,cAAc,CA+ExD"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
2
|
import { Download, Loader2 } from 'lucide-react';
|
|
3
|
-
import { DatePicker, Button, toast } from '@fayz-ai/ui';
|
|
4
|
-
import { connectionStatus, createConnectionStore, getSupabaseClientOptional } from '@fayz-ai/core';
|
|
3
|
+
import { DatePicker, Button, Checkbox, toast } from '@fayz-ai/ui';
|
|
4
|
+
import { connectionStatus, formatCurrency, createConnectionStore, getSupabaseClientOptional } from '@fayz-ai/core';
|
|
5
5
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
7
|
// src/connectors/connectorDef.tsx
|
|
@@ -122,13 +122,6 @@ function statementLine(run) {
|
|
|
122
122
|
const imported = `${run.written ?? 0} importadas de ${from} a ${to}`;
|
|
123
123
|
return duplicates > 0 ? `${imported} \xB7 ${duplicates} j\xE1 estavam aqui` : imported;
|
|
124
124
|
}
|
|
125
|
-
function brl(n) {
|
|
126
|
-
try {
|
|
127
|
-
return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(n);
|
|
128
|
-
} catch {
|
|
129
|
-
return `R$ ${n.toFixed(2)}`;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
125
|
function OpenBankingImport({ onRan }) {
|
|
133
126
|
const [integration, setIntegration] = useState(null);
|
|
134
127
|
const [loaded, setLoaded] = useState(false);
|
|
@@ -217,12 +210,12 @@ function OpenBankingImport({ onRan }) {
|
|
|
217
210
|
] }),
|
|
218
211
|
lines.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
219
212
|
/* @__PURE__ */ jsx("div", { className: "rounded-md border divide-y max-h-72 overflow-auto", children: lines.map((l) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-3 px-3 py-2 text-sm cursor-pointer hover:bg-muted/30", children: [
|
|
220
|
-
/* @__PURE__ */ jsx(
|
|
213
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: selected.has(l.externalId), onChange: () => toggle(l.externalId) }),
|
|
221
214
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs w-20 shrink-0", children: l.date }),
|
|
222
215
|
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: l.description }),
|
|
223
216
|
/* @__PURE__ */ jsxs("span", { className: `font-semibold shrink-0 ${l.type === "C" ? "text-success" : "text-destructive"}`, children: [
|
|
224
217
|
l.type === "C" ? "+" : "-",
|
|
225
|
-
|
|
218
|
+
formatCurrency(l.amount, "BRL")
|
|
226
219
|
] })
|
|
227
220
|
] }, l.externalId)) }),
|
|
228
221
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/data/supabase.ts","../src/connectors/connectorDef.tsx","../src/lib/banking-events.ts","../src/migrations/index.ts","../src/index.ts"],"names":["createConnectionStore"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;;AAAA;;AAAA;;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA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","file":"index.js","sourcesContent":["// Open Banking plugin — Supabase data provider.\n//\n// Thin control-plane client that writes no SQL. The connection is a row of\n// public.connections and each import is a row of public.sync_runs\n// (packages/db 029), reached through createConnectionStore — the bespoke\n// bank_integrations / bank_integration_sync_log pair this file used to address\n// was the generic shape under a private name (FAY-1376, migration 003).\n//\n// The heavy lifting (calling the Tecnospeed PlugBank API, normalizing lines,\n// importing into plg_financial_movements with idempotency) runs in the\n// `plugbank-sync` Edge Function, declared on the manifest's `functions[]`.\n//\n// MULTI-ACCOUNT IS THE POINT HERE. The old key was\n// UNIQUE (tenant_id, bank_account_id, provider) and every read filtered by\n// bank_account_id: a clinic with two bank accounts had two connections. That is\n// preserved exactly, by `instance_key` — one store per account, and the\n// account-less default (the panel's case) is instance_key `''`, which is what\n// `bank_account_id IS NULL` used to mean.\n//\n// It never touches a credential. The PlugBank token and the CNPJ that identifies\n// the account will live in the Fayz platform, redeemed per call (FAY-1360 +\n// FAY-1389; today they still sit on the live pool's legacy bank_integrations).\n// connections has no credential column at all, so \"this file never touches\n// one\" is a property of the table rather than of this file.\nimport { createConnectionStore, getSupabaseClientOptional } from '@fayz-ai/core'\nimport type { ConnectionConfig, ConnectionStore, SyncRun } from '@fayz-ai/core'\nimport type {\n BankIntegration, SaveIntegrationInput, BankSyncRun,\n FetchStatementResult, ImportResult, BankLine,\n} from '../types'\n\nconst FUNCTION = 'plugbank-sync'\n\nexport const PLUGBANK_CONNECTOR_ID = 'plugbank'\n\n/** Non-credential keys this connector stores. `environment` selects prod/sandbox. */\nconst SETTINGS_KEYS = ['environment']\n\n/**\n * The store for ONE bank account. `undefined` is the account-less default, not\n * \"any account\": resolving it to `''` keeps the old `bank_account_id IS NULL`\n * filter, so the panel that never picks an account keeps addressing the same\n * single connection it always did.\n */\nfunction storeFor(bankAccountId?: string): ConnectionStore {\n return createConnectionStore(PLUGBANK_CONNECTOR_ID, {\n settingsKeys: SETTINGS_KEYS,\n instanceKey: bankAccountId ?? '',\n })\n}\n\nfunction sb() {\n const supabase = getSupabaseClientOptional() as any\n if (!supabase) throw new Error('Supabase not initialized')\n return supabase\n}\n\nexport function toIntegration(connection: ConnectionConfig): BankIntegration {\n return {\n id: connection.id ?? '',\n provider: connection.connectorId,\n // Back to `undefined` for the default account, so callers that pass this\n // value straight back into getIntegration() address the same row.\n bankAccountId: connection.instanceKey || undefined,\n environment: (connection.settings?.environment as string | undefined) ?? 'production',\n active: connection.active,\n lastSyncAt: connection.lastSyncAt ?? undefined,\n }\n}\n\n/**\n * The audit row, read as the connector contract's SyncRun. The three facts a\n * MANUAL statement import has and a generic run does not — the period the\n * operator picked, and the duplicates a re-import of that period produces by\n * design — live in `stats`, which is the column that exists for exactly this.\n */\nexport function toSyncRun(run: SyncRun): BankSyncRun {\n const stats = run.stats ?? {}\n return {\n ...run,\n id: run.id,\n connectionId: run.connectionId,\n connectorId: run.connectorId,\n direction: run.direction,\n triggerKind: run.triggerKind,\n status: run.status,\n fetched: run.fetched ?? 0,\n written: run.written ?? 0,\n error: run.error ?? undefined,\n startedAt: run.startedAt,\n finishedAt: run.finishedAt ?? undefined,\n periodFrom: (stats.period_from as string | undefined) ?? undefined,\n periodTo: (stats.period_to as string | undefined) ?? undefined,\n duplicates: Number(stats.duplicates ?? 0),\n // sync_runs has no created_at: a run is identified by when it STARTED,\n // which is what the old created_at recorded anyway (the row was inserted at\n // the end, so the two disagreed by the length of the import).\n createdAt: run.startedAt ?? '',\n }\n}\n\nexport interface OpenBankingProvider {\n getIntegration(bankAccountId?: string): Promise<BankIntegration | null>\n saveIntegration(input?: SaveIntegrationInput): Promise<BankIntegration>\n disconnect(bankAccountId?: string): Promise<void>\n fetchStatement(input: { integrationId: string; from: string; to: string }): Promise<FetchStatementResult>\n importTransactions(input: { integrationId: string; bankAccountId?: string; from: string; to: string; lines: BankLine[] }): Promise<ImportResult>\n getSyncRuns(bankAccountId?: string): Promise<BankSyncRun[]>\n}\n\nexport function createOpenBankingProvider(): OpenBankingProvider {\n return {\n async getIntegration(bankAccountId) {\n const connection = await storeFor(bankAccountId).get()\n return connection ? toIntegration(connection) : null\n },\n\n async saveIntegration(input) {\n const saved = await storeFor(input?.bankAccountId).save({\n settings: { environment: input?.environment ?? 'production' },\n active: true,\n })\n return toIntegration(saved)\n },\n\n /**\n * Scoped to ONE account, where the old provider deleted every plugbank row\n * of the tenant. That breadth was invisible while the panel only ever drove\n * the default account; with two accounts it means disconnecting Itaú also\n * silently kills Banco do Brasil. The narrow verb is the correct one.\n */\n async disconnect(bankAccountId) {\n await storeFor(bankAccountId).disconnect()\n },\n\n async fetchStatement(input) {\n const supabase = sb()\n const { data, error } = await supabase.functions.invoke(FUNCTION, {\n body: { action: 'fetch_statement', integrationId: input.integrationId, from: input.from, to: input.to },\n })\n if (error) throw new Error(error.message)\n return data as FetchStatementResult\n },\n\n async importTransactions(input) {\n const supabase = sb()\n const { data, error } = await supabase.functions.invoke(FUNCTION, {\n body: {\n action: 'import_transactions',\n // Still `integrationId` on the wire: it now carries a connections\n // id, and keeping the key means the deployed function needs no\n // coordinated release. The RPC takes the tenant AND the bank account\n // from that row, so neither can be steered from here.\n integrationId: input.integrationId,\n bankAccountId: input.bankAccountId,\n from: input.from,\n to: input.to,\n lines: input.lines,\n },\n })\n if (error) throw new Error(error.message)\n return data as ImportResult\n },\n\n async getSyncRuns(bankAccountId) {\n const runs = await storeFor(bankAccountId).listRuns({ limit: 20 })\n return runs.map(toSyncRun)\n },\n }\n}\n","// Open Banking (Tecnospeed PlugBank) as a unified ConnectorDefinition.\n//\n// Surfaces inside plugin-financial's settings → Integrations tab (the host),\n// rendered by the shared ConnectorsHub. Importing a statement is an ACT, so it\n// arrives through `RunActions` and lands on the Sincronizações tab, above the\n// history the shared surface renders from `sync_runs`. It used to be an\n// `ExtraPanel` on the settings tab with its own hand-written run list under it.\n//\n// A PULL connector: the bank does not know Fayz, so an edge function reaches out\n// to it and needs a credential at fetch time. That credential is registered in\n// the Fayz platform, not here — the connect form asks for no secret, because a\n// secret typed into this app would land in a table any member of the tenant can\n// read. See migration 002 §1.\nimport React, { useEffect, useState } from 'react'\nimport { Download, Loader2 } from 'lucide-react'\nimport { Button, DatePicker, toast } from '@fayz-ai/ui'\nimport { connectionStatus, createConnectionStore, type ConnectorDefinition } from '@fayz-ai/core'\nimport type { SyncRun } from '@fayz-ai/core'\nimport { createOpenBankingProvider, toIntegration, PLUGBANK_CONNECTOR_ID } from '../data/supabase'\nimport type { BankIntegration, BankLine } from '../types'\n\nconst provider = createOpenBankingProvider()\n\n/** dd/mm from the ISO date the RPC stores in `stats`. */\nfunction day(value: unknown): string | undefined {\n return typeof value === 'string' && value.length >= 10 ? value.slice(8, 10) + '/' + value.slice(5, 7) : undefined\n}\n\n/**\n * What one statement import DID, as the sentence every run carries.\n *\n * The period and the duplicate count live in `stats` — three keys the RPC writes\n * and only this connector can read (migrations §sync_runs). The shared\n * history renders `message`, so the interpretation belongs here rather than in a\n * view that would have to learn one connector's jsonb.\n */\nfunction statementLine(run: SyncRun): string | undefined {\n const from = day(run.stats?.period_from)\n const to = day(run.stats?.period_to)\n const duplicates = Number(run.stats?.duplicates ?? 0)\n if (!from || !to) return undefined\n const imported = `${run.written ?? 0} importadas de ${from} a ${to}`\n // A re-import of the same period is mostly duplicates BY DESIGN, so saying so\n // is what stops \"0 importadas\" reading as a failure.\n return duplicates > 0 ? `${imported} · ${duplicates} já estavam aqui` : imported\n}\n\nfunction brl(n: number): string {\n try { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(n) }\n catch { return `R$ ${n.toFixed(2)}` }\n}\n\nfunction OpenBankingImport({ onRan }: { onRan?: () => void }) {\n const [integration, setIntegration] = useState<BankIntegration | null>(null)\n const [loaded, setLoaded] = useState(false)\n const [from, setFrom] = useState(() => { const d = new Date(); d.setDate(d.getDate() - 30); return d.toISOString().slice(0, 10) })\n const [to, setTo] = useState(() => new Date().toISOString().slice(0, 10))\n const [lines, setLines] = useState<BankLine[]>([])\n const [selected, setSelected] = useState<Set<string>>(new Set())\n const [fetching, setFetching] = useState(false)\n const [importing, setImporting] = useState(false)\n\n async function load() {\n setIntegration(await provider.getIntegration().catch(() => null))\n setLoaded(true)\n }\n useEffect(() => { void load() }, [])\n\n // Nothing to import until the connection is live. `active` and not merely \"a\n // row exists\": disconnect is soft now, so a revoked connection still has one.\n if (!loaded || !integration?.active) return null\n\n async function handleFetch() {\n if (!integration) return\n setFetching(true)\n try {\n const res = await provider.fetchStatement({ integrationId: integration.id, from, to })\n setLines(res.lines)\n setSelected(new Set(res.lines.map((l) => l.externalId)))\n } catch (e: any) { toast.error(e?.message ?? 'Erro ao buscar extrato') }\n finally { setFetching(false) }\n }\n\n async function handleImport() {\n if (!integration) return\n const toImport = lines.filter((l) => selected.has(l.externalId))\n if (toImport.length === 0) { toast.error('Selecione ao menos uma linha'); return }\n setImporting(true)\n try {\n const res = await provider.importTransactions({\n integrationId: integration.id, bankAccountId: integration.bankAccountId, from, to, lines: toImport,\n })\n toast.success(`${res.imported} importadas${res.duplicates ? ` · ${res.duplicates} duplicadas` : ''}`)\n setLines([]); setSelected(new Set())\n onRan?.()\n } catch (e: any) { toast.error(e?.message ?? 'Erro ao importar') }\n finally { setImporting(false) }\n }\n\n function toggle(id: string) {\n setSelected((prev) => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next })\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center gap-2\"><Download className=\"h-4 w-4 text-muted-foreground\" /><h4 className=\"text-sm font-semibold\">Importar extrato</h4></div>\n <div className=\"flex flex-wrap items-end gap-3\">\n <div><span className=\"text-xs font-medium text-muted-foreground\">De</span><DatePicker value={from} onChange={setFrom} className=\"mt-1\" /></div>\n <div><span className=\"text-xs font-medium text-muted-foreground\">Até</span><DatePicker value={to} onChange={setTo} className=\"mt-1\" /></div>\n <Button variant=\"outline\" size=\"sm\" onClick={handleFetch} disabled={fetching}>\n {fetching ? <Loader2 className=\"h-3.5 w-3.5 animate-spin\" /> : <Download className=\"h-3.5 w-3.5\" />} Buscar extrato\n </Button>\n </div>\n\n {lines.length > 0 && (\n <>\n <div className=\"rounded-md border divide-y max-h-72 overflow-auto\">\n {lines.map((l) => (\n <label key={l.externalId} className=\"flex items-center gap-3 px-3 py-2 text-sm cursor-pointer hover:bg-muted/30\">\n <input type=\"checkbox\" checked={selected.has(l.externalId)} onChange={() => toggle(l.externalId)} />\n <span className=\"text-muted-foreground text-xs w-20 shrink-0\">{l.date}</span>\n <span className=\"flex-1 truncate\">{l.description}</span>\n <span className={`font-semibold shrink-0 ${l.type === 'C' ? 'text-success' : 'text-destructive'}`}>\n {l.type === 'C' ? '+' : '-'}{brl(l.amount)}\n </span>\n </label>\n ))}\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs text-muted-foreground\">{selected.size} de {lines.length} selecionadas</span>\n <Button size=\"sm\" onClick={handleImport} disabled={importing || selected.size === 0}>\n {importing ? <Loader2 className=\"h-3.5 w-3.5 animate-spin\" /> : <Download className=\"h-3.5 w-3.5\" />} Importar selecionadas\n </Button>\n </div>\n </>\n )}\n\n <p className=\"text-[11px] text-muted-foreground\">\n O token do PlugBank e o CNPJ da conta ficam guardados na plataforma Fayz, não neste\n aplicativo. Quem importa o extrato é o servidor, que pede as credenciais na hora da consulta.\n </p>\n </div>\n )\n}\n\nexport const openBankingConnector: ConnectorDefinition = {\n id: 'plugbank',\n // The product is PlugBank, the company in the directory is TecnoSpeed. Same\n // row, two names — and until this said so, the connector took neither the\n // catalogue's copy nor its mark.\n catalogSlug: 'tecnospeed',\n hostPluginId: 'financial',\n name: 'Open Banking · Tecnospeed PlugBank',\n description: 'Importe o extrato bancário e concilie em Financeiro → Conciliação.',\n icon: 'Landmark',\n authKind: 'api-key',\n // `banking`, not `payments`: reading a statement is not taking a payment, and\n // the platform catalogue files the provider (slug `tecnospeed`) under banking.\n category: 'banking',\n // Inbound because WE reach the bank: PlugBank has never heard of Fayz, which\n // is what makes this the canonical pull connector and why it ships an edge\n // function at all.\n direction: 'inbound',\n // Sync only. The statement has to exist INSIDE Fayz to be listed, filtered and\n // matched against internal movements — reconciliation is not a question the\n // assistant can answer by asking the bank at the moment it is asked.\n planes: ['sync'],\n docsUrl: 'https://tecnospeed.com.br/plugbank/',\n // Write and NOT read, which is unusual enough to be worth saying out loud:\n // `plugbank_import_movements` (migrations/003) only ever INSERTs, with\n // `ON CONFLICT (tenant_id, external_source, external_id) DO NOTHING`. It never\n // SELECTs a movement, so declaring `read` here would be an access the code does\n // not take — and the whole value of this list is that it is not padded.\n //\n // The `emit` is the second half, and it is the one a tenant would not guess:\n // every imported line fires `financial.movement.imported` through the trigger\n // in migrations/002, and that payload carries amount, date, description and\n // bank account to whatever automation is listening. Importing a statement is\n // therefore also publishing it internally.\n permissions: [\n {\n kind: 'data',\n resource: 'movement',\n actions: ['write'],\n reason:\n 'Gravar no seu financeiro as linhas do extrato bancário — valor, data, descrição e conta. Lançamentos já importados não entram de novo.',\n sensitive: true,\n },\n {\n kind: 'event',\n resource: 'financial.movement.imported',\n actions: ['emit'],\n reason:\n 'Avisar o app a cada lançamento importado, para que suas automações de conciliação possam reagir. O aviso leva valor, data e conta.',\n sensitive: true,\n },\n ],\n invokedAt: [\n {\n key: 'financial.statement.import',\n label: 'Você escolhe um período aqui e o extrato do banco é importado',\n plane: 'sync',\n trigger: 'manual',\n touches: ['movement', 'financial.movement.imported'],\n },\n ],\n // No credential field. The token and the CNPJ that identify the account are\n // registered in the Fayz platform; anything typed here would be stored in a\n // table every member of the tenant can read.\n //\n // `subscribesTo` is deliberately absent, and so is `managedAutomations`.\n // Nothing that happens inside Fayz should wake this connector: it pulls a\n // statement, and what makes it pull is the operator picking a period below or\n // the Fayz SCHEDULE worker (FAY-1347) — never a domain fact. A subscription\n // written to fill the field would put this connector on the ingress's\n // delivery list for events it has no reaction to.\n //\n // The reconciliation automations (\"avise quando entrar um valor acima de X\")\n // are real, but they are the TENANT's: they react to\n // `financial.movement.imported` and the tenant may delete them without the\n // connection becoming half-wired. `managedAutomations` is for the wiring the\n // integration IS, and this integration is the import itself.\n // Read from the row the list already batched — one request for N connectors.\n // The account-less default instance (`''`) is the one the panel addresses.\n ...connectionStatus(PLUGBANK_CONNECTOR_ID, (connection) => {\n const found = connection ? toIntegration(connection) : null\n return { connected: !!found?.active, detail: found?.environment, plane: 'sync' }\n }),\n // `testConnection` is deleted rather than stubbed: the credential is not in\n // this app, so there is nothing this side can validate, and ConnectorDetail\n // renders the button only when the verb exists.\n saveConnection: async () => { await provider.saveIntegration() },\n disconnect: () => provider.disconnect(),\n // Every import files a `sync_runs` row, and the shared surface renders it.\n // What is connector-specific is not the VIEW, it is the meaning of `stats`:\n // \"de 01/08 a 12/08 · 3 duplicadas\" is three keys only this connector can\n // read, turned here into the one sentence every run carries. Scoped to the\n // default instance — the account this panel addresses.\n async listRuns(options) {\n try {\n const runs = await createConnectionStore(PLUGBANK_CONNECTOR_ID).listRuns(options)\n return runs.map((run) => ({ ...run, message: run.message ?? statementLine(run) }))\n } catch {\n return []\n }\n },\n RunActions: OpenBankingImport,\n}\n","import type { EventField, PluginEventDefinition } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// The FACT of the bank import.\n//\n// The emitter is not in this file and cannot be: a statement line reaches the\n// ledger from the edge function's insert, from plugbank_import_movements, from\n// a future scheduled pull and occasionally from a hand-run backfill. The one\n// place all four end up is the row, so the emitter is a trigger on\n// plg_financial_movements (migration 002 §4) and this is its contract.\n//\n// This is the fact that turns reconciliation from a screen someone remembers to\n// open into something the tenant can react to — \"avise quando entrar um valor\n// acima de X\" is an automation on this event and nothing else.\n//\n// `provider` is in the payload because the trigger fires for ANY imported\n// movement, not only PlugBank's. A second bank connector writing into the same\n// ledger produces the same fact rather than a second vocabulary for it.\n// ---------------------------------------------------------------------------\n\n/** Payload keys, in the order the trigger builds them. */\nexport const MOVEMENT_IMPORTED_FIELDS: EventField[] = [\n { name: 'movement_id', label: 'ID do lançamento', type: 'id', example: '8f14e45f-…' },\n { name: 'provider', label: 'Origem', type: 'string', example: 'plugbank' },\n { name: 'external_id', label: 'Identificador no banco', type: 'string', example: 'LINE-1' },\n { name: 'direction', label: 'Tipo', type: 'string', example: 'credit' },\n { name: 'amount', label: 'Valor', type: 'money', example: 'R$ 150,00' },\n { name: 'payment_date', label: 'Data', type: 'date', example: '03/08/2026' },\n { name: 'description', label: 'Descrição', type: 'string', example: 'Recebimento PIX' },\n { name: 'bank_account_id', label: 'Conta bancária', type: 'id', example: '3f2a91c4-…' },\n]\n\nconst PAYLOAD_SCHEMA: Record<string, unknown> = {\n type: 'object',\n properties: {\n movement_id: { type: 'string', format: 'uuid', description: 'Movement id in the tenant ledger' },\n provider: {\n type: 'string',\n description:\n \"Which integration imported the line — 'plugbank' today. Reads from plg_financial_movements.external_source, so a second bank connector produces the same fact.\",\n },\n external_id: {\n type: 'string',\n description: \"The bank's own id for the statement line. With `provider` it is the import idempotency key.\",\n },\n direction: { type: 'string', enum: ['credit', 'debit'], description: 'Money in or money out' },\n amount: { type: 'number', description: 'Always positive; `direction` carries the sign' },\n payment_date: { type: 'string', format: 'date', description: 'Date the bank settled the line (YYYY-MM-DD)' },\n description: { type: ['string', 'null'], description: 'Statement narrative, as the bank wrote it' },\n bank_account_id: { type: ['string', 'null'], format: 'uuid', description: 'Account the line landed in, when known' },\n },\n required: ['movement_id', 'provider', 'external_id', 'direction', 'amount', 'payment_date'],\n additionalProperties: false,\n}\n\n/**\n * `audit: true`. Money arriving from outside the app is exactly the subject\n * where \"where did this come from, and when?\" needs an answer that survives a\n * later edit — sharper still when the answer is a connector rather than a\n * person. There is no `updated` counterpart on purpose: a statement line is not\n * revised, it is imported once and then reconciled, and reconciliation is the\n * financial plugin's fact to declare, not this addon's.\n */\nexport function buildBankingDomainEvents(): PluginEventDefinition[] {\n return [\n {\n name: 'financial.movement.imported',\n title: 'Lançamento bancário importado',\n description: 'Uma linha do extrato entrou no financeiro',\n trigger: 'Quando o extrato é importado — pelo painel, pelo conector ou por uma carga manual',\n icon: 'Landmark',\n audit: true,\n fields: MOVEMENT_IMPORTED_FIELDS,\n payloadSchema: PAYLOAD_SCHEMA,\n },\n ]\n}\n","// AUTO-GENERATED from 000_baseline.sql, 001_the_import_rpc_names_real_tables.sql — regenerate with scripts/embed-migrations.mjs\n// SQL files are the source of truth; this inline copy lets the manifest declare\n// migrations as data. Do not edit by hand — run the embed script instead.\n\nexport const MIGRATION_000_BASELINE = `-- ============================================================================\n-- plugins/plugin-banking-br/src/migrations/000_baseline.sql — what installing this provisions, as one file.\n--\n-- GENERATED by scripts/emit-unit-baselines.mjs (#338). Do not edit by hand:\n-- change the schema, regenerate, and let scripts/check-chain-equivalence.mjs\n-- prove the result still matches.\n--\n-- This replaced 2 migration files. They are kept, unapplied, in\n-- migrations.archive/ — a year of decisions is worth reading even when it is no\n-- longer worth replaying.\n--\n-- Object order is fixed rather than dependency-sorted: schemas, types,\n-- sequences, tables, defaults, functions, constraints, views, indexes, foreign\n-- keys, triggers, row security, policies, grants. Dependencies BETWEEN units\n-- are carried by the order packages/db/chain.json declares.\n-- ============================================================================\n\n-- A LANGUAGE sql function binds its body at CREATE time, and 570 functions\n-- sorted by name are not sorted by who calls whom. This is what lets them load\n-- in any order; every body is still compiled on first call, so a genuinely\n-- broken reference surfaces then rather than never. Session-scoped.\nSET check_function_bodies = false;\n\n\n\n-- ── default privileges: reset ───────────────────────────────────────────\n-- Cleared BEFORE anything is created, so every object below gets the ACL the\n-- dump describes rather than the image's defaults on top of it. Restored at\n-- the end of this file.\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\n\n-- ── function ────────────────────────────────────────────────────────────\n\nCREATE FUNCTION public.plg_bank_emit_movement_imported() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO 'public'\n AS $$\nDECLARE\n v_payload jsonb;\nBEGIN\n IF NEW.external_source IS NULL OR NEW.external_id IS NULL THEN\n RETURN NULL;\n END IF;\n\n -- The contract every consumer reads. Not the row: a whole-row payload would\n -- put the ledger's internal bookkeeping inside every webhook by default.\n v_payload := jsonb_build_object(\n 'movement_id', NEW.id::text,\n 'provider', NEW.external_source,\n 'external_id', NEW.external_id,\n 'direction', NEW.direction,\n 'amount', NEW.amount,\n 'payment_date', NEW.payment_date,\n 'description', NEW.notes,\n 'bank_account_id', NEW.bank_account_id);\n\n BEGIN\n PERFORM public.plg_emit_event(\n 'financial.movement.imported', v_payload, 'financial_movement',\n NEW.id::text, NEW.tenant_id, NULL);\n EXCEPTION WHEN OTHERS THEN\n -- Observability must never be able to reject the write that caused it.\n RAISE WARNING '[plg_bank_emit_movement_imported] % on %: %', TG_OP, TG_TABLE_NAME, SQLERRM;\n END;\n\n RETURN NULL;\nEND;\n$$;\n\nCREATE FUNCTION public.plugbank_import_movements(p_payload jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO 'public'\n AS $$\nDECLARE\n v_tenants uuid[];\n v_tenant uuid;\n v_connection uuid;\n v_instance text;\n v_account uuid;\n v_connector text;\n v_trigger text;\n v_from date;\n v_to date;\n v_started timestamptz := now();\n v_line jsonb;\n v_external text;\n v_amount numeric(14,2);\n v_date date;\n v_fetched int := 0;\n v_imported int := 0;\n v_duplicates int := 0;\n v_inserted uuid;\n v_run uuid;\nBEGIN\n v_connection := nullif(btrim(p_payload->>'integration_id'), '')::uuid;\n IF v_connection IS NULL THEN\n RAISE EXCEPTION 'plugbank_import_movements: integration_id is required'\n USING ERRCODE = 'invalid_parameter_value';\n END IF;\n\n SELECT c.tenant_id, c.instance_key, c.connector_id\n INTO v_tenant, v_instance, v_connector\n FROM public.plg_connections c\n WHERE c.id = v_connection AND c.active;\n\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'plugbank_import_movements: no active connection %', v_connection\n USING ERRCODE = 'no_data_found';\n END IF;\n\n -- The account this connection is for. \\`instance_key\\` is text because it also\n -- carries things that are not uuids for other connectors (a Bling company, a\n -- gateway instance); here it is the bank account, and '' is the account-less\n -- default that \\`bank_account_id IS NULL\\` used to mean.\n v_account := nullif(btrim(v_instance), '')::uuid;\n\n -- SECURITY DEFINER bypasses RLS, so the membership check the policy would\n -- have made has to happen here.\n SELECT array_agg(t) INTO v_tenants FROM public.user_tenant_ids() AS t;\n IF auth.uid() IS NOT NULL AND NOT (v_tenant = ANY (COALESCE(v_tenants, '{}'::uuid[]))) THEN\n RAISE EXCEPTION 'plugbank_import_movements: caller is not a member of tenant %', v_tenant\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n -- Refusing beats ignoring: a caller that believes it is importing into\n -- another tenant must be told, not silently redirected into this one.\n IF nullif(btrim(p_payload->>'tenant_id'), '') IS NOT NULL\n AND (p_payload->>'tenant_id')::uuid IS DISTINCT FROM v_tenant THEN\n RAISE EXCEPTION 'plugbank_import_movements: payload names another tenant; the tenant comes from the connection'\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n -- Same rule as the tenant, one level down: the account is the connection's,\n -- and a payload naming a different one is a caller importing into the wrong\n -- ledger account. 002 let the payload override it silently — with one\n -- connection per account that override is how two accounts get mixed.\n IF nullif(btrim(p_payload->>'bank_account_id'), '') IS NOT NULL\n AND (p_payload->>'bank_account_id')::uuid IS DISTINCT FROM v_account THEN\n RAISE EXCEPTION 'plugbank_import_movements: payload names another bank account; the account comes from the connection'\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n v_from := nullif(btrim(p_payload->>'period_from'), '')::date;\n v_to := nullif(btrim(p_payload->>'period_to'), '')::date;\n v_trigger := lower(coalesce(nullif(btrim(p_payload->>'trigger'), ''), 'manual'));\n IF v_trigger NOT IN ('on-write', 'scheduled', 'manual', 'webhook') THEN\n RAISE EXCEPTION 'plugbank_import_movements: unknown trigger %', v_trigger\n USING ERRCODE = 'invalid_parameter_value';\n END IF;\n\n FOR v_line IN SELECT * FROM jsonb_array_elements(coalesce(p_payload->'lines', '[]'::jsonb))\n LOOP\n v_fetched := v_fetched + 1;\n v_external := nullif(btrim(v_line->>'external_id'), '');\n v_date := nullif(btrim(v_line->>'date'), '')::date;\n v_amount := abs(coalesce((v_line->>'amount')::numeric, 0));\n\n -- A line with no provider id cannot be recognised on re-delivery, so it\n -- would import again on every run. Skipping it is the lesser wrong.\n IF v_external IS NULL OR v_date IS NULL OR v_amount = 0 THEN\n CONTINUE;\n END IF;\n\n INSERT INTO public.plg_financial_movements (\n tenant_id, direction, movement_kind, amount, paid_amount, status,\n due_date, payment_date, bank_account_id, notes, external_id, external_source)\n VALUES (\n v_tenant,\n CASE WHEN upper(coalesce(v_line->>'type', 'C')) LIKE 'C%' THEN 'credit' ELSE 'debit' END,\n 'payment', v_amount, v_amount, 'paid',\n v_date, v_date, v_account,\n nullif(btrim(v_line->>'description'), ''),\n v_external, coalesce(v_connector, 'plugbank'))\n ON CONFLICT (tenant_id, external_source, external_id)\n WHERE external_id IS NOT NULL AND external_source IS NOT NULL\n DO NOTHING\n RETURNING id INTO v_inserted;\n\n IF v_inserted IS NULL THEN\n v_duplicates := v_duplicates + 1;\n ELSE\n v_imported := v_imported + 1;\n END IF;\n v_inserted := NULL;\n END LOOP;\n\n UPDATE public.plg_connections\n SET last_sync_at = now(), status = 'connected', status_detail = NULL, updated_at = now()\n WHERE id = v_connection;\n\n -- \\`stats\\` is where the three facts a manual statement import has and a generic\n -- run does not go: the operator picked a period, a re-import of that period is\n -- mostly duplicates by design, and someone pressed the button. The panel reads\n -- all three back from here.\n INSERT INTO public.plg_sync_runs (\n tenant_id, connection_id, connector_id, direction, trigger_kind, status,\n stream, fetched, written, stats, started_at, finished_at)\n VALUES (\n v_tenant, v_connection, coalesce(v_connector, 'plugbank'), 'inbound', v_trigger, 'success',\n 'statement', v_fetched, v_imported,\n jsonb_strip_nulls(jsonb_build_object(\n 'duplicates', v_duplicates,\n 'period_from', v_from,\n 'period_to', v_to,\n 'triggered_by_user_id', auth.uid())),\n v_started, now())\n RETURNING id INTO v_run;\n\n RETURN jsonb_build_object(\n 'ok', true,\n 'sync_run_id', v_run,\n 'fetched', v_fetched,\n 'imported', v_imported,\n 'duplicates', v_duplicates);\nEND;\n$$;\n\n\n-- ── trigger ─────────────────────────────────────────────────────────────\n\nCREATE TRIGGER plg_financial_movements_emit_imported AFTER INSERT ON public.plg_financial_movements FOR EACH ROW EXECUTE FUNCTION public.plg_bank_emit_movement_imported();\n\n\n-- ── acl ─────────────────────────────────────────────────────────────────\n\nREVOKE ALL ON FUNCTION public.plg_bank_emit_movement_imported() FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plg_bank_emit_movement_imported() TO authenticated;\nGRANT ALL ON FUNCTION public.plg_bank_emit_movement_imported() TO service_role;\n\nREVOKE ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) TO authenticated;\nGRANT ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) TO service_role;\n\n\n-- ── comment ─────────────────────────────────────────────────────────────\n\nCOMMENT ON FUNCTION public.plugbank_import_movements(p_payload jsonb) IS 'Import bank-statement lines into the financial ledger and file the run in plg_sync_runs. The tenant AND the bank account come from the plg_connections row named by integration_id, never from the payload.';\n\n\n-- ── default privileges: restore ─────────────────────────────────────────\n-- Put back, so the NEXT thing installed inherits what the chain had.\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$--\n-- PostgreSQL database dump complete\n--$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$--\n-- PostgreSQL database dump complete\n--$stmt$;\nEND $dp$;\n`\n\nexport const MIGRATION_001_THE_IMPORT_RPC_NAMES_REAL_TABLES = `-- ==========================================================================\n-- 001_the_import_rpc_names_real_tables — plugbank_import_movements\n-- stops reaching through 141's bridge (#352).\n--\n-- 107 renamed the connector spine out of the \\`plg_\\` prefix — the prefix means\n-- \"a plugin owns this\", and the connector framework's tables are the BASE's.\n-- 141 left \\`plg_connections\\` and \\`plg_sync_runs\\` answering as views so callers\n-- could move without a synchronised deploy. This function never moved, and it\n-- is not alone: \\`plg_connections\\` had 13 callers and \\`connections\\` had none.\n-- The bridge became the road.\n--\n-- The body is 000_baseline's, unchanged except for the three names. It is not\n-- an edit of that file — an applied migration is immutable and the ledger\n-- treats drift as a hard stop — it is a CREATE OR REPLACE that lands after it.\n--\n-- Why this plugin has to carry its own: the function is banking-br's, and the\n-- views it stops using are the spine's. The spine runs FIRST, so a spine file\n-- cannot drop a view that a plugin applied later still reads. Each package\n-- moves its own callers; the aliases go once nobody is left.\n--\n-- Replay-safe: CREATE OR REPLACE, same signature, same grants (which 000 set\n-- and this does not touch).\n-- ==========================================================================\n\nCREATE OR REPLACE FUNCTION public.plugbank_import_movements(p_payload jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO 'public'\n AS $$\nDECLARE\n v_tenants uuid[];\n v_tenant uuid;\n v_connection uuid;\n v_instance text;\n v_account uuid;\n v_connector text;\n v_trigger text;\n v_from date;\n v_to date;\n v_started timestamptz := now();\n v_line jsonb;\n v_external text;\n v_amount numeric(14,2);\n v_date date;\n v_fetched int := 0;\n v_imported int := 0;\n v_duplicates int := 0;\n v_inserted uuid;\n v_run uuid;\nBEGIN\n v_connection := nullif(btrim(p_payload->>'integration_id'), '')::uuid;\n IF v_connection IS NULL THEN\n RAISE EXCEPTION 'plugbank_import_movements: integration_id is required'\n USING ERRCODE = 'invalid_parameter_value';\n END IF;\n\n SELECT c.tenant_id, c.instance_key, c.connector_id\n INTO v_tenant, v_instance, v_connector\n FROM public.connections c\n WHERE c.id = v_connection AND c.active;\n\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'plugbank_import_movements: no active connection %', v_connection\n USING ERRCODE = 'no_data_found';\n END IF;\n\n -- The account this connection is for. \\`instance_key\\` is text because it also\n -- carries things that are not uuids for other connectors (a Bling company, a\n -- gateway instance); here it is the bank account, and '' is the account-less\n -- default that \\`bank_account_id IS NULL\\` used to mean.\n v_account := nullif(btrim(v_instance), '')::uuid;\n\n -- SECURITY DEFINER bypasses RLS, so the membership check the policy would\n -- have made has to happen here.\n SELECT array_agg(t) INTO v_tenants FROM public.user_tenant_ids() AS t;\n IF auth.uid() IS NOT NULL AND NOT (v_tenant = ANY (COALESCE(v_tenants, '{}'::uuid[]))) THEN\n RAISE EXCEPTION 'plugbank_import_movements: caller is not a member of tenant %', v_tenant\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n -- Refusing beats ignoring: a caller that believes it is importing into\n -- another tenant must be told, not silently redirected into this one.\n IF nullif(btrim(p_payload->>'tenant_id'), '') IS NOT NULL\n AND (p_payload->>'tenant_id')::uuid IS DISTINCT FROM v_tenant THEN\n RAISE EXCEPTION 'plugbank_import_movements: payload names another tenant; the tenant comes from the connection'\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n -- Same rule as the tenant, one level down: the account is the connection's,\n -- and a payload naming a different one is a caller importing into the wrong\n -- ledger account. 002 let the payload override it silently — with one\n -- connection per account that override is how two accounts get mixed.\n IF nullif(btrim(p_payload->>'bank_account_id'), '') IS NOT NULL\n AND (p_payload->>'bank_account_id')::uuid IS DISTINCT FROM v_account THEN\n RAISE EXCEPTION 'plugbank_import_movements: payload names another bank account; the account comes from the connection'\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n v_from := nullif(btrim(p_payload->>'period_from'), '')::date;\n v_to := nullif(btrim(p_payload->>'period_to'), '')::date;\n v_trigger := lower(coalesce(nullif(btrim(p_payload->>'trigger'), ''), 'manual'));\n IF v_trigger NOT IN ('on-write', 'scheduled', 'manual', 'webhook') THEN\n RAISE EXCEPTION 'plugbank_import_movements: unknown trigger %', v_trigger\n USING ERRCODE = 'invalid_parameter_value';\n END IF;\n\n FOR v_line IN SELECT * FROM jsonb_array_elements(coalesce(p_payload->'lines', '[]'::jsonb))\n LOOP\n v_fetched := v_fetched + 1;\n v_external := nullif(btrim(v_line->>'external_id'), '');\n v_date := nullif(btrim(v_line->>'date'), '')::date;\n v_amount := abs(coalesce((v_line->>'amount')::numeric, 0));\n\n -- A line with no provider id cannot be recognised on re-delivery, so it\n -- would import again on every run. Skipping it is the lesser wrong.\n IF v_external IS NULL OR v_date IS NULL OR v_amount = 0 THEN\n CONTINUE;\n END IF;\n\n INSERT INTO public.plg_financial_movements (\n tenant_id, direction, movement_kind, amount, paid_amount, status,\n due_date, payment_date, bank_account_id, notes, external_id, external_source)\n VALUES (\n v_tenant,\n CASE WHEN upper(coalesce(v_line->>'type', 'C')) LIKE 'C%' THEN 'credit' ELSE 'debit' END,\n 'payment', v_amount, v_amount, 'paid',\n v_date, v_date, v_account,\n nullif(btrim(v_line->>'description'), ''),\n v_external, coalesce(v_connector, 'plugbank'))\n ON CONFLICT (tenant_id, external_source, external_id)\n WHERE external_id IS NOT NULL AND external_source IS NOT NULL\n DO NOTHING\n RETURNING id INTO v_inserted;\n\n IF v_inserted IS NULL THEN\n v_duplicates := v_duplicates + 1;\n ELSE\n v_imported := v_imported + 1;\n END IF;\n v_inserted := NULL;\n END LOOP;\n\n UPDATE public.connections\n SET last_sync_at = now(), status = 'connected', status_detail = NULL, updated_at = now()\n WHERE id = v_connection;\n\n -- \\`stats\\` is where the three facts a manual statement import has and a generic\n -- run does not go: the operator picked a period, a re-import of that period is\n -- mostly duplicates by design, and someone pressed the button. The panel reads\n -- all three back from here.\n INSERT INTO public.sync_runs (\n tenant_id, connection_id, connector_id, direction, trigger_kind, status,\n stream, fetched, written, stats, started_at, finished_at)\n VALUES (\n v_tenant, v_connection, coalesce(v_connector, 'plugbank'), 'inbound', v_trigger, 'success',\n 'statement', v_fetched, v_imported,\n jsonb_strip_nulls(jsonb_build_object(\n 'duplicates', v_duplicates,\n 'period_from', v_from,\n 'period_to', v_to,\n 'triggered_by_user_id', auth.uid())),\n v_started, now())\n RETURNING id INTO v_run;\n\n RETURN jsonb_build_object(\n 'ok', true,\n 'sync_run_id', v_run,\n 'fetched', v_fetched,\n 'imported', v_imported,\n 'duplicates', v_duplicates);\nEND;\n$$;\nCOMMENT ON FUNCTION public.plugbank_import_movements(p_payload jsonb) IS\n 'Import bank-statement lines into the financial ledger and file the run in public.sync_runs. The tenant AND the bank account come from the public.connections row named by integration_id, never from the payload.';\n`\n\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = [\n { id: \"000_baseline\", sql: MIGRATION_000_BASELINE },\n { id: \"001_the_import_rpc_names_real_tables\", sql: MIGRATION_001_THE_IMPORT_RPC_NAMES_REAL_TABLES },\n]\n","// Open Banking plugin (Tecnospeed PlugBank) — public surface + manifest.\n//\n// The canonical PULL connector: the provider has never heard of Fayz, so an edge\n// function reaches out to it. Settings-only (no left-nav page) — it contributes\n// its connector into plugin-financial's settings → Integrations, and imported\n// lines are matched in Financeiro → Conciliação (enable the financial\n// `reconciliation` module).\nimport type { PluginManifest } from '@fayz-ai/core'\nimport { openBankingConnector } from './connectors/connectorDef'\nimport { buildBankingDomainEvents } from './lib/banking-events'\nimport { MIGRATIONS } from './migrations'\n\nexport { createOpenBankingProvider } from './data/supabase'\nexport { openBankingConnector } from './connectors/connectorDef'\nexport { buildBankingDomainEvents, MOVEMENT_IMPORTED_FIELDS } from './lib/banking-events'\nexport * from './schema'\nexport * from './types'\n\n// Addon plugin: contributes the PlugBank connector into its host\n// (plugin-financial). It appears in Financial settings → Integrations — no\n// top-level settings tab of its own.\nexport function createOpenBankingPlugin(): PluginManifest {\n return {\n id: 'openbanking',\n name: 'Open Banking',\n icon: 'Landmark',\n version: '0.9.1',\n apiVersion: 1,\n scope: 'addon',\n defaultEnabled: true,\n dependencies: ['financial'],\n navigation: [],\n routes: [],\n connectors: [openBankingConnector],\n // The fact the import produces. Declared HERE and not on plugin-financial\n // because the trigger that emits it ships in this plugin's 002: a host\n // advertising an event that only exists when an addon is installed is the\n // same broken promise as an automation the ingress never delivers.\n events: buildBankingDomainEvents(),\n // `emitsFrom` is deliberately absent. It generates\n // `registry.<entity>.<action>` keys for every row change, and \"imported\" is\n // not a row action — it is an INSERT carrying an external_source, which only\n // a purpose-built trigger can tell apart from a sale typed into the till.\n functions: [\n {\n slug: 'plugbank-sync',\n // Invoked from the browser by an authenticated operator picking a\n // period, so Supabase must check the caller's JWT. It is not reached\n // through the Fayz ingress — nothing in Fayz wakes a pull connector —\n // which is what lets verifyJwt stay true.\n ingress: 'none',\n verifyJwt: true,\n // NAMES only. PLUGBANK_BASE_URL selects production vs sandbox; the\n // per-tenant token and account CNPJ are NOT here, because a Supabase\n // function secret is per-POOL and a pool serves N tenants with N\n // different bank accounts. Those come from the Fayz platform per call.\n secrets: ['PLUGBANK_BASE_URL', 'SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY'],\n // What this connector's credential may touch. Direct table access is\n // not on the list on purpose (2026-08-11 ADR): the import goes through\n // the RPC, which takes the tenant from the integration row instead of\n // from the request body.\n grants: ['plugbank_import_movements'],\n },\n ],\n declaredRpcs: [\n {\n name: 'plugbank_import_movements',\n kind: 'write',\n description:\n 'Import bank-statement lines into the financial ledger. Idempotent on (tenant, provider, external id); the tenant comes from the integration row, never from the payload.',\n audits: false,\n },\n ],\n // This plugin creates no table. 003 retired the two it had; what is left is\n // the RPC that is the import's one door, and the trigger that turns an\n // imported movement into a fact. The connection and its run history are rows\n // of connections / sync_runs, owned by packages/db.\n // One baseline per unit since #338 — read from the generated barrel\n // rather than listed here, so regenerating the SQL never needs a\n // manifest edit to match.\n migrations: MIGRATIONS.map((entry) => ({\n id: `openbanking-${entry.id}`,\n version: '1.0.0',\n sql: entry.sql,\n description:\n 'The whole schema this plugin provisions, as one file: every table, view, function, policy and grant, plus the reference rows it seeds. Replaced the per-change chain in the 1.0.0 squash (#338); the files it collapsed are kept, unapplied, in src/migrations.archive/.',\n })),\n diagnostics: [\n {\n id: 'openbanking-ledger-ready',\n description:\n 'The import writes into the financial ledger and needs its reconciliation columns (plugin-financial 007), the generic connection tables (packages/db 029) and the row-level event emitter.',\n requires: {\n tables: ['connections', 'sync_runs', 'plg_financial_movements'],\n rpcs: ['plugbank_import_movements', 'plg_emit_event'],\n },\n level: 'error',\n },\n ],\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/data/supabase.ts","../src/connectors/connectorDef.tsx","../src/lib/banking-events.ts","../src/migrations/index.ts","../src/index.ts"],"names":["createConnectionStore"],"mappings":"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AAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;;AAAA;;AAAA;;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AA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","file":"index.js","sourcesContent":["// Open Banking plugin — Supabase data provider.\n//\n// Thin control-plane client that writes no SQL. The connection is a row of\n// public.connections and each import is a row of public.sync_runs\n// (packages/db 029), reached through createConnectionStore — the bespoke\n// bank_integrations / bank_integration_sync_log pair this file used to address\n// was the generic shape under a private name (FAY-1376, migration 003).\n//\n// The heavy lifting (calling the Tecnospeed PlugBank API, normalizing lines,\n// importing into plg_financial_movements with idempotency) runs in the\n// `plugbank-sync` Edge Function, declared on the manifest's `functions[]`.\n//\n// MULTI-ACCOUNT IS THE POINT HERE. The old key was\n// UNIQUE (tenant_id, bank_account_id, provider) and every read filtered by\n// bank_account_id: a clinic with two bank accounts had two connections. That is\n// preserved exactly, by `instance_key` — one store per account, and the\n// account-less default (the panel's case) is instance_key `''`, which is what\n// `bank_account_id IS NULL` used to mean.\n//\n// It never touches a credential. The PlugBank token and the CNPJ that identifies\n// the account will live in the Fayz platform, redeemed per call (FAY-1360 +\n// FAY-1389; today they still sit on the live pool's legacy bank_integrations).\n// connections has no credential column at all, so \"this file never touches\n// one\" is a property of the table rather than of this file.\nimport { createConnectionStore, getSupabaseClientOptional } from '@fayz-ai/core'\nimport type { ConnectionConfig, ConnectionStore, SyncRun } from '@fayz-ai/core'\nimport type {\n BankIntegration, SaveIntegrationInput, BankSyncRun,\n FetchStatementResult, ImportResult, BankLine,\n} from '../types'\n\nconst FUNCTION = 'plugbank-sync'\n\nexport const PLUGBANK_CONNECTOR_ID = 'plugbank'\n\n/** Non-credential keys this connector stores. `environment` selects prod/sandbox. */\nconst SETTINGS_KEYS = ['environment']\n\n/**\n * The store for ONE bank account. `undefined` is the account-less default, not\n * \"any account\": resolving it to `''` keeps the old `bank_account_id IS NULL`\n * filter, so the panel that never picks an account keeps addressing the same\n * single connection it always did.\n */\nfunction storeFor(bankAccountId?: string): ConnectionStore {\n return createConnectionStore(PLUGBANK_CONNECTOR_ID, {\n settingsKeys: SETTINGS_KEYS,\n instanceKey: bankAccountId ?? '',\n })\n}\n\nfunction sb() {\n const supabase = getSupabaseClientOptional() as any\n if (!supabase) throw new Error('Supabase not initialized')\n return supabase\n}\n\nexport function toIntegration(connection: ConnectionConfig): BankIntegration {\n return {\n id: connection.id ?? '',\n provider: connection.connectorId,\n // Back to `undefined` for the default account, so callers that pass this\n // value straight back into getIntegration() address the same row.\n bankAccountId: connection.instanceKey || undefined,\n environment: (connection.settings?.environment as string | undefined) ?? 'production',\n active: connection.active,\n lastSyncAt: connection.lastSyncAt ?? undefined,\n }\n}\n\n/**\n * The audit row, read as the connector contract's SyncRun. The three facts a\n * MANUAL statement import has and a generic run does not — the period the\n * operator picked, and the duplicates a re-import of that period produces by\n * design — live in `stats`, which is the column that exists for exactly this.\n */\nexport function toSyncRun(run: SyncRun): BankSyncRun {\n const stats = run.stats ?? {}\n return {\n ...run,\n id: run.id,\n connectionId: run.connectionId,\n connectorId: run.connectorId,\n direction: run.direction,\n triggerKind: run.triggerKind,\n status: run.status,\n fetched: run.fetched ?? 0,\n written: run.written ?? 0,\n error: run.error ?? undefined,\n startedAt: run.startedAt,\n finishedAt: run.finishedAt ?? undefined,\n periodFrom: (stats.period_from as string | undefined) ?? undefined,\n periodTo: (stats.period_to as string | undefined) ?? undefined,\n duplicates: Number(stats.duplicates ?? 0),\n // sync_runs has no created_at: a run is identified by when it STARTED,\n // which is what the old created_at recorded anyway (the row was inserted at\n // the end, so the two disagreed by the length of the import).\n createdAt: run.startedAt ?? '',\n }\n}\n\nexport interface OpenBankingProvider {\n getIntegration(bankAccountId?: string): Promise<BankIntegration | null>\n saveIntegration(input?: SaveIntegrationInput): Promise<BankIntegration>\n disconnect(bankAccountId?: string): Promise<void>\n fetchStatement(input: { integrationId: string; from: string; to: string }): Promise<FetchStatementResult>\n importTransactions(input: { integrationId: string; bankAccountId?: string; from: string; to: string; lines: BankLine[] }): Promise<ImportResult>\n getSyncRuns(bankAccountId?: string): Promise<BankSyncRun[]>\n}\n\nexport function createOpenBankingProvider(): OpenBankingProvider {\n return {\n async getIntegration(bankAccountId) {\n const connection = await storeFor(bankAccountId).get()\n return connection ? toIntegration(connection) : null\n },\n\n async saveIntegration(input) {\n const saved = await storeFor(input?.bankAccountId).save({\n settings: { environment: input?.environment ?? 'production' },\n active: true,\n })\n return toIntegration(saved)\n },\n\n /**\n * Scoped to ONE account, where the old provider deleted every plugbank row\n * of the tenant. That breadth was invisible while the panel only ever drove\n * the default account; with two accounts it means disconnecting Itaú also\n * silently kills Banco do Brasil. The narrow verb is the correct one.\n */\n async disconnect(bankAccountId) {\n await storeFor(bankAccountId).disconnect()\n },\n\n async fetchStatement(input) {\n const supabase = sb()\n const { data, error } = await supabase.functions.invoke(FUNCTION, {\n body: { action: 'fetch_statement', integrationId: input.integrationId, from: input.from, to: input.to },\n })\n if (error) throw new Error(error.message)\n return data as FetchStatementResult\n },\n\n async importTransactions(input) {\n const supabase = sb()\n const { data, error } = await supabase.functions.invoke(FUNCTION, {\n body: {\n action: 'import_transactions',\n // Still `integrationId` on the wire: it now carries a connections\n // id, and keeping the key means the deployed function needs no\n // coordinated release. The RPC takes the tenant AND the bank account\n // from that row, so neither can be steered from here.\n integrationId: input.integrationId,\n bankAccountId: input.bankAccountId,\n from: input.from,\n to: input.to,\n lines: input.lines,\n },\n })\n if (error) throw new Error(error.message)\n return data as ImportResult\n },\n\n async getSyncRuns(bankAccountId) {\n const runs = await storeFor(bankAccountId).listRuns({ limit: 20 })\n return runs.map(toSyncRun)\n },\n }\n}\n","// Open Banking (Tecnospeed PlugBank) as a unified ConnectorDefinition.\n//\n// Surfaces inside plugin-financial's settings → Integrations tab (the host),\n// rendered by the shared ConnectorsHub. Importing a statement is an ACT, so it\n// arrives through `RunActions` and lands on the Sincronizações tab, above the\n// history the shared surface renders from `sync_runs`. It used to be an\n// `ExtraPanel` on the settings tab with its own hand-written run list under it.\n//\n// A PULL connector: the bank does not know Fayz, so an edge function reaches out\n// to it and needs a credential at fetch time. That credential is registered in\n// the Fayz platform, not here — the connect form asks for no secret, because a\n// secret typed into this app would land in a table any member of the tenant can\n// read. See migration 002 §1.\nimport React, { useEffect, useState } from 'react'\nimport { Download, Loader2 } from 'lucide-react'\nimport { Button, Checkbox, DatePicker, toast } from '@fayz-ai/ui'\nimport { connectionStatus, createConnectionStore, formatCurrency, type ConnectorDefinition } from '@fayz-ai/core'\nimport type { SyncRun } from '@fayz-ai/core'\nimport { createOpenBankingProvider, toIntegration, PLUGBANK_CONNECTOR_ID } from '../data/supabase'\nimport type { BankIntegration, BankLine } from '../types'\n\nconst provider = createOpenBankingProvider()\n\n/** dd/mm from the ISO date the RPC stores in `stats`. */\nfunction day(value: unknown): string | undefined {\n return typeof value === 'string' && value.length >= 10 ? value.slice(8, 10) + '/' + value.slice(5, 7) : undefined\n}\n\n/**\n * What one statement import DID, as the sentence every run carries.\n *\n * The period and the duplicate count live in `stats` — three keys the RPC writes\n * and only this connector can read (migrations §sync_runs). The shared\n * history renders `message`, so the interpretation belongs here rather than in a\n * view that would have to learn one connector's jsonb.\n */\nfunction statementLine(run: SyncRun): string | undefined {\n const from = day(run.stats?.period_from)\n const to = day(run.stats?.period_to)\n const duplicates = Number(run.stats?.duplicates ?? 0)\n if (!from || !to) return undefined\n const imported = `${run.written ?? 0} importadas de ${from} a ${to}`\n // A re-import of the same period is mostly duplicates BY DESIGN, so saying so\n // is what stops \"0 importadas\" reading as a failure.\n return duplicates > 0 ? `${imported} · ${duplicates} já estavam aqui` : imported\n}\n\nfunction OpenBankingImport({ onRan }: { onRan?: () => void }) {\n const [integration, setIntegration] = useState<BankIntegration | null>(null)\n const [loaded, setLoaded] = useState(false)\n const [from, setFrom] = useState(() => { const d = new Date(); d.setDate(d.getDate() - 30); return d.toISOString().slice(0, 10) })\n const [to, setTo] = useState(() => new Date().toISOString().slice(0, 10))\n const [lines, setLines] = useState<BankLine[]>([])\n const [selected, setSelected] = useState<Set<string>>(new Set())\n const [fetching, setFetching] = useState(false)\n const [importing, setImporting] = useState(false)\n\n async function load() {\n setIntegration(await provider.getIntegration().catch(() => null))\n setLoaded(true)\n }\n useEffect(() => { void load() }, [])\n\n // Nothing to import until the connection is live. `active` and not merely \"a\n // row exists\": disconnect is soft now, so a revoked connection still has one.\n if (!loaded || !integration?.active) return null\n\n async function handleFetch() {\n if (!integration) return\n setFetching(true)\n try {\n const res = await provider.fetchStatement({ integrationId: integration.id, from, to })\n setLines(res.lines)\n setSelected(new Set(res.lines.map((l) => l.externalId)))\n } catch (e: any) { toast.error(e?.message ?? 'Erro ao buscar extrato') }\n finally { setFetching(false) }\n }\n\n async function handleImport() {\n if (!integration) return\n const toImport = lines.filter((l) => selected.has(l.externalId))\n if (toImport.length === 0) { toast.error('Selecione ao menos uma linha'); return }\n setImporting(true)\n try {\n const res = await provider.importTransactions({\n integrationId: integration.id, bankAccountId: integration.bankAccountId, from, to, lines: toImport,\n })\n toast.success(`${res.imported} importadas${res.duplicates ? ` · ${res.duplicates} duplicadas` : ''}`)\n setLines([]); setSelected(new Set())\n onRan?.()\n } catch (e: any) { toast.error(e?.message ?? 'Erro ao importar') }\n finally { setImporting(false) }\n }\n\n function toggle(id: string) {\n setSelected((prev) => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next })\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center gap-2\"><Download className=\"h-4 w-4 text-muted-foreground\" /><h4 className=\"text-sm font-semibold\">Importar extrato</h4></div>\n <div className=\"flex flex-wrap items-end gap-3\">\n <div><span className=\"text-xs font-medium text-muted-foreground\">De</span><DatePicker value={from} onChange={setFrom} className=\"mt-1\" /></div>\n <div><span className=\"text-xs font-medium text-muted-foreground\">Até</span><DatePicker value={to} onChange={setTo} className=\"mt-1\" /></div>\n <Button variant=\"outline\" size=\"sm\" onClick={handleFetch} disabled={fetching}>\n {fetching ? <Loader2 className=\"h-3.5 w-3.5 animate-spin\" /> : <Download className=\"h-3.5 w-3.5\" />} Buscar extrato\n </Button>\n </div>\n\n {lines.length > 0 && (\n <>\n <div className=\"rounded-md border divide-y max-h-72 overflow-auto\">\n {lines.map((l) => (\n <label key={l.externalId} className=\"flex items-center gap-3 px-3 py-2 text-sm cursor-pointer hover:bg-muted/30\">\n <Checkbox checked={selected.has(l.externalId)} onChange={() => toggle(l.externalId)} />\n <span className=\"text-muted-foreground text-xs w-20 shrink-0\">{l.date}</span>\n <span className=\"flex-1 truncate\">{l.description}</span>\n <span className={`font-semibold shrink-0 ${l.type === 'C' ? 'text-success' : 'text-destructive'}`}>\n {l.type === 'C' ? '+' : '-'}{formatCurrency(l.amount, 'BRL')}\n </span>\n </label>\n ))}\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs text-muted-foreground\">{selected.size} de {lines.length} selecionadas</span>\n <Button size=\"sm\" onClick={handleImport} disabled={importing || selected.size === 0}>\n {importing ? <Loader2 className=\"h-3.5 w-3.5 animate-spin\" /> : <Download className=\"h-3.5 w-3.5\" />} Importar selecionadas\n </Button>\n </div>\n </>\n )}\n\n <p className=\"text-[11px] text-muted-foreground\">\n O token do PlugBank e o CNPJ da conta ficam guardados na plataforma Fayz, não neste\n aplicativo. Quem importa o extrato é o servidor, que pede as credenciais na hora da consulta.\n </p>\n </div>\n )\n}\n\nexport const openBankingConnector: ConnectorDefinition = {\n id: 'plugbank',\n // The product is PlugBank, the company in the directory is TecnoSpeed. Same\n // row, two names — and until this said so, the connector took neither the\n // catalogue's copy nor its mark.\n catalogSlug: 'tecnospeed',\n hostPluginId: 'financial',\n name: 'Open Banking · Tecnospeed PlugBank',\n description: 'Importe o extrato bancário e concilie em Financeiro → Conciliação.',\n icon: 'Landmark',\n authKind: 'api-key',\n // `banking`, not `payments`: reading a statement is not taking a payment, and\n // the platform catalogue files the provider (slug `tecnospeed`) under banking.\n category: 'banking',\n // Inbound because WE reach the bank: PlugBank has never heard of Fayz, which\n // is what makes this the canonical pull connector and why it ships an edge\n // function at all.\n direction: 'inbound',\n // Sync only. The statement has to exist INSIDE Fayz to be listed, filtered and\n // matched against internal movements — reconciliation is not a question the\n // assistant can answer by asking the bank at the moment it is asked.\n planes: ['sync'],\n docsUrl: 'https://tecnospeed.com.br/plugbank/',\n // Write and NOT read, which is unusual enough to be worth saying out loud:\n // `plugbank_import_movements` (migrations/003) only ever INSERTs, with\n // `ON CONFLICT (tenant_id, external_source, external_id) DO NOTHING`. It never\n // SELECTs a movement, so declaring `read` here would be an access the code does\n // not take — and the whole value of this list is that it is not padded.\n //\n // The `emit` is the second half, and it is the one a tenant would not guess:\n // every imported line fires `financial.movement.imported` through the trigger\n // in migrations/002, and that payload carries amount, date, description and\n // bank account to whatever automation is listening. Importing a statement is\n // therefore also publishing it internally.\n permissions: [\n {\n kind: 'data',\n resource: 'movement',\n actions: ['write'],\n reason:\n 'Gravar no seu financeiro as linhas do extrato bancário — valor, data, descrição e conta. Lançamentos já importados não entram de novo.',\n sensitive: true,\n },\n {\n kind: 'event',\n resource: 'financial.movement.imported',\n actions: ['emit'],\n reason:\n 'Avisar o app a cada lançamento importado, para que suas automações de conciliação possam reagir. O aviso leva valor, data e conta.',\n sensitive: true,\n },\n ],\n invokedAt: [\n {\n key: 'financial.statement.import',\n label: 'Você escolhe um período aqui e o extrato do banco é importado',\n plane: 'sync',\n trigger: 'manual',\n touches: ['movement', 'financial.movement.imported'],\n },\n ],\n // No credential field. The token and the CNPJ that identify the account are\n // registered in the Fayz platform; anything typed here would be stored in a\n // table every member of the tenant can read.\n //\n // `subscribesTo` is deliberately absent, and so is `managedAutomations`.\n // Nothing that happens inside Fayz should wake this connector: it pulls a\n // statement, and what makes it pull is the operator picking a period below or\n // the Fayz SCHEDULE worker (FAY-1347) — never a domain fact. A subscription\n // written to fill the field would put this connector on the ingress's\n // delivery list for events it has no reaction to.\n //\n // The reconciliation automations (\"avise quando entrar um valor acima de X\")\n // are real, but they are the TENANT's: they react to\n // `financial.movement.imported` and the tenant may delete them without the\n // connection becoming half-wired. `managedAutomations` is for the wiring the\n // integration IS, and this integration is the import itself.\n // Read from the row the list already batched — one request for N connectors.\n // The account-less default instance (`''`) is the one the panel addresses.\n ...connectionStatus(PLUGBANK_CONNECTOR_ID, (connection) => {\n const found = connection ? toIntegration(connection) : null\n return { connected: !!found?.active, detail: found?.environment, plane: 'sync' }\n }),\n // `testConnection` is deleted rather than stubbed: the credential is not in\n // this app, so there is nothing this side can validate, and ConnectorDetail\n // renders the button only when the verb exists.\n saveConnection: async () => { await provider.saveIntegration() },\n disconnect: () => provider.disconnect(),\n // Every import files a `sync_runs` row, and the shared surface renders it.\n // What is connector-specific is not the VIEW, it is the meaning of `stats`:\n // \"de 01/08 a 12/08 · 3 duplicadas\" is three keys only this connector can\n // read, turned here into the one sentence every run carries. Scoped to the\n // default instance — the account this panel addresses.\n async listRuns(options) {\n try {\n const runs = await createConnectionStore(PLUGBANK_CONNECTOR_ID).listRuns(options)\n return runs.map((run) => ({ ...run, message: run.message ?? statementLine(run) }))\n } catch {\n return []\n }\n },\n RunActions: OpenBankingImport,\n}\n","import type { EventField, PluginEventDefinition } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// The FACT of the bank import.\n//\n// The emitter is not in this file and cannot be: a statement line reaches the\n// ledger from the edge function's insert, from plugbank_import_movements, from\n// a future scheduled pull and occasionally from a hand-run backfill. The one\n// place all four end up is the row, so the emitter is a trigger on\n// plg_financial_movements (migration 002 §4) and this is its contract.\n//\n// This is the fact that turns reconciliation from a screen someone remembers to\n// open into something the tenant can react to — \"avise quando entrar um valor\n// acima de X\" is an automation on this event and nothing else.\n//\n// `provider` is in the payload because the trigger fires for ANY imported\n// movement, not only PlugBank's. A second bank connector writing into the same\n// ledger produces the same fact rather than a second vocabulary for it.\n// ---------------------------------------------------------------------------\n\n/** Payload keys, in the order the trigger builds them. */\nexport const MOVEMENT_IMPORTED_FIELDS: EventField[] = [\n { name: 'movement_id', label: 'ID do lançamento', type: 'id', example: '8f14e45f-…' },\n { name: 'provider', label: 'Origem', type: 'string', example: 'plugbank' },\n { name: 'external_id', label: 'Identificador no banco', type: 'string', example: 'LINE-1' },\n { name: 'direction', label: 'Tipo', type: 'string', example: 'credit' },\n { name: 'amount', label: 'Valor', type: 'money', example: 'R$ 150,00' },\n { name: 'payment_date', label: 'Data', type: 'date', example: '03/08/2026' },\n { name: 'description', label: 'Descrição', type: 'string', example: 'Recebimento PIX' },\n { name: 'bank_account_id', label: 'Conta bancária', type: 'id', example: '3f2a91c4-…' },\n]\n\nconst PAYLOAD_SCHEMA: Record<string, unknown> = {\n type: 'object',\n properties: {\n movement_id: { type: 'string', format: 'uuid', description: 'Movement id in the tenant ledger' },\n provider: {\n type: 'string',\n description:\n \"Which integration imported the line — 'plugbank' today. Reads from plg_financial_movements.external_source, so a second bank connector produces the same fact.\",\n },\n external_id: {\n type: 'string',\n description: \"The bank's own id for the statement line. With `provider` it is the import idempotency key.\",\n },\n direction: { type: 'string', enum: ['credit', 'debit'], description: 'Money in or money out' },\n amount: { type: 'number', description: 'Always positive; `direction` carries the sign' },\n payment_date: { type: 'string', format: 'date', description: 'Date the bank settled the line (YYYY-MM-DD)' },\n description: { type: ['string', 'null'], description: 'Statement narrative, as the bank wrote it' },\n bank_account_id: { type: ['string', 'null'], format: 'uuid', description: 'Account the line landed in, when known' },\n },\n required: ['movement_id', 'provider', 'external_id', 'direction', 'amount', 'payment_date'],\n additionalProperties: false,\n}\n\n/**\n * `audit: true`. Money arriving from outside the app is exactly the subject\n * where \"where did this come from, and when?\" needs an answer that survives a\n * later edit — sharper still when the answer is a connector rather than a\n * person. There is no `updated` counterpart on purpose: a statement line is not\n * revised, it is imported once and then reconciled, and reconciliation is the\n * financial plugin's fact to declare, not this addon's.\n */\nexport function buildBankingDomainEvents(): PluginEventDefinition[] {\n return [\n {\n name: 'financial.movement.imported',\n title: 'Lançamento bancário importado',\n description: 'Uma linha do extrato entrou no financeiro',\n trigger: 'Quando o extrato é importado — pelo painel, pelo conector ou por uma carga manual',\n icon: 'Landmark',\n audit: true,\n fields: MOVEMENT_IMPORTED_FIELDS,\n payloadSchema: PAYLOAD_SCHEMA,\n },\n ]\n}\n","// AUTO-GENERATED from 000_baseline.sql, 001_the_import_rpc_names_real_tables.sql — regenerate with scripts/embed-migrations.mjs\n// SQL files are the source of truth; this inline copy lets the manifest declare\n// migrations as data. Do not edit by hand — run the embed script instead.\n\nexport const MIGRATION_000_BASELINE = `-- ============================================================================\n-- plugins/plugin-banking-br/src/migrations/000_baseline.sql — what installing this provisions, as one file.\n--\n-- GENERATED by scripts/emit-unit-baselines.mjs (#338). Do not edit by hand:\n-- change the schema, regenerate, and let scripts/check-chain-equivalence.mjs\n-- prove the result still matches.\n--\n-- This replaced 2 migration files. They are kept, unapplied, in\n-- migrations.archive/ — a year of decisions is worth reading even when it is no\n-- longer worth replaying.\n--\n-- Object order is fixed rather than dependency-sorted: schemas, types,\n-- sequences, tables, defaults, functions, constraints, views, indexes, foreign\n-- keys, triggers, row security, policies, grants. Dependencies BETWEEN units\n-- are carried by the order packages/db/chain.json declares.\n-- ============================================================================\n\n-- A LANGUAGE sql function binds its body at CREATE time, and 570 functions\n-- sorted by name are not sorted by who calls whom. This is what lets them load\n-- in any order; every body is still compiled on first call, so a genuinely\n-- broken reference surfaces then rather than never. Session-scoped.\nSET check_function_bodies = false;\n\n\n\n-- ── default privileges: reset ───────────────────────────────────────────\n-- Cleared BEFORE anything is created, so every object below gets the ACL the\n-- dump describes rather than the image's defaults on top of it. Restored at\n-- the end of this file.\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\n\n-- ── function ────────────────────────────────────────────────────────────\n\nCREATE FUNCTION public.plg_bank_emit_movement_imported() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO 'public'\n AS $$\nDECLARE\n v_payload jsonb;\nBEGIN\n IF NEW.external_source IS NULL OR NEW.external_id IS NULL THEN\n RETURN NULL;\n END IF;\n\n -- The contract every consumer reads. Not the row: a whole-row payload would\n -- put the ledger's internal bookkeeping inside every webhook by default.\n v_payload := jsonb_build_object(\n 'movement_id', NEW.id::text,\n 'provider', NEW.external_source,\n 'external_id', NEW.external_id,\n 'direction', NEW.direction,\n 'amount', NEW.amount,\n 'payment_date', NEW.payment_date,\n 'description', NEW.notes,\n 'bank_account_id', NEW.bank_account_id);\n\n BEGIN\n PERFORM public.plg_emit_event(\n 'financial.movement.imported', v_payload, 'financial_movement',\n NEW.id::text, NEW.tenant_id, NULL);\n EXCEPTION WHEN OTHERS THEN\n -- Observability must never be able to reject the write that caused it.\n RAISE WARNING '[plg_bank_emit_movement_imported] % on %: %', TG_OP, TG_TABLE_NAME, SQLERRM;\n END;\n\n RETURN NULL;\nEND;\n$$;\n\nCREATE FUNCTION public.plugbank_import_movements(p_payload jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO 'public'\n AS $$\nDECLARE\n v_tenants uuid[];\n v_tenant uuid;\n v_connection uuid;\n v_instance text;\n v_account uuid;\n v_connector text;\n v_trigger text;\n v_from date;\n v_to date;\n v_started timestamptz := now();\n v_line jsonb;\n v_external text;\n v_amount numeric(14,2);\n v_date date;\n v_fetched int := 0;\n v_imported int := 0;\n v_duplicates int := 0;\n v_inserted uuid;\n v_run uuid;\nBEGIN\n v_connection := nullif(btrim(p_payload->>'integration_id'), '')::uuid;\n IF v_connection IS NULL THEN\n RAISE EXCEPTION 'plugbank_import_movements: integration_id is required'\n USING ERRCODE = 'invalid_parameter_value';\n END IF;\n\n SELECT c.tenant_id, c.instance_key, c.connector_id\n INTO v_tenant, v_instance, v_connector\n FROM public.plg_connections c\n WHERE c.id = v_connection AND c.active;\n\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'plugbank_import_movements: no active connection %', v_connection\n USING ERRCODE = 'no_data_found';\n END IF;\n\n -- The account this connection is for. \\`instance_key\\` is text because it also\n -- carries things that are not uuids for other connectors (a Bling company, a\n -- gateway instance); here it is the bank account, and '' is the account-less\n -- default that \\`bank_account_id IS NULL\\` used to mean.\n v_account := nullif(btrim(v_instance), '')::uuid;\n\n -- SECURITY DEFINER bypasses RLS, so the membership check the policy would\n -- have made has to happen here.\n SELECT array_agg(t) INTO v_tenants FROM public.user_tenant_ids() AS t;\n IF auth.uid() IS NOT NULL AND NOT (v_tenant = ANY (COALESCE(v_tenants, '{}'::uuid[]))) THEN\n RAISE EXCEPTION 'plugbank_import_movements: caller is not a member of tenant %', v_tenant\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n -- Refusing beats ignoring: a caller that believes it is importing into\n -- another tenant must be told, not silently redirected into this one.\n IF nullif(btrim(p_payload->>'tenant_id'), '') IS NOT NULL\n AND (p_payload->>'tenant_id')::uuid IS DISTINCT FROM v_tenant THEN\n RAISE EXCEPTION 'plugbank_import_movements: payload names another tenant; the tenant comes from the connection'\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n -- Same rule as the tenant, one level down: the account is the connection's,\n -- and a payload naming a different one is a caller importing into the wrong\n -- ledger account. 002 let the payload override it silently — with one\n -- connection per account that override is how two accounts get mixed.\n IF nullif(btrim(p_payload->>'bank_account_id'), '') IS NOT NULL\n AND (p_payload->>'bank_account_id')::uuid IS DISTINCT FROM v_account THEN\n RAISE EXCEPTION 'plugbank_import_movements: payload names another bank account; the account comes from the connection'\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n v_from := nullif(btrim(p_payload->>'period_from'), '')::date;\n v_to := nullif(btrim(p_payload->>'period_to'), '')::date;\n v_trigger := lower(coalesce(nullif(btrim(p_payload->>'trigger'), ''), 'manual'));\n IF v_trigger NOT IN ('on-write', 'scheduled', 'manual', 'webhook') THEN\n RAISE EXCEPTION 'plugbank_import_movements: unknown trigger %', v_trigger\n USING ERRCODE = 'invalid_parameter_value';\n END IF;\n\n FOR v_line IN SELECT * FROM jsonb_array_elements(coalesce(p_payload->'lines', '[]'::jsonb))\n LOOP\n v_fetched := v_fetched + 1;\n v_external := nullif(btrim(v_line->>'external_id'), '');\n v_date := nullif(btrim(v_line->>'date'), '')::date;\n v_amount := abs(coalesce((v_line->>'amount')::numeric, 0));\n\n -- A line with no provider id cannot be recognised on re-delivery, so it\n -- would import again on every run. Skipping it is the lesser wrong.\n IF v_external IS NULL OR v_date IS NULL OR v_amount = 0 THEN\n CONTINUE;\n END IF;\n\n INSERT INTO public.plg_financial_movements (\n tenant_id, direction, movement_kind, amount, paid_amount, status,\n due_date, payment_date, bank_account_id, notes, external_id, external_source)\n VALUES (\n v_tenant,\n CASE WHEN upper(coalesce(v_line->>'type', 'C')) LIKE 'C%' THEN 'credit' ELSE 'debit' END,\n 'payment', v_amount, v_amount, 'paid',\n v_date, v_date, v_account,\n nullif(btrim(v_line->>'description'), ''),\n v_external, coalesce(v_connector, 'plugbank'))\n ON CONFLICT (tenant_id, external_source, external_id)\n WHERE external_id IS NOT NULL AND external_source IS NOT NULL\n DO NOTHING\n RETURNING id INTO v_inserted;\n\n IF v_inserted IS NULL THEN\n v_duplicates := v_duplicates + 1;\n ELSE\n v_imported := v_imported + 1;\n END IF;\n v_inserted := NULL;\n END LOOP;\n\n UPDATE public.plg_connections\n SET last_sync_at = now(), status = 'connected', status_detail = NULL, updated_at = now()\n WHERE id = v_connection;\n\n -- \\`stats\\` is where the three facts a manual statement import has and a generic\n -- run does not go: the operator picked a period, a re-import of that period is\n -- mostly duplicates by design, and someone pressed the button. The panel reads\n -- all three back from here.\n INSERT INTO public.plg_sync_runs (\n tenant_id, connection_id, connector_id, direction, trigger_kind, status,\n stream, fetched, written, stats, started_at, finished_at)\n VALUES (\n v_tenant, v_connection, coalesce(v_connector, 'plugbank'), 'inbound', v_trigger, 'success',\n 'statement', v_fetched, v_imported,\n jsonb_strip_nulls(jsonb_build_object(\n 'duplicates', v_duplicates,\n 'period_from', v_from,\n 'period_to', v_to,\n 'triggered_by_user_id', auth.uid())),\n v_started, now())\n RETURNING id INTO v_run;\n\n RETURN jsonb_build_object(\n 'ok', true,\n 'sync_run_id', v_run,\n 'fetched', v_fetched,\n 'imported', v_imported,\n 'duplicates', v_duplicates);\nEND;\n$$;\n\n\n-- ── trigger ─────────────────────────────────────────────────────────────\n\nCREATE TRIGGER plg_financial_movements_emit_imported AFTER INSERT ON public.plg_financial_movements FOR EACH ROW EXECUTE FUNCTION public.plg_bank_emit_movement_imported();\n\n\n-- ── acl ─────────────────────────────────────────────────────────────────\n\nREVOKE ALL ON FUNCTION public.plg_bank_emit_movement_imported() FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plg_bank_emit_movement_imported() TO authenticated;\nGRANT ALL ON FUNCTION public.plg_bank_emit_movement_imported() TO service_role;\n\nREVOKE ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) TO authenticated;\nGRANT ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) TO service_role;\n\n\n-- ── comment ─────────────────────────────────────────────────────────────\n\nCOMMENT ON FUNCTION public.plugbank_import_movements(p_payload jsonb) IS 'Import bank-statement lines into the financial ledger and file the run in plg_sync_runs. The tenant AND the bank account come from the plg_connections row named by integration_id, never from the payload.';\n\n\n-- ── default privileges: restore ─────────────────────────────────────────\n-- Put back, so the NEXT thing installed inherits what the chain had.\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$--\n-- PostgreSQL database dump complete\n--$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$--\n-- PostgreSQL database dump complete\n--$stmt$;\nEND $dp$;\n`\n\nexport const MIGRATION_001_THE_IMPORT_RPC_NAMES_REAL_TABLES = `-- ==========================================================================\n-- 001_the_import_rpc_names_real_tables — plugbank_import_movements\n-- stops reaching through 141's bridge (#352).\n--\n-- 107 renamed the connector spine out of the \\`plg_\\` prefix — the prefix means\n-- \"a plugin owns this\", and the connector framework's tables are the BASE's.\n-- 141 left \\`plg_connections\\` and \\`plg_sync_runs\\` answering as views so callers\n-- could move without a synchronised deploy. This function never moved, and it\n-- is not alone: \\`plg_connections\\` had 13 callers and \\`connections\\` had none.\n-- The bridge became the road.\n--\n-- The body is 000_baseline's, unchanged except for the three names. It is not\n-- an edit of that file — an applied migration is immutable and the ledger\n-- treats drift as a hard stop — it is a CREATE OR REPLACE that lands after it.\n--\n-- Why this plugin has to carry its own: the function is banking-br's, and the\n-- views it stops using are the spine's. The spine runs FIRST, so a spine file\n-- cannot drop a view that a plugin applied later still reads. Each package\n-- moves its own callers; the aliases go once nobody is left.\n--\n-- Replay-safe: CREATE OR REPLACE, same signature, same grants (which 000 set\n-- and this does not touch).\n-- ==========================================================================\n\nCREATE OR REPLACE FUNCTION public.plugbank_import_movements(p_payload jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO 'public'\n AS $$\nDECLARE\n v_tenants uuid[];\n v_tenant uuid;\n v_connection uuid;\n v_instance text;\n v_account uuid;\n v_connector text;\n v_trigger text;\n v_from date;\n v_to date;\n v_started timestamptz := now();\n v_line jsonb;\n v_external text;\n v_amount numeric(14,2);\n v_date date;\n v_fetched int := 0;\n v_imported int := 0;\n v_duplicates int := 0;\n v_inserted uuid;\n v_run uuid;\nBEGIN\n v_connection := nullif(btrim(p_payload->>'integration_id'), '')::uuid;\n IF v_connection IS NULL THEN\n RAISE EXCEPTION 'plugbank_import_movements: integration_id is required'\n USING ERRCODE = 'invalid_parameter_value';\n END IF;\n\n SELECT c.tenant_id, c.instance_key, c.connector_id\n INTO v_tenant, v_instance, v_connector\n FROM public.connections c\n WHERE c.id = v_connection AND c.active;\n\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'plugbank_import_movements: no active connection %', v_connection\n USING ERRCODE = 'no_data_found';\n END IF;\n\n -- The account this connection is for. \\`instance_key\\` is text because it also\n -- carries things that are not uuids for other connectors (a Bling company, a\n -- gateway instance); here it is the bank account, and '' is the account-less\n -- default that \\`bank_account_id IS NULL\\` used to mean.\n v_account := nullif(btrim(v_instance), '')::uuid;\n\n -- SECURITY DEFINER bypasses RLS, so the membership check the policy would\n -- have made has to happen here.\n SELECT array_agg(t) INTO v_tenants FROM public.user_tenant_ids() AS t;\n IF auth.uid() IS NOT NULL AND NOT (v_tenant = ANY (COALESCE(v_tenants, '{}'::uuid[]))) THEN\n RAISE EXCEPTION 'plugbank_import_movements: caller is not a member of tenant %', v_tenant\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n -- Refusing beats ignoring: a caller that believes it is importing into\n -- another tenant must be told, not silently redirected into this one.\n IF nullif(btrim(p_payload->>'tenant_id'), '') IS NOT NULL\n AND (p_payload->>'tenant_id')::uuid IS DISTINCT FROM v_tenant THEN\n RAISE EXCEPTION 'plugbank_import_movements: payload names another tenant; the tenant comes from the connection'\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n -- Same rule as the tenant, one level down: the account is the connection's,\n -- and a payload naming a different one is a caller importing into the wrong\n -- ledger account. 002 let the payload override it silently — with one\n -- connection per account that override is how two accounts get mixed.\n IF nullif(btrim(p_payload->>'bank_account_id'), '') IS NOT NULL\n AND (p_payload->>'bank_account_id')::uuid IS DISTINCT FROM v_account THEN\n RAISE EXCEPTION 'plugbank_import_movements: payload names another bank account; the account comes from the connection'\n USING ERRCODE = 'insufficient_privilege';\n END IF;\n\n v_from := nullif(btrim(p_payload->>'period_from'), '')::date;\n v_to := nullif(btrim(p_payload->>'period_to'), '')::date;\n v_trigger := lower(coalesce(nullif(btrim(p_payload->>'trigger'), ''), 'manual'));\n IF v_trigger NOT IN ('on-write', 'scheduled', 'manual', 'webhook') THEN\n RAISE EXCEPTION 'plugbank_import_movements: unknown trigger %', v_trigger\n USING ERRCODE = 'invalid_parameter_value';\n END IF;\n\n FOR v_line IN SELECT * FROM jsonb_array_elements(coalesce(p_payload->'lines', '[]'::jsonb))\n LOOP\n v_fetched := v_fetched + 1;\n v_external := nullif(btrim(v_line->>'external_id'), '');\n v_date := nullif(btrim(v_line->>'date'), '')::date;\n v_amount := abs(coalesce((v_line->>'amount')::numeric, 0));\n\n -- A line with no provider id cannot be recognised on re-delivery, so it\n -- would import again on every run. Skipping it is the lesser wrong.\n IF v_external IS NULL OR v_date IS NULL OR v_amount = 0 THEN\n CONTINUE;\n END IF;\n\n INSERT INTO public.plg_financial_movements (\n tenant_id, direction, movement_kind, amount, paid_amount, status,\n due_date, payment_date, bank_account_id, notes, external_id, external_source)\n VALUES (\n v_tenant,\n CASE WHEN upper(coalesce(v_line->>'type', 'C')) LIKE 'C%' THEN 'credit' ELSE 'debit' END,\n 'payment', v_amount, v_amount, 'paid',\n v_date, v_date, v_account,\n nullif(btrim(v_line->>'description'), ''),\n v_external, coalesce(v_connector, 'plugbank'))\n ON CONFLICT (tenant_id, external_source, external_id)\n WHERE external_id IS NOT NULL AND external_source IS NOT NULL\n DO NOTHING\n RETURNING id INTO v_inserted;\n\n IF v_inserted IS NULL THEN\n v_duplicates := v_duplicates + 1;\n ELSE\n v_imported := v_imported + 1;\n END IF;\n v_inserted := NULL;\n END LOOP;\n\n UPDATE public.connections\n SET last_sync_at = now(), status = 'connected', status_detail = NULL, updated_at = now()\n WHERE id = v_connection;\n\n -- \\`stats\\` is where the three facts a manual statement import has and a generic\n -- run does not go: the operator picked a period, a re-import of that period is\n -- mostly duplicates by design, and someone pressed the button. The panel reads\n -- all three back from here.\n INSERT INTO public.sync_runs (\n tenant_id, connection_id, connector_id, direction, trigger_kind, status,\n stream, fetched, written, stats, started_at, finished_at)\n VALUES (\n v_tenant, v_connection, coalesce(v_connector, 'plugbank'), 'inbound', v_trigger, 'success',\n 'statement', v_fetched, v_imported,\n jsonb_strip_nulls(jsonb_build_object(\n 'duplicates', v_duplicates,\n 'period_from', v_from,\n 'period_to', v_to,\n 'triggered_by_user_id', auth.uid())),\n v_started, now())\n RETURNING id INTO v_run;\n\n RETURN jsonb_build_object(\n 'ok', true,\n 'sync_run_id', v_run,\n 'fetched', v_fetched,\n 'imported', v_imported,\n 'duplicates', v_duplicates);\nEND;\n$$;\nCOMMENT ON FUNCTION public.plugbank_import_movements(p_payload jsonb) IS\n 'Import bank-statement lines into the financial ledger and file the run in public.sync_runs. The tenant AND the bank account come from the public.connections row named by integration_id, never from the payload.';\n`\n\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = [\n { id: \"000_baseline\", sql: MIGRATION_000_BASELINE },\n { id: \"001_the_import_rpc_names_real_tables\", sql: MIGRATION_001_THE_IMPORT_RPC_NAMES_REAL_TABLES },\n]\n","// Open Banking plugin (Tecnospeed PlugBank) — public surface + manifest.\n//\n// The canonical PULL connector: the provider has never heard of Fayz, so an edge\n// function reaches out to it. Settings-only (no left-nav page) — it contributes\n// its connector into plugin-financial's settings → Integrations, and imported\n// lines are matched in Financeiro → Conciliação (enable the financial\n// `reconciliation` module).\nimport type { PluginManifest } from '@fayz-ai/core'\nimport { openBankingConnector } from './connectors/connectorDef'\nimport { buildBankingDomainEvents } from './lib/banking-events'\nimport { MIGRATIONS } from './migrations'\n\nexport { createOpenBankingProvider } from './data/supabase'\nexport { openBankingConnector } from './connectors/connectorDef'\nexport { buildBankingDomainEvents, MOVEMENT_IMPORTED_FIELDS } from './lib/banking-events'\n\nexport type {\n BankIntegration,\n BankLine,\n BankSyncRun,\n FetchStatementResult,\n ImportResult,\n SaveIntegrationInput,\n SyncRun,\n} from './types'\n\n// Addon plugin: contributes the PlugBank connector into its host\n// (plugin-financial). It appears in Financial settings → Integrations — no\n// top-level settings tab of its own.\nexport function createOpenBankingPlugin(): PluginManifest {\n return {\n id: 'openbanking',\n name: 'Open Banking',\n icon: 'Landmark',\n version: '0.9.1',\n apiVersion: 1,\n scope: 'addon',\n defaultEnabled: true,\n dependencies: ['financial'],\n navigation: [],\n routes: [],\n connectors: [openBankingConnector],\n // The fact the import produces. Declared HERE and not on plugin-financial\n // because the trigger that emits it ships in this plugin's 002: a host\n // advertising an event that only exists when an addon is installed is the\n // same broken promise as an automation the ingress never delivers.\n events: buildBankingDomainEvents(),\n // `emitsFrom` is deliberately absent. It generates\n // `registry.<entity>.<action>` keys for every row change, and \"imported\" is\n // not a row action — it is an INSERT carrying an external_source, which only\n // a purpose-built trigger can tell apart from a sale typed into the till.\n functions: [\n {\n slug: 'plugbank-sync',\n // Invoked from the browser by an authenticated operator picking a\n // period, so Supabase must check the caller's JWT. It is not reached\n // through the Fayz ingress — nothing in Fayz wakes a pull connector —\n // which is what lets verifyJwt stay true.\n ingress: 'none',\n verifyJwt: true,\n // NAMES only. PLUGBANK_BASE_URL selects production vs sandbox; the\n // per-tenant token and account CNPJ are NOT here, because a Supabase\n // function secret is per-POOL and a pool serves N tenants with N\n // different bank accounts. Those come from the Fayz platform per call.\n secrets: ['PLUGBANK_BASE_URL', 'SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY'],\n // What this connector's credential may touch. Direct table access is\n // not on the list on purpose (2026-08-11 ADR): the import goes through\n // the RPC, which takes the tenant from the integration row instead of\n // from the request body.\n grants: ['plugbank_import_movements'],\n },\n ],\n declaredRpcs: [\n {\n name: 'plugbank_import_movements',\n kind: 'write',\n description:\n 'Import bank-statement lines into the financial ledger. Idempotent on (tenant, provider, external id); the tenant comes from the integration row, never from the payload.',\n audits: false,\n },\n ],\n // This plugin creates no table. 003 retired the two it had; what is left is\n // the RPC that is the import's one door, and the trigger that turns an\n // imported movement into a fact. The connection and its run history are rows\n // of connections / sync_runs, owned by packages/db.\n // One baseline per unit since #338 — read from the generated barrel\n // rather than listed here, so regenerating the SQL never needs a\n // manifest edit to match.\n migrations: MIGRATIONS.map((entry) => ({\n id: `openbanking-${entry.id}`,\n version: '1.0.0',\n sql: entry.sql,\n description:\n 'The whole schema this plugin provisions, as one file: every table, view, function, policy and grant, plus the reference rows it seeds. Replaced the per-change chain in the 1.0.0 squash (#338); the files it collapsed are kept, unapplied, in src/migrations.archive/.',\n })),\n diagnostics: [\n {\n id: 'openbanking-ledger-ready',\n description:\n 'The import writes into the financial ledger and needs its reconciliation columns (plugin-financial 007), the generic connection tables (packages/db 029) and the row-level event emitter.',\n requires: {\n tables: ['connections', 'sync_runs', 'plg_financial_movements'],\n rpcs: ['plugbank_import_movements', 'plg_emit_event'],\n },\n level: 'error',\n },\n ],\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fayz-ai/plugin-banking-br",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.34",
|
|
4
4
|
"description": "[experimental] Fayz SDK — Brazilian open-banking connector (Tecnospeed PlugBank) feeding the financial ledger",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@fayz-ai/core": "^0.
|
|
29
|
-
"@fayz-ai/db": "^0.24.
|
|
30
|
-
"@fayz-ai/ui": "^0.
|
|
28
|
+
"@fayz-ai/core": "^0.49.0",
|
|
29
|
+
"@fayz-ai/db": "^0.24.1",
|
|
30
|
+
"@fayz-ai/ui": "^0.49.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/react": "^18.3.0",
|