@fayz-ai/plugin-financial 0.24.5 → 0.25.0

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/index.js CHANGED
@@ -44899,6 +44899,261 @@ END $$;
44899
44899
  REVOKE ALL ON FUNCTION public.financial_set_invoice_lines(uuid, jsonb, boolean) FROM PUBLIC, anon;
44900
44900
  GRANT EXECUTE ON FUNCTION public.financial_set_invoice_lines(uuid, jsonb, boolean) TO authenticated, service_role;
44901
44901
  `;
44902
+ var MIGRATION_136_A_BANK_STATEMENT_ARRIVES_FROM_THE_SERVICE = `-- ============================================================================
44903
+ -- 136 \u2014 a bank statement arrives from the integration service
44904
+ --
44905
+ -- The V2 sink of the banking module (ADR 0003): the doors through which the
44906
+ -- integration service hands this product the lines of a store's bank or
44907
+ -- credit-card statement, withdraws the ones the bank took back, reports the
44908
+ -- connection's state, and reads which lines are already here. The provider
44909
+ -- calls, the schedule and the waiting are the service's; what a line IS here
44910
+ -- is decided here.
44911
+ --
44912
+ -- A line is keyed as before: (tenant, external_source, external_id), where
44913
+ -- external_source is the statement source's provider ('openbanking:pagbank')
44914
+ -- and external_id the provider's transaction id. Lines already brought in by
44915
+ -- the V1 migration match the ones the service brings, so nothing doubles.
44916
+ --
44917
+ -- Providers revise statements for days: an Open Finance bank can drop a
44918
+ -- transaction and bring it back under a new id. A withdrawn line is marked
44919
+ -- (provider_deleted), never deleted, and comes back if the provider brings
44920
+ -- the same id again. A line already reconciled keeps its money and its date:
44921
+ -- a revision of either is recorded beside it, for a person to look at.
44922
+ --
44923
+ -- No person is behind these calls; the actor is the service, acting for the
44924
+ -- tenant it names. The functions are closed to every signed-in user and
44925
+ -- opened only to the service's role, \`fayz_integration\`, when it exists.
44926
+ -- ============================================================================
44927
+
44928
+ CREATE OR REPLACE FUNCTION public.financial_statement_source_get(p_tenant uuid, p_source uuid)
44929
+ RETURNS jsonb
44930
+ LANGUAGE sql STABLE SECURITY DEFINER
44931
+ SET search_path TO ''
44932
+ AS $$
44933
+ SELECT jsonb_build_object(
44934
+ 'id', s.id, 'tenantId', s.tenant_id, 'unitId', s.unit_id, 'accountId', s.account_id,
44935
+ 'provider', s.provider, 'kind', s.statement_kind, 'cardLast4', s.card_last4,
44936
+ 'accountRef', s.provider_account_hash, 'active', s.is_active,
44937
+ 'lastSyncedAt', s.last_synced_at, 'lastSyncStatus', s.last_sync_status
44938
+ )
44939
+ FROM public.plg_financial_account_statement_sources s
44940
+ WHERE s.id = p_source AND s.tenant_id = p_tenant
44941
+ $$;
44942
+
44943
+ -- Every source tied to a provider account, across tenants: how the service
44944
+ -- finds the connections the V1 migration left, to adopt them.
44945
+ CREATE OR REPLACE FUNCTION public.financial_statement_sources_linked(p_provider_prefix text)
44946
+ RETURNS SETOF jsonb
44947
+ LANGUAGE sql STABLE SECURITY DEFINER
44948
+ SET search_path TO ''
44949
+ AS $$
44950
+ SELECT jsonb_build_object(
44951
+ 'id', s.id, 'tenantId', s.tenant_id, 'unitId', s.unit_id, 'accountId', s.account_id,
44952
+ 'provider', s.provider, 'kind', s.statement_kind, 'cardLast4', s.card_last4,
44953
+ 'accountRef', s.provider_account_hash, 'active', s.is_active
44954
+ )
44955
+ FROM public.plg_financial_account_statement_sources s
44956
+ WHERE s.provider LIKE p_provider_prefix || '%' AND s.provider_account_hash IS NOT NULL
44957
+ ORDER BY s.tenant_id, s.created_at
44958
+ $$;
44959
+
44960
+ -- Ties a source to the provider's account, once the store has authorized it.
44961
+ CREATE OR REPLACE FUNCTION public.financial_statement_source_link(p_tenant uuid, p_source uuid, p_account_ref text)
44962
+ RETURNS void
44963
+ LANGUAGE plpgsql SECURITY DEFINER
44964
+ SET search_path TO ''
44965
+ AS $$
44966
+ BEGIN
44967
+ IF p_account_ref IS NULL OR btrim(p_account_ref) = '' THEN
44968
+ RAISE EXCEPTION 'financial_statement_source_link: account ref is required' USING ERRCODE = '22023';
44969
+ END IF;
44970
+ UPDATE public.plg_financial_account_statement_sources
44971
+ SET provider_account_hash = btrim(p_account_ref), updated_at = now()
44972
+ WHERE id = p_source AND tenant_id = p_tenant;
44973
+ IF NOT FOUND THEN
44974
+ RAISE EXCEPTION 'financial_statement_source_link: source % is not of tenant %', p_source, p_tenant USING ERRCODE = 'P0002';
44975
+ END IF;
44976
+ END
44977
+ $$;
44978
+
44979
+ CREATE OR REPLACE FUNCTION public.financial_statement_lines_ingest(p_tenant uuid, p_source uuid, p_lines jsonb)
44980
+ RETURNS jsonb
44981
+ LANGUAGE plpgsql SECURITY DEFINER
44982
+ SET search_path TO ''
44983
+ AS $$
44984
+ DECLARE
44985
+ v_source public.plg_financial_account_statement_sources%ROWTYPE;
44986
+ v_line jsonb;
44987
+ v_amount numeric;
44988
+ v_day date;
44989
+ v_method text;
44990
+ v_meta jsonb;
44991
+ v_existing public.plg_financial_bank_statement_lines%ROWTYPE;
44992
+ v_inserted integer := 0;
44993
+ v_updated integer := 0;
44994
+ BEGIN
44995
+ SELECT * INTO v_source FROM public.plg_financial_account_statement_sources
44996
+ WHERE id = p_source AND tenant_id = p_tenant;
44997
+ IF NOT FOUND THEN
44998
+ RAISE EXCEPTION 'financial_statement_lines_ingest: source % is not of tenant %', p_source, p_tenant USING ERRCODE = 'P0002';
44999
+ END IF;
45000
+ IF jsonb_typeof(p_lines) <> 'array' THEN
45001
+ RAISE EXCEPTION 'financial_statement_lines_ingest: lines must be an array' USING ERRCODE = '22023';
45002
+ END IF;
45003
+
45004
+ FOR v_line IN SELECT value FROM jsonb_array_elements(p_lines) LOOP
45005
+ IF coalesce(v_line ->> 'externalId', '') = '' OR (v_line ->> 'direction') NOT IN ('credit', 'debit') THEN
45006
+ RAISE EXCEPTION 'financial_statement_lines_ingest: a line needs externalId and direction' USING ERRCODE = '22023';
45007
+ END IF;
45008
+ v_amount := round((v_line ->> 'amountCents')::numeric / 100, 2);
45009
+ v_day := (v_line ->> 'postedOn')::date;
45010
+ IF v_amount IS NULL OR v_amount < 0 OR v_day IS NULL THEN
45011
+ RAISE EXCEPTION 'financial_statement_lines_ingest: line % needs a positive amount and a day', v_line ->> 'externalId' USING ERRCODE = '22023';
45012
+ END IF;
45013
+ v_method := nullif(v_line ->> 'paymentMethod', '');
45014
+ v_meta := jsonb_strip_nulls(jsonb_build_object(
45015
+ 'statement', jsonb_build_object('ref', v_line ->> 'statementRef', 'via', 'integration-service'),
45016
+ 'paymentMethod', v_method,
45017
+ 'currency', coalesce(v_line ->> 'currency', 'BRL'),
45018
+ 'boleto', v_line -> 'boleto',
45019
+ 'card', v_line -> 'card'
45020
+ ));
45021
+
45022
+ SELECT * INTO v_existing FROM public.plg_financial_bank_statement_lines
45023
+ WHERE tenant_id = p_tenant AND external_source = v_source.provider AND external_id = v_line ->> 'externalId';
45024
+
45025
+ IF NOT FOUND THEN
45026
+ INSERT INTO public.plg_financial_bank_statement_lines (
45027
+ tenant_id, unit_id, bank_account_id, external_source, external_id, posted_on, direction, amount,
45028
+ description, counterparty_name, counterparty_document, document, code, category, transaction_type,
45029
+ provider_deleted, raw_payload, metadata
45030
+ ) VALUES (
45031
+ p_tenant, v_source.unit_id, v_source.account_id, v_source.provider, v_line ->> 'externalId', v_day,
45032
+ v_line ->> 'direction', v_amount,
45033
+ nullif(v_line ->> 'description', ''), nullif(v_line #>> '{counterparty,name}', ''),
45034
+ nullif(v_line #>> '{counterparty,document}', ''), nullif(v_line ->> 'reference', ''),
45035
+ nullif(v_line ->> 'code', ''), nullif(v_line ->> 'category', ''),
45036
+ coalesce(v_method, upper(v_line ->> 'direction')),
45037
+ false, coalesce(v_line -> 'raw', '{}'::jsonb), v_meta
45038
+ );
45039
+ v_inserted := v_inserted + 1;
45040
+ ELSE
45041
+ UPDATE public.plg_financial_bank_statement_lines l SET
45042
+ -- Money, day and direction move only while nobody has reconciled the line.
45043
+ posted_on = CASE WHEN l.reconciled_at IS NULL THEN v_day ELSE l.posted_on END,
45044
+ amount = CASE WHEN l.reconciled_at IS NULL THEN v_amount ELSE l.amount END,
45045
+ direction = CASE WHEN l.reconciled_at IS NULL THEN v_line ->> 'direction' ELSE l.direction END,
45046
+ description = coalesce(nullif(v_line ->> 'description', ''), l.description),
45047
+ counterparty_name = coalesce(nullif(v_line #>> '{counterparty,name}', ''), l.counterparty_name),
45048
+ counterparty_document = coalesce(nullif(v_line #>> '{counterparty,document}', ''), l.counterparty_document),
45049
+ document = coalesce(nullif(v_line ->> 'reference', ''), l.document),
45050
+ code = coalesce(nullif(v_line ->> 'code', ''), l.code),
45051
+ category = coalesce(nullif(v_line ->> 'category', ''), l.category),
45052
+ transaction_type = coalesce(v_method, l.transaction_type),
45053
+ provider_deleted = false,
45054
+ raw_payload = coalesce(v_line -> 'raw', l.raw_payload),
45055
+ metadata = (l.metadata - 'withdrawn') || v_meta
45056
+ || CASE WHEN l.reconciled_at IS NOT NULL AND (l.amount <> v_amount OR l.posted_on <> v_day OR l.direction <> v_line ->> 'direction')
45057
+ THEN jsonb_build_object('revisedAfterReconciliation', jsonb_build_object(
45058
+ 'amount', v_amount, 'postedOn', v_day, 'direction', v_line ->> 'direction', 'at', now()))
45059
+ ELSE '{}'::jsonb END,
45060
+ updated_at = now()
45061
+ WHERE l.id = v_existing.id;
45062
+ v_updated := v_updated + 1;
45063
+ END IF;
45064
+ END LOOP;
45065
+
45066
+ RETURN jsonb_build_object('inserted', v_inserted, 'updated', v_updated);
45067
+ END
45068
+ $$;
45069
+
45070
+ CREATE OR REPLACE FUNCTION public.financial_statement_lines_withdraw(p_tenant uuid, p_source uuid, p_external_ids text[], p_reason text)
45071
+ RETURNS integer
45072
+ LANGUAGE plpgsql SECURITY DEFINER
45073
+ SET search_path TO ''
45074
+ AS $$
45075
+ DECLARE
45076
+ v_source public.plg_financial_account_statement_sources%ROWTYPE;
45077
+ v_count integer;
45078
+ BEGIN
45079
+ SELECT * INTO v_source FROM public.plg_financial_account_statement_sources
45080
+ WHERE id = p_source AND tenant_id = p_tenant;
45081
+ IF NOT FOUND THEN
45082
+ RAISE EXCEPTION 'financial_statement_lines_withdraw: source % is not of tenant %', p_source, p_tenant USING ERRCODE = 'P0002';
45083
+ END IF;
45084
+ UPDATE public.plg_financial_bank_statement_lines l
45085
+ SET provider_deleted = true,
45086
+ metadata = l.metadata || jsonb_build_object('withdrawn', jsonb_build_object('reason', p_reason, 'at', now())),
45087
+ updated_at = now()
45088
+ WHERE l.tenant_id = p_tenant AND l.external_source = v_source.provider
45089
+ AND l.bank_account_id = v_source.account_id
45090
+ AND l.external_id = ANY (p_external_ids) AND NOT l.provider_deleted;
45091
+ GET DIAGNOSTICS v_count = ROW_COUNT;
45092
+ RETURN v_count;
45093
+ END
45094
+ $$;
45095
+
45096
+ -- The lines here for a source in a window, withdrawn ones left out: what the
45097
+ -- service compares with the provider to find the ones the bank took back.
45098
+ CREATE OR REPLACE FUNCTION public.financial_statement_lines_known(p_tenant uuid, p_source uuid, p_from date, p_to date)
45099
+ RETURNS SETOF text
45100
+ LANGUAGE sql STABLE SECURITY DEFINER
45101
+ SET search_path TO ''
45102
+ AS $$
45103
+ SELECT l.external_id
45104
+ FROM public.plg_financial_bank_statement_lines l
45105
+ JOIN public.plg_financial_account_statement_sources s
45106
+ ON s.id = p_source AND s.tenant_id = p_tenant
45107
+ WHERE l.tenant_id = p_tenant AND l.external_source = s.provider AND l.bank_account_id = s.account_id
45108
+ AND l.posted_on BETWEEN p_from AND p_to AND NOT l.provider_deleted
45109
+ $$;
45110
+
45111
+ CREATE OR REPLACE FUNCTION public.financial_statement_source_report(
45112
+ p_tenant uuid, p_source uuid, p_status text, p_error text, p_details jsonb
45113
+ ) RETURNS void
45114
+ LANGUAGE plpgsql SECURITY DEFINER
45115
+ SET search_path TO ''
45116
+ AS $$
45117
+ BEGIN
45118
+ IF p_status NOT IN ('ok', 'error', 'pending', 'revoked') THEN
45119
+ RAISE EXCEPTION 'financial_statement_source_report: unknown status %', p_status USING ERRCODE = '22023';
45120
+ END IF;
45121
+ UPDATE public.plg_financial_account_statement_sources s
45122
+ SET last_sync_status = p_status,
45123
+ last_sync_error = CASE WHEN p_status = 'error' THEN left(p_error, 500) ELSE NULL END,
45124
+ last_synced_at = CASE WHEN p_status = 'ok' THEN now() ELSE s.last_synced_at END,
45125
+ metadata = s.metadata || jsonb_build_object('connection', coalesce(p_details, '{}'::jsonb)),
45126
+ updated_at = now()
45127
+ WHERE s.id = p_source AND s.tenant_id = p_tenant;
45128
+ IF NOT FOUND THEN
45129
+ RAISE EXCEPTION 'financial_statement_source_report: source % is not of tenant %', p_source, p_tenant USING ERRCODE = 'P0002';
45130
+ END IF;
45131
+ END
45132
+ $$;
45133
+
45134
+ REVOKE ALL ON FUNCTION public.financial_statement_source_get(uuid, uuid) FROM PUBLIC, anon, authenticated;
45135
+ REVOKE ALL ON FUNCTION public.financial_statement_sources_linked(text) FROM PUBLIC, anon, authenticated;
45136
+ REVOKE ALL ON FUNCTION public.financial_statement_source_link(uuid, uuid, text) FROM PUBLIC, anon, authenticated;
45137
+ REVOKE ALL ON FUNCTION public.financial_statement_lines_ingest(uuid, uuid, jsonb) FROM PUBLIC, anon, authenticated;
45138
+ REVOKE ALL ON FUNCTION public.financial_statement_lines_withdraw(uuid, uuid, text[], text) FROM PUBLIC, anon, authenticated;
45139
+ REVOKE ALL ON FUNCTION public.financial_statement_lines_known(uuid, uuid, date, date) FROM PUBLIC, anon, authenticated;
45140
+ REVOKE ALL ON FUNCTION public.financial_statement_source_report(uuid, uuid, text, text, jsonb) FROM PUBLIC, anon, authenticated;
45141
+
45142
+ DO $$
45143
+ BEGIN
45144
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fayz_integration') THEN
45145
+ GRANT USAGE ON SCHEMA public TO fayz_integration;
45146
+ GRANT EXECUTE ON FUNCTION public.financial_statement_source_get(uuid, uuid) TO fayz_integration;
45147
+ GRANT EXECUTE ON FUNCTION public.financial_statement_sources_linked(text) TO fayz_integration;
45148
+ GRANT EXECUTE ON FUNCTION public.financial_statement_source_link(uuid, uuid, text) TO fayz_integration;
45149
+ GRANT EXECUTE ON FUNCTION public.financial_statement_lines_ingest(uuid, uuid, jsonb) TO fayz_integration;
45150
+ GRANT EXECUTE ON FUNCTION public.financial_statement_lines_withdraw(uuid, uuid, text[], text) TO fayz_integration;
45151
+ GRANT EXECUTE ON FUNCTION public.financial_statement_lines_known(uuid, uuid, date, date) TO fayz_integration;
45152
+ GRANT EXECUTE ON FUNCTION public.financial_statement_source_report(uuid, uuid, text, text, jsonb) TO fayz_integration;
45153
+ END IF;
45154
+ END
45155
+ $$;
45156
+ `;
44902
45157
  var MIGRATIONS = [
44903
45158
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
44904
45159
  { id: "001_the_comanda_becomes_a_receivable_when_it_closes", sql: MIGRATION_001_THE_COMANDA_BECOMES_A_RECEIVABLE_WHEN_IT_CLOSES },
@@ -45035,7 +45290,8 @@ var MIGRATIONS = [
45035
45290
  { id: "132_the_statement_source_knows_its_provider_account", sql: MIGRATION_132_THE_STATEMENT_SOURCE_KNOWS_ITS_PROVIDER_ACCOUNT },
45036
45291
  { id: "133_reversing_a_payment_releases_its_bank_line", sql: MIGRATION_133_REVERSING_A_PAYMENT_RELEASES_ITS_BANK_LINE },
45037
45292
  { id: "134_a_bill_knows_who_filed_it", sql: MIGRATION_134_A_BILL_KNOWS_WHO_FILED_IT },
45038
- { id: "135_a_bill_and_its_lines_are_written_together", sql: MIGRATION_135_A_BILL_AND_ITS_LINES_ARE_WRITTEN_TOGETHER }
45293
+ { id: "135_a_bill_and_its_lines_are_written_together", sql: MIGRATION_135_A_BILL_AND_ITS_LINES_ARE_WRITTEN_TOGETHER },
45294
+ { id: "136_a_bank_statement_arrives_from_the_service", sql: MIGRATION_136_A_BANK_STATEMENT_ARRIVES_FROM_THE_SERVICE }
45039
45295
  ];
45040
45296
  var ACQUIRERS = ["stone", "cielo", "pagbank", "mercadopago"];
45041
45297
  var LOGOS = "https://assets.fayz.ai/connectors";