@fayz-ai/db 0.13.0 → 0.14.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/canonical-schema.json +106 -23
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/schema/spine.d.ts +42 -37
- package/dist/schema/spine.d.ts.map +1 -1
- package/migrations/000_baseline.sql +29667 -0
- package/migrations/001_the_tenant_has_apps.sql +314 -0
- package/migrations/002_the_spine_drops_the_vendor_and_the_dead.sql +129 -0
- package/migrations/003_the_role_rpcs_finish_and_the_mirrors_go.sql +135 -0
- package/migrations/004_the_projections_get_a_successor.sql +312 -0
- package/migrations/005_the_archetypes_anchor_to_the_unit.sql +90 -0
- package/migrations/006_the_helpers_read_the_source.sql +153 -0
- package/migrations/007_the_compat_machinery_leaves_with_the_tables.sql +77 -0
- package/migrations/008_the_switchers_see_across_accounts.sql +150 -0
- package/migrations/009_the_compat_aliases_nobody_adopted.sql +57 -0
- package/migrations/010_the_new_tenant_is_born_with_its_trade.sql +201 -0
- package/migrations/011_leadcontrol_joins_the_family.sql +116 -0
- package/migrations/012_chefcontrol_wears_its_own_mark.sql +28 -0
- package/migrations/013_the_workspace_switch_reaches_the_other_half.sql +156 -0
- package/migrations/014_the_chooser_can_read_what_it_offers.sql +91 -0
- package/migrations/015_the_unit_bindings_belong_to_one_workspace.sql +86 -0
- package/migrations/016_the_rule_stops_asking_every_row.sql +543 -0
- package/migrations/017_the_pin_is_checked_against_the_right_workspace.sql +139 -0
- package/migrations/018_the_bootstrap_says_what_you_reach.sql +114 -0
- package/migrations/019_distributing_one_row_stops_costing_the_table.sql +245 -0
- package/migrations/020_the_agenda_asks_where_someone_works.sql +81 -0
- package/migrations/022_the_line_says_which_document_it_belongs_to.sql +109 -0
- package/package.json +1 -1
- package/migrations/0000_legacy_quarantine.sql +0 -86
- package/migrations/0000b_straggler_sweep.sql +0 -37
- package/migrations/000_core_v1_convert.sql +0 -343
- package/migrations/001_core.sql +0 -372
- package/migrations/002_rls_user_tenant_ids.sql +0 -64
- package/migrations/003_invitations.sql +0 -17
- package/migrations/004_archetypes.sql +0 -343
- package/migrations/005_locations_archetype.sql +0 -18
- package/migrations/006_archetype_rls.sql +0 -74
- package/migrations/007_archetype_grants.sql +0 -4
- package/migrations/008_grants.sql +0 -5
- package/migrations/009_anon_hardening.sql +0 -19
- package/migrations/010_migration_ledger.sql +0 -46
- package/migrations/011_anon_write_revoke.sql +0 -12
- package/migrations/012_rename_migration_ledger.sql +0 -61
- package/migrations/013_sequences_rls.sql +0 -22
- package/migrations/014_plan_entitlements.sql +0 -28
- package/migrations/015_agent_guard.sql +0 -108
- package/migrations/016_agent_guard_actor.sql +0 -102
- package/migrations/017_core_addresses.sql +0 -115
- package/migrations/018_global_search.sql +0 -482
- package/migrations/019_tenant_members_person_id.sql +0 -20
- package/migrations/020_fayz_projects.sql +0 -63
- package/migrations/021_tenant_roles.sql +0 -74
- package/migrations/022_invite_acceptance.sql +0 -91
- package/migrations/023_team_visibility.sql +0 -75
- package/migrations/024_analytics_engine.sql +0 -317
- package/migrations/025_created_by.sql +0 -75
- package/migrations/026_audit_trail.sql +0 -73
- package/migrations/027_domain_events.sql +0 -266
- package/migrations/028_tenant_scoped_token.sql +0 -127
- package/migrations/029_connections.sql +0 -186
- package/migrations/030_effect_idempotency.sql +0 -159
- package/migrations/031_sync_run_message.sql +0 -39
- package/migrations/032_connection_secrets.sql +0 -227
- package/migrations/033_sync_schedule.sql +0 -651
- package/migrations/034_custom_fields.sql +0 -55
- package/migrations/035_field_registry.sql +0 -148
- package/migrations/036_analytics_run_batch.sql +0 -84
- package/migrations/037_sync_tick_one_at_a_time.sql +0 -256
- package/migrations/038_onboarding_responses.sql +0 -103
- package/migrations/039_unit_tree.sql +0 -270
- package/migrations/040_resource_grants.sql +0 -474
- package/migrations/041_scoped_columns.sql +0 -192
- package/migrations/042_unit_scope_policies.sql +0 -145
- package/migrations/043_view_invoker.sql +0 -90
- package/migrations/044_unit_member_facts.sql +0 -47
- package/migrations/045_unit_entry.sql +0 -236
- package/migrations/046_membership_visible_to_members.sql +0 -85
- package/migrations/047_tasks.sql +0 -266
- package/migrations/048_every_login_is_a_person.sql +0 -190
- package/migrations/049_bookable_people.sql +0 -126
- package/migrations/050_authz_core.sql +0 -230
- package/migrations/051_authz_helpers.sql +0 -330
- package/migrations/052_authz_fanout.sql +0 -253
- package/migrations/053_authz_catalog_templates.sql +0 -402
- package/migrations/060_tenancy_seam.sql +0 -132
- package/migrations/061_create_tenant.sql +0 -125
- package/migrations/062_invitations.sql +0 -329
- package/migrations/063_legacy_tenancy_retired.sql +0 -568
- package/migrations/064_agent_guard_authz.sql +0 -118
- package/migrations/064b_supersede_unit_scope.sql +0 -205
- package/migrations/065_scaffold_sweep.sql +0 -139
- package/migrations/066_shadow_report.sql +0 -122
- package/migrations/070_permission_catalog.sql +0 -205
- package/migrations/071_v1_permission_map.sql +0 -266
- package/migrations/072_role_rpcs.sql +0 -126
- package/migrations/073_legacy_rbac_retire.sql +0 -471
- package/migrations/075_company_legal_entities.sql +0 -169
- package/migrations/076_tenant_settings.sql +0 -251
- package/migrations/080_people_roles.sql +0 -269
- package/migrations/081_people_identity.sql +0 -271
- package/migrations/082_people_membership_link.sql +0 -142
- package/migrations/083_people_data_migration.sql +0 -75
- package/migrations/084_people_registrations_tokens.sql +0 -282
- package/migrations/085_catalog_products.sql +0 -333
- package/migrations/086_catalog_pricing.sql +0 -194
- package/migrations/087_catalog_packages.sql +0 -134
- package/migrations/090_orders_venda.sql +0 -166
- package/migrations/091_orders_scaffold.sql +0 -92
- package/migrations/095_config_field_rules.sql +0 -261
- package/migrations/096_audit_append_only.sql +0 -256
- package/migrations/097_sequences_authorized.sql +0 -111
- package/migrations/098_holidays.sql +0 -80
- package/migrations/099_access_grants.sql +0 -293
- package/migrations/100_migration_schema.sql +0 -217
- package/migrations/101_migration_allowlist.sql +0 -202
- package/migrations/102_migration_rpcs.sql +0 -933
- package/migrations/103_migration_reconcile_ponr.sql +0 -132
- package/migrations/105_policies_to_authenticated.sql +0 -71
- package/migrations/106_function_search_path.sql +0 -55
- package/migrations/107_core_table_prefix.sql +0 -361
- package/migrations/108_authz_write_guards.sql +0 -149
- package/migrations/109_catalog_migration_promoted_columns.sql +0 -126
- package/migrations/110_people_kind_vocabulary.sql +0 -120
- package/migrations/111_migration_excluded_is_a_decision.sql +0 -259
- package/migrations/111_replacing_a_view_keeps_its_dependents.sql +0 -132
- package/migrations/112_money_precision_standard.sql +0 -132
- package/migrations/113_addresses_units_companies.sql +0 -114
- package/migrations/114_migrations_ledger_immutable.sql +0 -65
- package/migrations/115_registration_honours_field_rules.sql +0 -172
- package/migrations/116_field_rules_check_needs_a_tenant.sql +0 -72
- package/migrations/117_authz_reconciliation_schedule.sql +0 -82
- package/migrations/118_migration_catalog_writers.sql +0 -450
- package/migrations/119_migration_config_writers.sql +0 -193
- package/migrations/120_migration_facet_writer.sql +0 -139
- package/migrations/121_migration_financial_writers.sql +0 -294
- package/migrations/122_migration_inventory_writers.sql +0 -245
- package/migrations/123_migration_order_item_writer.sql +0 -144
- package/migrations/124_migration_ledger_writers.sql +0 -170
- package/migrations/125_migration_authz_people_writers.sql +0 -205
- package/migrations/126_owner_scoping_is_configuration.sql +0 -247
- package/migrations/127_scaffold_audit_hook.sql +0 -98
- package/migrations/128_shadow_tables_enforced.sql +0 -93
- package/migrations/129_order_kind_terminal_states.sql +0 -57
- package/migrations/130_every_login_is_a_person.sql +0 -196
- package/migrations/131_migration_invoice_split.sql +0 -127
- package/migrations/132_audit_prune_scheduled.sql +0 -28
- package/migrations/133_unit_is_a_session_property.sql +0 -155
- package/migrations/134_field_rules_on_read.sql +0 -341
- package/migrations/135_distribution_polarity.sql +0 -158
- package/migrations/136_unit_id_means_locality.sql +0 -358
- package/migrations/137_vendor_named_spine_objects.sql +0 -126
- package/migrations/138_grants_inherit_down_the_tree.sql +0 -178
- package/migrations/139_distributable_is_a_scaffold_option.sql +0 -109
- package/migrations/140_masking_is_a_scaffold_option.sql +0 -85
- package/migrations/141_renamed_objects_keep_a_compat_name.sql +0 -192
- package/migrations/142_a_person_has_addresses.sql +0 -212
- package/migrations/143_a_professional_has_a_rate.sql +0 -75
- package/migrations/144_a_product_can_be_sold_in_sizes.sql +0 -57
- package/migrations/145_the_app_says_which_tenant_it_is_in.sql +0 -123
- package/migrations/146_products_stock_has_one_writer.sql +0 -67
- package/migrations/147_every_archetype_has_a_kind_registry.sql +0 -388
- package/migrations/148_the_legacy_archetype_stops_being_writable.sql +0 -154
- package/migrations/149_the_event_log_gets_a_consumer.sql +0 -331
- package/migrations/150_the_app_is_configured_in_the_database.sql +0 -285
- package/migrations/151_stock_balance_is_an_archetype.sql +0 -144
- package/migrations/152_a_file_is_an_archetype.sql +0 -222
- package/migrations/153_a_plugin_calls_a_service_through_one_door.sql +0 -199
- package/migrations/154_the_plan_is_a_ceiling.sql +0 -258
- package/migrations/155_a_tenant_can_leave.sql +0 -271
- package/migrations/156_the_service_door_has_a_server_side.sql +0 -335
- package/migrations/157_nobody_creates_objects_in_public.sql +0 -74
- package/migrations/158_the_registry_owns_the_status_vocabulary.sql +0 -153
- package/migrations/159_the_plan_catalogue_gets_its_first_rows.sql +0 -132
- package/migrations/160_the_vertical_decides_what_starts_on.sql +0 -246
- package/migrations/161_a_tenant_leaves_with_its_data.sql +0 -363
- package/migrations/162_every_name_has_one_creator_for_real.sql +0 -126
- package/migrations/163_a_species_nobody_declared_goes_to_quarantine.sql +0 -184
- package/migrations/164_the_sequence_table_joins_the_register.sql +0 -26
- package/migrations/165_a_view_is_not_a_way_in.sql +0 -90
- package/migrations/166_the_platform_billing_leftovers_go.sql +0 -72
- package/migrations/167_a_view_gets_back_what_its_table_grants.sql +0 -112
- package/migrations/168_the_grant_census_stops_crying_wolf.sql +0 -33
- package/migrations/169_the_census_asks_postgres_instead_of_guessing.sql +0 -45
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
-- 119_migration_config_writers.sql
|
|
2
|
-
--
|
|
3
|
-
-- FieldRules and holidays get their writers; the QR rows get a decision. #144 / #148.
|
|
4
|
-
--
|
|
5
|
-
-- 091 pointed both at a `config.*` schema that never shipped — the tables are in
|
|
6
|
-
-- `public` — and gave neither a writer, so every rule and every holiday landed
|
|
7
|
-
-- `pending`.
|
|
8
|
-
--
|
|
9
|
-
-- THE THREE qr_access_* ROWS ARE DEFERRED, and the reason is worth stating
|
|
10
|
-
-- because the allowlist's own note promised something that cannot be delivered:
|
|
11
|
-
-- "V1 issued codes become access_grants on the 'qr' channel with a freshly
|
|
12
|
-
-- minted secret". Two problems with that.
|
|
13
|
-
--
|
|
14
|
-
-- 1. `public.access_grants` requires a unit_id, a role_id and an expiry. V1's
|
|
15
|
-
-- `qr_access_configs` carries `enabled`, `hours_valid` and
|
|
16
|
-
-- `scan_window_minutes` — tenant-level and opaque. The unit and the role a
|
|
17
|
-
-- V2 grant is FOR are not in the source, so a migrated row would have to
|
|
18
|
-
-- invent them.
|
|
19
|
-
-- 2. A freshly minted secret invalidates every QR code already printed and
|
|
20
|
-
-- stuck to a wall. If the codes stop working either way, importing them
|
|
21
|
-
-- creates rows nobody can use rather than preserving access.
|
|
22
|
-
--
|
|
23
|
-
-- So a tenant re-issues its access grants in V2, which takes a minute per unit
|
|
24
|
-
-- and is honest about what changed. Sessions were never going to migrate — they
|
|
25
|
-
-- expire — and the allowlist already said so.
|
|
26
|
-
--
|
|
27
|
-
-- V1 `visibility` -> V2 `state`: `required_field_rules` is a list of fields that
|
|
28
|
-
-- ARE required, so its writer_args pin state='required'; `field_visibility_rules`
|
|
29
|
-
-- carries the vocabulary and is translated, with anything outside it quarantined
|
|
30
|
-
-- rather than defaulted (#148).
|
|
31
|
-
--
|
|
32
|
-
-- Replay-safe: `_allow` upserts under the documented unlock, writers are
|
|
33
|
-
-- CREATE OR REPLACE.
|
|
34
|
-
|
|
35
|
-
ALTER TABLE migration.allowlist DROP CONSTRAINT IF EXISTS allowlist_writer_check;
|
|
36
|
-
ALTER TABLE migration.allowlist ADD CONSTRAINT allowlist_writer_check
|
|
37
|
-
CHECK (writer IS NULL OR writer IN ('person', 'unit', 'category', 'product', 'order', 'invoice',
|
|
38
|
-
'price_table', 'price_table_item', 'package', 'package_item',
|
|
39
|
-
'field_rule', 'holiday'));
|
|
40
|
-
|
|
41
|
-
DO $unlock$
|
|
42
|
-
BEGIN
|
|
43
|
-
PERFORM set_config('migration.allowlist_unlock', 'on', true);
|
|
44
|
-
|
|
45
|
-
PERFORM migration._allow('required_field_rules', 'public.field_rules', 'config', 'field_rule', '{"state":"required"}', '{}',
|
|
46
|
-
'{"uuid":"id","context":"context","entity":"reference","field_key":"field_key","unit_id":"unit_id@companies"}',
|
|
47
|
-
'PRD 07 / #144: a row here IS a required field, so the state is the writer''s argument rather than a column (#109).');
|
|
48
|
-
|
|
49
|
-
PERFORM migration._allow('field_visibility_rules', 'public.field_rules', 'config', 'field_rule', '{}', '{}',
|
|
50
|
-
'{"uuid":"id","context":"context","entity":"reference","field_key":"field_key","visibility":"state","unit_id":"unit_id@companies"}',
|
|
51
|
-
'PRD 07 / #144: folded into FieldRules; `visibility` is translated to the 3-state vocabulary and quarantined when it is outside it (#109).');
|
|
52
|
-
|
|
53
|
-
PERFORM migration._allow('holidays', 'public.holidays', 'config', 'holiday', '{}', '{}',
|
|
54
|
-
'{"uuid":"id","date":"date","name":"name","recurring":"recurring","unit_id":"unit_id@companies","units":"jsonb:units"}',
|
|
55
|
-
'PRD 07: tenant/unit holidays.');
|
|
56
|
-
|
|
57
|
-
PERFORM migration._allow('central_holidays', 'public.holidays', 'config', 'holiday', '{"kind":"national"}', '{}',
|
|
58
|
-
'{"uuid":"id","date":"date","name":"name"}',
|
|
59
|
-
'National calendar; platform-level rows land as tenant-wide holidays of kind national (#109).');
|
|
60
|
-
|
|
61
|
-
DELETE FROM migration.allowlist WHERE source_table IN ('qr_access_configs', 'qr_access_grants', 'qr_access_sessions');
|
|
62
|
-
END $unlock$;
|
|
63
|
-
|
|
64
|
-
INSERT INTO migration.excluded_tables (source_table, reason) VALUES
|
|
65
|
-
('qr_access_configs', 'Deferred: public.access_grants needs a unit, a role and an expiry; V1 qr_access_configs is tenant-level and opaque (enabled / hours_valid / scan_window_minutes), so a migrated grant would have to invent the unit and role it is FOR. A tenant re-issues its access grants in V2 (#109).'),
|
|
66
|
-
('qr_access_grants', 'Deferred: issued codes are credentials. Migrating them means minting a new secret, which invalidates every QR already printed — so the row would be unusable either way, and importing it creates dead rows rather than preserving access (#109).'),
|
|
67
|
-
('qr_access_sessions', 'Not migrated: live sessions expire. 091 already said so; this makes it a recorded decision instead of a pending row (#109).')
|
|
68
|
-
ON CONFLICT (source_table) DO UPDATE SET reason = EXCLUDED.reason;
|
|
69
|
-
|
|
70
|
-
-- ── field rules ─────────────────────────────────────────────────────────
|
|
71
|
-
CREATE OR REPLACE FUNCTION migration.upsert_field_rule(p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb)
|
|
72
|
-
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
|
|
73
|
-
DECLARE
|
|
74
|
-
v_state_raw text := coalesce(p_args ->> 'state', nullif(btrim(lower(coalesce(p_row ->> 'state', ''))), ''));
|
|
75
|
-
v_state text := CASE v_state_raw
|
|
76
|
-
WHEN 'required' THEN 'required'
|
|
77
|
-
WHEN 'visible' THEN 'visible'
|
|
78
|
-
WHEN 'hidden' THEN 'hidden'
|
|
79
|
-
-- the V1 vocabulary, translated
|
|
80
|
-
WHEN 'optional' THEN 'visible'
|
|
81
|
-
WHEN 'shown' THEN 'visible'
|
|
82
|
-
WHEN 'visivel' THEN 'visible'
|
|
83
|
-
WHEN 'oculto' THEN 'hidden'
|
|
84
|
-
WHEN 'obrigatorio' THEN 'required'
|
|
85
|
-
END;
|
|
86
|
-
v_context text := nullif(btrim(coalesce(p_row ->> 'context', '')), '');
|
|
87
|
-
v_ref text := lower(nullif(btrim(coalesce(p_row ->> 'reference', '')), ''));
|
|
88
|
-
v_key text := nullif(btrim(coalesce(p_row ->> 'field_key', '')), '');
|
|
89
|
-
v_existing_tenant uuid;
|
|
90
|
-
v_created boolean := false;
|
|
91
|
-
v_standalone boolean := migration._standalone();
|
|
92
|
-
BEGIN
|
|
93
|
-
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
94
|
-
IF p_id IS NULL OR v_context IS NULL OR v_ref IS NULL OR v_key IS NULL THEN
|
|
95
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'a field rule needs an id, a context, a reference and a field key');
|
|
96
|
-
END IF;
|
|
97
|
-
IF v_state IS NULL THEN
|
|
98
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value',
|
|
99
|
-
'reason', format('unknown visibility %L (expected hidden|visible|required)', coalesce(v_state_raw, '<null>')));
|
|
100
|
-
END IF;
|
|
101
|
-
-- the context is an FK: an unknown one is a broken reference, not a new context
|
|
102
|
-
IF NOT EXISTS (SELECT 1 FROM public.field_rule_contexts c WHERE c.key = v_context) THEN
|
|
103
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',
|
|
104
|
-
'reason', format('context %L is not registered in public.field_rule_contexts', v_context));
|
|
105
|
-
END IF;
|
|
106
|
-
-- One rule per (tenant, unit, context, reference, field_key, role). A V1 row
|
|
107
|
-
-- with a different id for a scope that already has a rule is a genuine
|
|
108
|
-
-- conflict — two sources disagreeing about the same field — so it quarantines
|
|
109
|
-
-- with that sentence rather than leaking `duplicate key value violates unique
|
|
110
|
-
-- constraint "field_rules_one_per_scope"` through the generic handler.
|
|
111
|
-
IF EXISTS (SELECT 1 FROM public.field_rules r
|
|
112
|
-
WHERE r.tenant_id = p_tenant AND r.id <> p_id
|
|
113
|
-
AND r.unit_id IS NOT DISTINCT FROM (p_row ->> 'unit_id')::uuid
|
|
114
|
-
AND r.context = v_context AND r.reference = v_ref AND r.field_key = v_key
|
|
115
|
-
AND r.role_key IS NOT DISTINCT FROM nullif(btrim(coalesce(p_row ->> 'role_key', '')), '')) THEN
|
|
116
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state',
|
|
117
|
-
'reason', format('a field rule already exists for %s/%s/%s in this scope — two sources disagree about the same field', v_context, v_ref, v_key));
|
|
118
|
-
END IF;
|
|
119
|
-
SELECT tenant_id INTO v_existing_tenant FROM public.field_rules WHERE id = p_id;
|
|
120
|
-
IF v_existing_tenant IS NOT NULL AND v_existing_tenant <> p_tenant THEN
|
|
121
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant', 'reason', format('field rule %s already exists in another tenant', p_id));
|
|
122
|
-
END IF;
|
|
123
|
-
IF v_existing_tenant IS NULL THEN
|
|
124
|
-
INSERT INTO public.field_rules (id, tenant_id, unit_id, context, reference, field_key, state, sort_order)
|
|
125
|
-
VALUES (p_id, p_tenant, (p_row ->> 'unit_id')::uuid, v_context, v_ref, v_key, v_state,
|
|
126
|
-
coalesce((p_row ->> 'sort_order')::integer, 0));
|
|
127
|
-
v_created := true;
|
|
128
|
-
ELSE
|
|
129
|
-
UPDATE public.field_rules SET
|
|
130
|
-
unit_id = CASE WHEN p_row ? 'unit_id' THEN (p_row ->> 'unit_id')::uuid ELSE unit_id END,
|
|
131
|
-
context = v_context, reference = v_ref, field_key = v_key, state = v_state
|
|
132
|
-
WHERE id = p_id;
|
|
133
|
-
END IF;
|
|
134
|
-
IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.field_rules', p_id, jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END)); END IF;
|
|
135
|
-
RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);
|
|
136
|
-
END $$;
|
|
137
|
-
|
|
138
|
-
-- ── holidays ────────────────────────────────────────────────────────────
|
|
139
|
-
CREATE OR REPLACE FUNCTION migration.upsert_holiday(p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb)
|
|
140
|
-
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
|
|
141
|
-
DECLARE
|
|
142
|
-
v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);
|
|
143
|
-
v_kind text := coalesce(p_args ->> 'kind', 'company');
|
|
144
|
-
v_date date;
|
|
145
|
-
v_name text := nullif(btrim(coalesce(p_row ->> 'name', '')), '');
|
|
146
|
-
v_existing_tenant uuid;
|
|
147
|
-
v_created boolean := false;
|
|
148
|
-
v_standalone boolean := migration._standalone();
|
|
149
|
-
BEGIN
|
|
150
|
-
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
151
|
-
BEGIN
|
|
152
|
-
v_date := (p_row ->> 'date')::date;
|
|
153
|
-
EXCEPTION WHEN OTHERS THEN
|
|
154
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value', 'reason', format('%L is not a date', p_row ->> 'date'));
|
|
155
|
-
END;
|
|
156
|
-
IF p_id IS NULL OR v_date IS NULL OR v_name IS NULL THEN
|
|
157
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'a holiday needs an id, a date and a name');
|
|
158
|
-
END IF;
|
|
159
|
-
IF v_kind NOT IN ('national', 'state', 'city', 'company') THEN
|
|
160
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value', 'reason', format('unknown holiday kind %L', v_kind));
|
|
161
|
-
END IF;
|
|
162
|
-
SELECT tenant_id INTO v_existing_tenant FROM public.holidays WHERE id = p_id;
|
|
163
|
-
IF v_existing_tenant IS NOT NULL AND v_existing_tenant <> p_tenant THEN
|
|
164
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant', 'reason', format('holiday %s already exists in another tenant', p_id));
|
|
165
|
-
END IF;
|
|
166
|
-
IF v_existing_tenant IS NULL THEN
|
|
167
|
-
INSERT INTO public.holidays (id, tenant_id, unit_id, date, name, kind, recurring, description, is_active, metadata)
|
|
168
|
-
VALUES (p_id, p_tenant, (p_row ->> 'unit_id')::uuid, v_date, v_name, v_kind,
|
|
169
|
-
coalesce((p_row ->> 'recurring')::boolean, false), p_row ->> 'description',
|
|
170
|
-
coalesce((p_row ->> 'is_active')::boolean, true), v_meta);
|
|
171
|
-
v_created := true;
|
|
172
|
-
ELSE
|
|
173
|
-
UPDATE public.holidays SET
|
|
174
|
-
unit_id = CASE WHEN p_row ? 'unit_id' THEN (p_row ->> 'unit_id')::uuid ELSE unit_id END,
|
|
175
|
-
date = v_date, name = v_name, kind = v_kind,
|
|
176
|
-
recurring = CASE WHEN p_row ? 'recurring' THEN coalesce((p_row ->> 'recurring')::boolean, false) ELSE recurring END,
|
|
177
|
-
description = CASE WHEN p_row ? 'description' THEN p_row ->> 'description' ELSE description END,
|
|
178
|
-
metadata = migration._merge_meta(metadata, v_meta)
|
|
179
|
-
WHERE id = p_id;
|
|
180
|
-
END IF;
|
|
181
|
-
IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.holidays', p_id, jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END)); END IF;
|
|
182
|
-
RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);
|
|
183
|
-
END $$;
|
|
184
|
-
|
|
185
|
-
DO $$
|
|
186
|
-
DECLARE r record;
|
|
187
|
-
BEGIN
|
|
188
|
-
FOR r IN SELECT p.oid::regprocedure AS sig FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
189
|
-
WHERE n.nspname = 'migration' AND p.proname IN ('upsert_field_rule', 'upsert_holiday') LOOP
|
|
190
|
-
EXECUTE format('REVOKE ALL ON FUNCTION %s FROM public, anon, authenticated', r.sig);
|
|
191
|
-
EXECUTE format('GRANT EXECUTE ON FUNCTION %s TO service_role', r.sig);
|
|
192
|
-
END LOOP;
|
|
193
|
-
END $$;
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
-- 120_migration_facet_writer.sql
|
|
2
|
-
--
|
|
3
|
-
-- One writer for every facet, instead of twenty that each create nothing. #148.
|
|
4
|
-
--
|
|
5
|
-
-- A large share of the allowlist is not "a table that becomes a table" — it is a
|
|
6
|
-
-- FACET: a V1 side-table that enriches a row which already migrated. Barcodes,
|
|
7
|
-
-- sale info, fiscal info, sale channels, asset and ingredient info all hang off
|
|
8
|
-
-- `products`; general_settings, modules, software profiles and domains all hang
|
|
9
|
-
-- off the tenant. `_upsert_row_impl` had no shape for that, so all of them landed
|
|
10
|
-
-- `pending` and each would otherwise need a near-identical writer whose only real
|
|
11
|
-
-- job is `metadata = metadata || <mapped jsonb>`.
|
|
12
|
-
--
|
|
13
|
-
-- `upsert_facet` is that shape, parameterised from the allowlist:
|
|
14
|
-
--
|
|
15
|
-
-- {"target":"public.products"} -- parent id comes from the
|
|
16
|
-
-- mapped `id` (an @ref the
|
|
17
|
-
-- ledger resolved)
|
|
18
|
-
-- {"target":"public.tenants","parent":"tenant", -- parent IS the batch's
|
|
19
|
-
-- "column":"settings"} tenant; merge into
|
|
20
|
-
-- `settings`, not metadata
|
|
21
|
-
--
|
|
22
|
-
-- It refuses rather than creates. A facet whose parent is not in the destination
|
|
23
|
-
-- is an fk quarantine, not a new row: enriching something that does not exist
|
|
24
|
-
-- would mean inventing the thing being enriched, and the parent's own table is
|
|
25
|
-
-- what owns its creation. That also makes offer ORDER not matter — a facet
|
|
26
|
-
-- offered before its parent quarantines, and the next offer of the same row
|
|
27
|
-
-- (after the parent lands) migrates it.
|
|
28
|
-
--
|
|
29
|
-
-- TWO REFERENCE LISTS ARE DEFERRED. `origins` and `partnerships` are V1 lookup
|
|
30
|
-
-- tables that people POINT AT; 091 mapped them onto `public.people` with a
|
|
31
|
-
-- `jsonb:customer.origin` path and no person reference at all, which cannot be
|
|
32
|
-
-- executed — there is no row to enrich. The values reach V2 inside the person
|
|
33
|
-
-- that references them, which `upsert_person` already carries.
|
|
34
|
-
--
|
|
35
|
-
-- Replay-safe: `_allow` upserts under the documented unlock, writer is
|
|
36
|
-
-- CREATE OR REPLACE, and the merge is idempotent by construction.
|
|
37
|
-
|
|
38
|
-
ALTER TABLE migration.allowlist DROP CONSTRAINT IF EXISTS allowlist_writer_check;
|
|
39
|
-
ALTER TABLE migration.allowlist ADD CONSTRAINT allowlist_writer_check
|
|
40
|
-
CHECK (writer IS NULL OR writer IN ('person', 'unit', 'category', 'product', 'order', 'invoice',
|
|
41
|
-
'price_table', 'price_table_item', 'package', 'package_item',
|
|
42
|
-
'field_rule', 'holiday', 'facet'));
|
|
43
|
-
|
|
44
|
-
CREATE OR REPLACE FUNCTION migration.upsert_facet(p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb)
|
|
45
|
-
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
|
|
46
|
-
DECLARE
|
|
47
|
-
v_target text := p_args ->> 'target';
|
|
48
|
-
v_column text := coalesce(p_args ->> 'column', 'metadata');
|
|
49
|
-
v_parent uuid;
|
|
50
|
-
v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);
|
|
51
|
-
v_found boolean;
|
|
52
|
-
v_standalone boolean := migration._standalone();
|
|
53
|
-
BEGIN
|
|
54
|
-
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
55
|
-
IF v_target IS NULL OR to_regclass(v_target) IS NULL THEN
|
|
56
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state',
|
|
57
|
-
'reason', format('the facet writer needs a target table that exists; got %L', coalesce(v_target, '<null>')));
|
|
58
|
-
END IF;
|
|
59
|
-
-- Nothing mapped: the facet carries no column this contract keeps. Every
|
|
60
|
-
-- migrated row is stamped with its origin (`metadata.migration`), so the test
|
|
61
|
-
-- is "anything BESIDES the marker" — otherwise a facet that maps nothing would
|
|
62
|
-
-- report a write that changed nothing but the provenance of its parent.
|
|
63
|
-
IF (v_meta - 'migration') = '{}'::jsonb THEN
|
|
64
|
-
RETURN jsonb_build_object('status', 'skipped', 'reason', 'the facet maps no column into the destination');
|
|
65
|
-
END IF;
|
|
66
|
-
|
|
67
|
-
IF coalesce(p_args ->> 'parent', 'row') = 'tenant' THEN
|
|
68
|
-
v_parent := p_tenant;
|
|
69
|
-
ELSE
|
|
70
|
-
BEGIN
|
|
71
|
-
v_parent := (p_row ->> 'id')::uuid;
|
|
72
|
-
EXCEPTION WHEN OTHERS THEN v_parent := NULL; END;
|
|
73
|
-
IF v_parent IS NULL THEN
|
|
74
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',
|
|
75
|
-
'reason', format('a facet of %s needs its parent''s id; the mapped reference did not resolve', v_target));
|
|
76
|
-
END IF;
|
|
77
|
-
END IF;
|
|
78
|
-
|
|
79
|
-
-- public.tenants has no tenant_id column — its own id IS the tenant — so the
|
|
80
|
-
-- scope predicate differs by parent kind. Every other target is a tenant-scoped
|
|
81
|
-
-- business table and must be checked in scope, or a facet could enrich a row of
|
|
82
|
-
-- another tenant that happens to share an id.
|
|
83
|
-
IF coalesce(p_args ->> 'parent', 'row') = 'tenant' THEN
|
|
84
|
-
EXECUTE format('SELECT EXISTS (SELECT 1 FROM %s WHERE id = $1)', v_target) INTO v_found USING v_parent;
|
|
85
|
-
ELSE
|
|
86
|
-
EXECUTE format('SELECT EXISTS (SELECT 1 FROM %s WHERE id = $1 AND tenant_id = $2)', v_target)
|
|
87
|
-
INTO v_found USING v_parent, p_tenant;
|
|
88
|
-
END IF;
|
|
89
|
-
IF NOT v_found THEN
|
|
90
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',
|
|
91
|
-
'reason', format('%s %s is not in this tenant yet — a facet enriches a row, it does not create one', v_target, v_parent));
|
|
92
|
-
END IF;
|
|
93
|
-
|
|
94
|
-
IF coalesce(p_args ->> 'parent', 'row') = 'tenant' THEN
|
|
95
|
-
EXECUTE format('UPDATE %s SET %I = migration._merge_meta(%I, $1), updated_at = now() WHERE id = $2', v_target, v_column, v_column)
|
|
96
|
-
USING v_meta, v_parent;
|
|
97
|
-
ELSE
|
|
98
|
-
EXECUTE format('UPDATE %s SET %I = migration._merge_meta(%I, $1), updated_at = now() WHERE id = $2 AND tenant_id = $3',
|
|
99
|
-
v_target, v_column, v_column)
|
|
100
|
-
USING v_meta, v_parent, p_tenant;
|
|
101
|
-
END IF;
|
|
102
|
-
|
|
103
|
-
IF v_standalone THEN PERFORM migration._audit(p_tenant, v_target, v_parent, jsonb_build_object('reason', 'facet merged')); END IF;
|
|
104
|
-
RETURN jsonb_build_object('status', 'migrated', 'target_id', v_parent, 'reason', 'facet merged');
|
|
105
|
-
END $$;
|
|
106
|
-
REVOKE ALL ON FUNCTION migration.upsert_facet(uuid, uuid, jsonb, jsonb) FROM public, anon, authenticated;
|
|
107
|
-
GRANT EXECUTE ON FUNCTION migration.upsert_facet(uuid, uuid, jsonb, jsonb) TO service_role;
|
|
108
|
-
|
|
109
|
-
DO $unlock$
|
|
110
|
-
DECLARE r record;
|
|
111
|
-
BEGIN
|
|
112
|
-
PERFORM set_config('migration.allowlist_unlock', 'on', true);
|
|
113
|
-
|
|
114
|
-
-- facets of a row: the parent's id is the mapped @ref
|
|
115
|
-
FOR r IN SELECT * FROM (VALUES
|
|
116
|
-
('product_barcodes', 'public.products'),
|
|
117
|
-
('product_sale_info', 'public.products'),
|
|
118
|
-
('product_sale_channels', 'public.products'),
|
|
119
|
-
('product_fiscal_info', 'public.products'),
|
|
120
|
-
('product_asset_info', 'public.products'),
|
|
121
|
-
('product_ingredient_info', 'public.products')
|
|
122
|
-
) AS t(src, tgt)
|
|
123
|
-
LOOP
|
|
124
|
-
UPDATE migration.allowlist SET writer = 'facet', writer_args = jsonb_build_object('target', r.tgt)
|
|
125
|
-
WHERE source_table = r.src;
|
|
126
|
-
END LOOP;
|
|
127
|
-
|
|
128
|
-
-- facets of the tenant: general_settings lands in `settings` (it IS the
|
|
129
|
-
-- settings registry #138 reads), the rest describe the tenant and land there too
|
|
130
|
-
UPDATE migration.allowlist SET writer = 'facet', writer_args = jsonb_build_object('target', 'public.tenants', 'parent', 'tenant', 'column', 'settings')
|
|
131
|
-
WHERE source_table IN ('general_settings', 'modules', 'software_profiles', 'software_profile_modules', 'tenant_domains', 'licenses_admin');
|
|
132
|
-
|
|
133
|
-
DELETE FROM migration.allowlist WHERE source_table IN ('origins', 'partnerships');
|
|
134
|
-
END $unlock$;
|
|
135
|
-
|
|
136
|
-
INSERT INTO migration.excluded_tables (source_table, reason) VALUES
|
|
137
|
-
('origins', 'Deferred: a V1 lookup table that clients POINT AT. 091 mapped it onto public.people with a jsonb:customer.origin path and no person reference, which cannot be executed — there is no row to enrich. The value reaches V2 inside the person that references it (#110).'),
|
|
138
|
-
('partnerships', 'Deferred: same shape as origins — a reference list, not a row of public.people (#110).')
|
|
139
|
-
ON CONFLICT (source_table) DO UPDATE SET reason = EXCLUDED.reason;
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
-- 121_migration_financial_writers.sql
|
|
2
|
-
--
|
|
3
|
-
-- The financial reference tables get their writers. #142 / #148.
|
|
4
|
-
--
|
|
5
|
-
-- Every financial row in the allowlist had `writer = NULL`, so accounts, payment
|
|
6
|
-
-- methods, card brands, the chart of accounts and cost centres all landed
|
|
7
|
-
-- `pending`. These are the reference data a tenant's money hangs off — without
|
|
8
|
-
-- them no movement can resolve its account, so they come before the ledger
|
|
9
|
-
-- itself (112).
|
|
10
|
-
--
|
|
11
|
-
-- FOUR MAPPINGS NAMED COLUMNS THAT DO NOT EXIST, the same silent-drop shape as
|
|
12
|
-
-- #198 and the `counterparty_id` bug. Corrected here rather than left for a
|
|
13
|
-
-- writer to ignore:
|
|
14
|
-
--
|
|
15
|
-
-- chart_of_accounts.account_type -> node_type (there is no `kind` column)
|
|
16
|
-
-- cost_centers.description -> dropped (no column; V1's free text has
|
|
17
|
-
-- no home and inventing one for
|
|
18
|
-
-- a cost centre's description is
|
|
19
|
-
-- not worth a JSONB bag)
|
|
20
|
-
-- account_central/bank_accounts.account_type -> kind, and VALIDATED: the
|
|
21
|
-
-- ledger's eight kinds are a closed vocabulary (bank, cash_physical,
|
|
22
|
-
-- cash_session, card_receivable_credit/_debit, card_payable, person, system),
|
|
23
|
-
-- and a V1 account_type outside it quarantines rather than defaulting to
|
|
24
|
-
-- 'bank' — an account posted to the wrong kind puts money in the wrong place
|
|
25
|
-
-- and nothing downstream would notice.
|
|
26
|
-
--
|
|
27
|
-
-- `cash_session_reviewers` becomes a facet of the session (110's writer), which
|
|
28
|
-
-- is what 091 already called it.
|
|
29
|
-
--
|
|
30
|
-
-- Replay-safe: `_allow` upserts under the documented unlock; writers are
|
|
31
|
-
-- CREATE OR REPLACE.
|
|
32
|
-
|
|
33
|
-
ALTER TABLE migration.allowlist DROP CONSTRAINT IF EXISTS allowlist_writer_check;
|
|
34
|
-
ALTER TABLE migration.allowlist ADD CONSTRAINT allowlist_writer_check
|
|
35
|
-
CHECK (writer IS NULL OR writer IN ('person', 'unit', 'category', 'product', 'order', 'invoice',
|
|
36
|
-
'price_table', 'price_table_item', 'package', 'package_item',
|
|
37
|
-
'field_rule', 'holiday', 'facet',
|
|
38
|
-
'fin_account', 'fin_payment_method_type', 'fin_payment_method',
|
|
39
|
-
'fin_card_brand', 'fin_chart_account', 'fin_cost_center'));
|
|
40
|
-
|
|
41
|
-
DO $unlock$
|
|
42
|
-
BEGIN
|
|
43
|
-
PERFORM set_config('migration.allowlist_unlock', 'on', true);
|
|
44
|
-
|
|
45
|
-
UPDATE migration.allowlist SET writer = 'fin_account' WHERE source_table IN ('account_central', 'bank_accounts');
|
|
46
|
-
UPDATE migration.allowlist SET writer = 'fin_payment_method_type' WHERE source_table = 'payment_method_types';
|
|
47
|
-
UPDATE migration.allowlist SET writer = 'fin_payment_method' WHERE source_table = 'payment_methods';
|
|
48
|
-
UPDATE migration.allowlist SET writer = 'fin_card_brand' WHERE source_table = 'card_brands';
|
|
49
|
-
|
|
50
|
-
-- account_type is node_type here; there is no `kind` column on the chart
|
|
51
|
-
UPDATE migration.allowlist SET writer = 'fin_chart_account',
|
|
52
|
-
columns = '{"uuid":"id","code":"code","name":"name","account_type":"node_type","parent_id":"parent_id@chart_of_accounts","active":"is_active"}'::jsonb,
|
|
53
|
-
notes = 'PRD 05. V1 account_type is the node_type of the chart, not a `kind` — the column named in 091 does not exist (#111).'
|
|
54
|
-
WHERE source_table = 'chart_of_accounts';
|
|
55
|
-
|
|
56
|
-
-- a cost centre has code, name and is_active in V2; the V1 description has no home
|
|
57
|
-
UPDATE migration.allowlist SET writer = 'fin_cost_center',
|
|
58
|
-
columns = '{"uuid":"id","code":"code","name":"name","description":"-","active":"is_active"}'::jsonb,
|
|
59
|
-
notes = 'PRD 05. V1 `description` is explicitly not migrated: public.plg_financial_cost_centers has no column for it and a JSONB bag for one free-text field is not worth the drift (#111).'
|
|
60
|
-
WHERE source_table = 'cost_centers';
|
|
61
|
-
|
|
62
|
-
UPDATE migration.allowlist SET writer = 'facet',
|
|
63
|
-
writer_args = jsonb_build_object('target', 'public.plg_financial_cash_register_sessions')
|
|
64
|
-
WHERE source_table = 'cash_session_reviewers';
|
|
65
|
-
END $unlock$;
|
|
66
|
-
|
|
67
|
-
CREATE OR REPLACE FUNCTION migration.upsert_fin_account(p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb)
|
|
68
|
-
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
|
|
69
|
-
DECLARE
|
|
70
|
-
v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);
|
|
71
|
-
v_existing_tenant uuid;
|
|
72
|
-
v_created boolean := false;
|
|
73
|
-
v_standalone boolean := migration._standalone();
|
|
74
|
-
BEGIN
|
|
75
|
-
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
76
|
-
IF p_id IS NULL OR nullif(btrim(coalesce(p_row ->> 'name', '')), '') IS NULL THEN
|
|
77
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'this row needs an id and a name');
|
|
78
|
-
END IF;
|
|
79
|
-
IF coalesce(p_row ->> 'kind', 'bank') NOT IN ('bank', 'cash_physical', 'cash_session', 'card_receivable_credit',
|
|
80
|
-
'card_receivable_debit', 'card_payable', 'person', 'system') THEN
|
|
81
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value',
|
|
82
|
-
'reason', format('unknown account kind %L — V1 account_type does not map onto the ledger''s eight', p_row ->> 'kind'));
|
|
83
|
-
END IF;
|
|
84
|
-
SELECT tenant_id INTO v_existing_tenant FROM public.plg_financial_accounts WHERE id = p_id;
|
|
85
|
-
IF v_existing_tenant IS NOT NULL AND v_existing_tenant <> p_tenant THEN
|
|
86
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant', 'reason', format('%s already exists in another tenant', p_id));
|
|
87
|
-
END IF;
|
|
88
|
-
IF v_existing_tenant IS NULL THEN
|
|
89
|
-
INSERT INTO public.plg_financial_accounts (id, tenant_id, unit_id, kind, code, name, bank_name, settlement_days, is_active, metadata)
|
|
90
|
-
VALUES (p_id, p_tenant,
|
|
91
|
-
(p_row ->> 'unit_id')::uuid,
|
|
92
|
-
coalesce(p_row ->> 'kind', 'bank'),
|
|
93
|
-
p_row ->> 'code',
|
|
94
|
-
p_row ->> 'name',
|
|
95
|
-
p_row ->> 'bank_name',
|
|
96
|
-
(p_row ->> 'settlement_days')::integer,
|
|
97
|
-
coalesce((p_row ->> 'is_active')::boolean, true),
|
|
98
|
-
v_meta);
|
|
99
|
-
v_created := true;
|
|
100
|
-
ELSE
|
|
101
|
-
UPDATE public.plg_financial_accounts SET
|
|
102
|
-
unit_id = CASE WHEN p_row ? 'unit_id' THEN (p_row ->> 'unit_id')::uuid ELSE unit_id END,
|
|
103
|
-
kind = CASE WHEN p_row ? 'kind' THEN coalesce(p_row ->> 'kind', 'bank') ELSE kind END,
|
|
104
|
-
code = CASE WHEN p_row ? 'code' THEN p_row ->> 'code' ELSE code END,
|
|
105
|
-
name = CASE WHEN p_row ? 'name' THEN p_row ->> 'name' ELSE name END,
|
|
106
|
-
bank_name = CASE WHEN p_row ? 'bank_name' THEN p_row ->> 'bank_name' ELSE bank_name END,
|
|
107
|
-
settlement_days = CASE WHEN p_row ? 'settlement_days' THEN (p_row ->> 'settlement_days')::integer ELSE settlement_days END,
|
|
108
|
-
is_active = CASE WHEN p_row ? 'is_active' THEN coalesce((p_row ->> 'is_active')::boolean, true) ELSE is_active END,
|
|
109
|
-
metadata = migration._merge_meta(metadata, v_meta)
|
|
110
|
-
WHERE id = p_id;
|
|
111
|
-
END IF;
|
|
112
|
-
IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.plg_financial_accounts', p_id, jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END)); END IF;
|
|
113
|
-
RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);
|
|
114
|
-
END $$;
|
|
115
|
-
|
|
116
|
-
CREATE OR REPLACE FUNCTION migration.upsert_fin_payment_method_type(p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb)
|
|
117
|
-
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
|
|
118
|
-
DECLARE
|
|
119
|
-
v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);
|
|
120
|
-
v_existing_tenant uuid;
|
|
121
|
-
v_created boolean := false;
|
|
122
|
-
v_standalone boolean := migration._standalone();
|
|
123
|
-
BEGIN
|
|
124
|
-
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
125
|
-
IF p_id IS NULL OR nullif(btrim(coalesce(p_row ->> 'name', '')), '') IS NULL THEN
|
|
126
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'this row needs an id and a name');
|
|
127
|
-
END IF;
|
|
128
|
-
SELECT tenant_id INTO v_existing_tenant FROM public.plg_financial_payment_method_types WHERE id = p_id;
|
|
129
|
-
IF v_existing_tenant IS NOT NULL AND v_existing_tenant <> p_tenant THEN
|
|
130
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant', 'reason', format('%s already exists in another tenant', p_id));
|
|
131
|
-
END IF;
|
|
132
|
-
IF v_existing_tenant IS NULL THEN
|
|
133
|
-
INSERT INTO public.plg_financial_payment_method_types (id, tenant_id, name, is_active)
|
|
134
|
-
VALUES (p_id, p_tenant,
|
|
135
|
-
p_row ->> 'name',
|
|
136
|
-
coalesce((p_row ->> 'is_active')::boolean, true));
|
|
137
|
-
v_created := true;
|
|
138
|
-
ELSE
|
|
139
|
-
UPDATE public.plg_financial_payment_method_types SET
|
|
140
|
-
name = CASE WHEN p_row ? 'name' THEN p_row ->> 'name' ELSE name END,
|
|
141
|
-
is_active = CASE WHEN p_row ? 'is_active' THEN coalesce((p_row ->> 'is_active')::boolean, true) ELSE is_active END
|
|
142
|
-
WHERE id = p_id;
|
|
143
|
-
END IF;
|
|
144
|
-
IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.plg_financial_payment_method_types', p_id, jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END)); END IF;
|
|
145
|
-
RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);
|
|
146
|
-
END $$;
|
|
147
|
-
|
|
148
|
-
CREATE OR REPLACE FUNCTION migration.upsert_fin_payment_method(p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb)
|
|
149
|
-
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
|
|
150
|
-
DECLARE
|
|
151
|
-
v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);
|
|
152
|
-
v_existing_tenant uuid;
|
|
153
|
-
v_created boolean := false;
|
|
154
|
-
v_standalone boolean := migration._standalone();
|
|
155
|
-
BEGIN
|
|
156
|
-
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
157
|
-
IF p_id IS NULL OR nullif(btrim(coalesce(p_row ->> 'name', '')), '') IS NULL THEN
|
|
158
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'this row needs an id and a name');
|
|
159
|
-
END IF;
|
|
160
|
-
SELECT tenant_id INTO v_existing_tenant FROM public.plg_financial_payment_methods WHERE id = p_id;
|
|
161
|
-
IF v_existing_tenant IS NOT NULL AND v_existing_tenant <> p_tenant THEN
|
|
162
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant', 'reason', format('%s already exists in another tenant', p_id));
|
|
163
|
-
END IF;
|
|
164
|
-
IF v_existing_tenant IS NULL THEN
|
|
165
|
-
INSERT INTO public.plg_financial_payment_methods (id, tenant_id, name, payment_method_type_id, is_active)
|
|
166
|
-
VALUES (p_id, p_tenant,
|
|
167
|
-
p_row ->> 'name',
|
|
168
|
-
(p_row ->> 'type_id')::uuid,
|
|
169
|
-
coalesce((p_row ->> 'is_active')::boolean, true));
|
|
170
|
-
v_created := true;
|
|
171
|
-
ELSE
|
|
172
|
-
UPDATE public.plg_financial_payment_methods SET
|
|
173
|
-
name = CASE WHEN p_row ? 'name' THEN p_row ->> 'name' ELSE name END,
|
|
174
|
-
payment_method_type_id = CASE WHEN p_row ? 'payment_method_type_id' THEN (p_row ->> 'type_id')::uuid ELSE payment_method_type_id END,
|
|
175
|
-
is_active = CASE WHEN p_row ? 'is_active' THEN coalesce((p_row ->> 'is_active')::boolean, true) ELSE is_active END
|
|
176
|
-
WHERE id = p_id;
|
|
177
|
-
END IF;
|
|
178
|
-
IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.plg_financial_payment_methods', p_id, jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END)); END IF;
|
|
179
|
-
RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);
|
|
180
|
-
END $$;
|
|
181
|
-
|
|
182
|
-
CREATE OR REPLACE FUNCTION migration.upsert_fin_card_brand(p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb)
|
|
183
|
-
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
|
|
184
|
-
DECLARE
|
|
185
|
-
v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);
|
|
186
|
-
v_existing_tenant uuid;
|
|
187
|
-
v_created boolean := false;
|
|
188
|
-
v_standalone boolean := migration._standalone();
|
|
189
|
-
BEGIN
|
|
190
|
-
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
191
|
-
IF p_id IS NULL OR nullif(btrim(coalesce(p_row ->> 'name', '')), '') IS NULL THEN
|
|
192
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'this row needs an id and a name');
|
|
193
|
-
END IF;
|
|
194
|
-
SELECT tenant_id INTO v_existing_tenant FROM public.plg_financial_card_brands WHERE id = p_id;
|
|
195
|
-
IF v_existing_tenant IS NOT NULL AND v_existing_tenant <> p_tenant THEN
|
|
196
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant', 'reason', format('%s already exists in another tenant', p_id));
|
|
197
|
-
END IF;
|
|
198
|
-
IF v_existing_tenant IS NULL THEN
|
|
199
|
-
INSERT INTO public.plg_financial_card_brands (id, tenant_id, name, is_active)
|
|
200
|
-
VALUES (p_id, p_tenant,
|
|
201
|
-
p_row ->> 'name',
|
|
202
|
-
coalesce((p_row ->> 'is_active')::boolean, true));
|
|
203
|
-
v_created := true;
|
|
204
|
-
ELSE
|
|
205
|
-
UPDATE public.plg_financial_card_brands SET
|
|
206
|
-
name = CASE WHEN p_row ? 'name' THEN p_row ->> 'name' ELSE name END,
|
|
207
|
-
is_active = CASE WHEN p_row ? 'is_active' THEN coalesce((p_row ->> 'is_active')::boolean, true) ELSE is_active END
|
|
208
|
-
WHERE id = p_id;
|
|
209
|
-
END IF;
|
|
210
|
-
IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.plg_financial_card_brands', p_id, jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END)); END IF;
|
|
211
|
-
RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);
|
|
212
|
-
END $$;
|
|
213
|
-
|
|
214
|
-
CREATE OR REPLACE FUNCTION migration.upsert_fin_chart_account(p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb)
|
|
215
|
-
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
|
|
216
|
-
DECLARE
|
|
217
|
-
v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);
|
|
218
|
-
v_existing_tenant uuid;
|
|
219
|
-
v_created boolean := false;
|
|
220
|
-
v_standalone boolean := migration._standalone();
|
|
221
|
-
BEGIN
|
|
222
|
-
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
223
|
-
IF p_id IS NULL OR nullif(btrim(coalesce(p_row ->> 'name', '')), '') IS NULL THEN
|
|
224
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'this row needs an id and a name');
|
|
225
|
-
END IF;
|
|
226
|
-
SELECT tenant_id INTO v_existing_tenant FROM public.plg_financial_chart_of_accounts WHERE id = p_id;
|
|
227
|
-
IF v_existing_tenant IS NOT NULL AND v_existing_tenant <> p_tenant THEN
|
|
228
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant', 'reason', format('%s already exists in another tenant', p_id));
|
|
229
|
-
END IF;
|
|
230
|
-
IF v_existing_tenant IS NULL THEN
|
|
231
|
-
INSERT INTO public.plg_financial_chart_of_accounts (id, tenant_id, code, name, node_type, parent_id, is_active)
|
|
232
|
-
VALUES (p_id, p_tenant,
|
|
233
|
-
p_row ->> 'code',
|
|
234
|
-
p_row ->> 'name',
|
|
235
|
-
coalesce(p_row ->> 'node_type', 'account'),
|
|
236
|
-
(p_row ->> 'parent_id')::uuid,
|
|
237
|
-
coalesce((p_row ->> 'is_active')::boolean, true));
|
|
238
|
-
v_created := true;
|
|
239
|
-
ELSE
|
|
240
|
-
UPDATE public.plg_financial_chart_of_accounts SET
|
|
241
|
-
code = CASE WHEN p_row ? 'code' THEN p_row ->> 'code' ELSE code END,
|
|
242
|
-
name = CASE WHEN p_row ? 'name' THEN p_row ->> 'name' ELSE name END,
|
|
243
|
-
node_type = CASE WHEN p_row ? 'node_type' THEN coalesce(p_row ->> 'node_type', 'account') ELSE node_type END,
|
|
244
|
-
parent_id = CASE WHEN p_row ? 'parent_id' THEN (p_row ->> 'parent_id')::uuid ELSE parent_id END,
|
|
245
|
-
is_active = CASE WHEN p_row ? 'is_active' THEN coalesce((p_row ->> 'is_active')::boolean, true) ELSE is_active END
|
|
246
|
-
WHERE id = p_id;
|
|
247
|
-
END IF;
|
|
248
|
-
IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.plg_financial_chart_of_accounts', p_id, jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END)); END IF;
|
|
249
|
-
RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);
|
|
250
|
-
END $$;
|
|
251
|
-
|
|
252
|
-
CREATE OR REPLACE FUNCTION migration.upsert_fin_cost_center(p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb)
|
|
253
|
-
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
|
|
254
|
-
DECLARE
|
|
255
|
-
v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);
|
|
256
|
-
v_existing_tenant uuid;
|
|
257
|
-
v_created boolean := false;
|
|
258
|
-
v_standalone boolean := migration._standalone();
|
|
259
|
-
BEGIN
|
|
260
|
-
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
261
|
-
IF p_id IS NULL OR nullif(btrim(coalesce(p_row ->> 'name', '')), '') IS NULL THEN
|
|
262
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'this row needs an id and a name');
|
|
263
|
-
END IF;
|
|
264
|
-
SELECT tenant_id INTO v_existing_tenant FROM public.plg_financial_cost_centers WHERE id = p_id;
|
|
265
|
-
IF v_existing_tenant IS NOT NULL AND v_existing_tenant <> p_tenant THEN
|
|
266
|
-
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant', 'reason', format('%s already exists in another tenant', p_id));
|
|
267
|
-
END IF;
|
|
268
|
-
IF v_existing_tenant IS NULL THEN
|
|
269
|
-
INSERT INTO public.plg_financial_cost_centers (id, tenant_id, code, name, is_active)
|
|
270
|
-
VALUES (p_id, p_tenant,
|
|
271
|
-
p_row ->> 'code',
|
|
272
|
-
p_row ->> 'name',
|
|
273
|
-
coalesce((p_row ->> 'is_active')::boolean, true));
|
|
274
|
-
v_created := true;
|
|
275
|
-
ELSE
|
|
276
|
-
UPDATE public.plg_financial_cost_centers SET
|
|
277
|
-
code = CASE WHEN p_row ? 'code' THEN p_row ->> 'code' ELSE code END,
|
|
278
|
-
name = CASE WHEN p_row ? 'name' THEN p_row ->> 'name' ELSE name END,
|
|
279
|
-
is_active = CASE WHEN p_row ? 'is_active' THEN coalesce((p_row ->> 'is_active')::boolean, true) ELSE is_active END
|
|
280
|
-
WHERE id = p_id;
|
|
281
|
-
END IF;
|
|
282
|
-
IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.plg_financial_cost_centers', p_id, jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END)); END IF;
|
|
283
|
-
RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);
|
|
284
|
-
END $$;
|
|
285
|
-
|
|
286
|
-
DO $$
|
|
287
|
-
DECLARE r record;
|
|
288
|
-
BEGIN
|
|
289
|
-
FOR r IN SELECT p.oid::regprocedure AS sig FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
290
|
-
WHERE n.nspname = 'migration' AND p.proname LIKE 'upsert\_fin\_%' LOOP
|
|
291
|
-
EXECUTE format('REVOKE ALL ON FUNCTION %s FROM public, anon, authenticated', r.sig);
|
|
292
|
-
EXECUTE format('GRANT EXECUTE ON FUNCTION %s TO service_role', r.sig);
|
|
293
|
-
END LOOP;
|
|
294
|
-
END $$;
|