@fayz-ai/db 0.12.0 → 0.13.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.
Files changed (116) hide show
  1. package/canonical-schema.json +49 -0
  2. package/migrations/010_migration_ledger.sql +11 -1
  3. package/migrations/040_resource_grants.sql +2 -2
  4. package/migrations/041_scoped_columns.sql +2 -2
  5. package/migrations/042_unit_scope_policies.sql +1 -1
  6. package/migrations/043_view_invoker.sql +12 -3
  7. package/migrations/044_unit_member_facts.sql +1 -1
  8. package/migrations/045_unit_entry.sql +1 -1
  9. package/migrations/046_membership_visible_to_members.sql +1 -1
  10. package/migrations/047_tasks.sql +4 -4
  11. package/migrations/048_every_login_is_a_person.sql +1 -1
  12. package/migrations/049_bookable_people.sql +1 -1
  13. package/migrations/050_authz_core.sql +230 -0
  14. package/migrations/051_authz_helpers.sql +330 -0
  15. package/migrations/052_authz_fanout.sql +253 -0
  16. package/migrations/053_authz_catalog_templates.sql +402 -0
  17. package/migrations/060_tenancy_seam.sql +132 -0
  18. package/migrations/061_create_tenant.sql +125 -0
  19. package/migrations/062_invitations.sql +329 -0
  20. package/migrations/063_legacy_tenancy_retired.sql +568 -0
  21. package/migrations/064_agent_guard_authz.sql +118 -0
  22. package/migrations/064b_supersede_unit_scope.sql +205 -0
  23. package/migrations/065_scaffold_sweep.sql +139 -0
  24. package/migrations/066_shadow_report.sql +122 -0
  25. package/migrations/070_permission_catalog.sql +205 -0
  26. package/migrations/071_v1_permission_map.sql +266 -0
  27. package/migrations/072_role_rpcs.sql +126 -0
  28. package/migrations/073_legacy_rbac_retire.sql +471 -0
  29. package/migrations/075_company_legal_entities.sql +169 -0
  30. package/migrations/076_tenant_settings.sql +251 -0
  31. package/migrations/080_people_roles.sql +269 -0
  32. package/migrations/081_people_identity.sql +271 -0
  33. package/migrations/082_people_membership_link.sql +142 -0
  34. package/migrations/083_people_data_migration.sql +75 -0
  35. package/migrations/084_people_registrations_tokens.sql +282 -0
  36. package/migrations/085_catalog_products.sql +333 -0
  37. package/migrations/086_catalog_pricing.sql +194 -0
  38. package/migrations/087_catalog_packages.sql +134 -0
  39. package/migrations/090_orders_venda.sql +166 -0
  40. package/migrations/091_orders_scaffold.sql +92 -0
  41. package/migrations/095_config_field_rules.sql +261 -0
  42. package/migrations/096_audit_append_only.sql +256 -0
  43. package/migrations/097_sequences_authorized.sql +111 -0
  44. package/migrations/098_holidays.sql +80 -0
  45. package/migrations/099_access_grants.sql +293 -0
  46. package/migrations/100_migration_schema.sql +217 -0
  47. package/migrations/101_migration_allowlist.sql +202 -0
  48. package/migrations/102_migration_rpcs.sql +933 -0
  49. package/migrations/103_migration_reconcile_ponr.sql +132 -0
  50. package/migrations/105_policies_to_authenticated.sql +71 -0
  51. package/migrations/106_function_search_path.sql +55 -0
  52. package/migrations/107_core_table_prefix.sql +361 -0
  53. package/migrations/108_authz_write_guards.sql +149 -0
  54. package/migrations/109_catalog_migration_promoted_columns.sql +126 -0
  55. package/migrations/110_people_kind_vocabulary.sql +120 -0
  56. package/migrations/111_migration_excluded_is_a_decision.sql +259 -0
  57. package/migrations/111_replacing_a_view_keeps_its_dependents.sql +132 -0
  58. package/migrations/112_money_precision_standard.sql +132 -0
  59. package/migrations/113_addresses_units_companies.sql +114 -0
  60. package/migrations/114_migrations_ledger_immutable.sql +65 -0
  61. package/migrations/115_registration_honours_field_rules.sql +172 -0
  62. package/migrations/116_field_rules_check_needs_a_tenant.sql +72 -0
  63. package/migrations/117_authz_reconciliation_schedule.sql +82 -0
  64. package/migrations/118_migration_catalog_writers.sql +450 -0
  65. package/migrations/119_migration_config_writers.sql +193 -0
  66. package/migrations/120_migration_facet_writer.sql +139 -0
  67. package/migrations/121_migration_financial_writers.sql +294 -0
  68. package/migrations/122_migration_inventory_writers.sql +245 -0
  69. package/migrations/123_migration_order_item_writer.sql +144 -0
  70. package/migrations/124_migration_ledger_writers.sql +170 -0
  71. package/migrations/125_migration_authz_people_writers.sql +205 -0
  72. package/migrations/126_owner_scoping_is_configuration.sql +247 -0
  73. package/migrations/127_scaffold_audit_hook.sql +98 -0
  74. package/migrations/128_shadow_tables_enforced.sql +93 -0
  75. package/migrations/129_order_kind_terminal_states.sql +57 -0
  76. package/migrations/130_every_login_is_a_person.sql +196 -0
  77. package/migrations/131_migration_invoice_split.sql +127 -0
  78. package/migrations/132_audit_prune_scheduled.sql +28 -0
  79. package/migrations/133_unit_is_a_session_property.sql +155 -0
  80. package/migrations/134_field_rules_on_read.sql +341 -0
  81. package/migrations/135_distribution_polarity.sql +158 -0
  82. package/migrations/136_unit_id_means_locality.sql +358 -0
  83. package/migrations/137_vendor_named_spine_objects.sql +126 -0
  84. package/migrations/138_grants_inherit_down_the_tree.sql +178 -0
  85. package/migrations/139_distributable_is_a_scaffold_option.sql +109 -0
  86. package/migrations/140_masking_is_a_scaffold_option.sql +85 -0
  87. package/migrations/141_renamed_objects_keep_a_compat_name.sql +192 -0
  88. package/migrations/142_a_person_has_addresses.sql +212 -0
  89. package/migrations/143_a_professional_has_a_rate.sql +75 -0
  90. package/migrations/144_a_product_can_be_sold_in_sizes.sql +57 -0
  91. package/migrations/145_the_app_says_which_tenant_it_is_in.sql +123 -0
  92. package/migrations/146_products_stock_has_one_writer.sql +67 -0
  93. package/migrations/147_every_archetype_has_a_kind_registry.sql +388 -0
  94. package/migrations/148_the_legacy_archetype_stops_being_writable.sql +154 -0
  95. package/migrations/149_the_event_log_gets_a_consumer.sql +331 -0
  96. package/migrations/150_the_app_is_configured_in_the_database.sql +285 -0
  97. package/migrations/151_stock_balance_is_an_archetype.sql +144 -0
  98. package/migrations/152_a_file_is_an_archetype.sql +222 -0
  99. package/migrations/153_a_plugin_calls_a_service_through_one_door.sql +199 -0
  100. package/migrations/154_the_plan_is_a_ceiling.sql +258 -0
  101. package/migrations/155_a_tenant_can_leave.sql +271 -0
  102. package/migrations/156_the_service_door_has_a_server_side.sql +335 -0
  103. package/migrations/157_nobody_creates_objects_in_public.sql +74 -0
  104. package/migrations/158_the_registry_owns_the_status_vocabulary.sql +153 -0
  105. package/migrations/159_the_plan_catalogue_gets_its_first_rows.sql +132 -0
  106. package/migrations/160_the_vertical_decides_what_starts_on.sql +246 -0
  107. package/migrations/161_a_tenant_leaves_with_its_data.sql +363 -0
  108. package/migrations/162_every_name_has_one_creator_for_real.sql +126 -0
  109. package/migrations/163_a_species_nobody_declared_goes_to_quarantine.sql +184 -0
  110. package/migrations/164_the_sequence_table_joins_the_register.sql +26 -0
  111. package/migrations/165_a_view_is_not_a_way_in.sql +90 -0
  112. package/migrations/166_the_platform_billing_leftovers_go.sql +72 -0
  113. package/migrations/167_a_view_gets_back_what_its_table_grants.sql +112 -0
  114. package/migrations/168_the_grant_census_stops_crying_wolf.sql +33 -0
  115. package/migrations/169_the_census_asks_postgres_instead_of_guessing.sql +45 -0
  116. package/package.json +3 -2
@@ -0,0 +1,202 @@
1
+ -- ============================================================================
2
+ -- 101_migration_allowlist.sql — the frozen core-A extraction allowlist
3
+ -- (core-beauty-chef.md §3.1) and the tables the extractor must never send.
4
+ --
5
+ -- Data every destination needs, so it is a migration, not a seed. Rows are
6
+ -- frozen: changing one is a new migration file, not an UPDATE (the trigger of
7
+ -- 090 refuses writes unless the session unlocks, as this file does).
8
+ --
9
+ -- Column maps: "target_col" copies, "target_col@source_table" resolves a V1
10
+ -- reference through the ledger (unresolved ⇒ quarantine 'fk'), "jsonb:a.b"
11
+ -- lands in the destination row's metadata (vertical data, ADR 0009), "-" is
12
+ -- dropped on purpose (documented minimisation, PRD 03 R7). Maps for tables
13
+ -- without a writer are coarse: the notable columns and where they go; the
14
+ -- domain slice that ships the destination completes them with its writer.
15
+ -- ============================================================================
16
+
17
+ SELECT set_config('migration.allowlist_unlock', 'on', false);
18
+
19
+ CREATE OR REPLACE FUNCTION migration._allow(
20
+ p_source text, p_target text, p_domain text, p_writer text, p_writer_args jsonb,
21
+ p_financial text[], p_columns jsonb, p_notes text
22
+ ) RETURNS void LANGUAGE sql SECURITY DEFINER SET search_path = '' AS $$
23
+ INSERT INTO migration.allowlist (source_table, target_table, layer, domain, columns, writer, writer_args, financial_columns, frozen, notes)
24
+ VALUES (p_source, p_target, 'core-A', p_domain, coalesce(p_columns, '{}'::jsonb), p_writer, coalesce(p_writer_args, '{}'::jsonb), coalesce(p_financial, '{}'), true, p_notes)
25
+ ON CONFLICT (source_table) DO UPDATE
26
+ SET target_table = EXCLUDED.target_table, layer = EXCLUDED.layer, domain = EXCLUDED.domain, columns = EXCLUDED.columns,
27
+ writer = EXCLUDED.writer, writer_args = EXCLUDED.writer_args, financial_columns = EXCLUDED.financial_columns,
28
+ frozen = true, notes = EXCLUDED.notes;
29
+ $$;
30
+ REVOKE ALL ON FUNCTION migration._allow(text, text, text, text, jsonb, text[], jsonb, text) FROM public, anon, authenticated;
31
+
32
+ -- ── licence / tenant / unit ─────────────────────────────────────────────
33
+ SELECT migration._allow('licenses', 'public.tenants', 'tenancy', NULL, '{}',
34
+ '{}', '{"uuid":"id","name":"name","slug":"slug","plan":"jsonb:migration.v1_plan","active":"jsonb:migration.v1_active","max_users":"jsonb:migration.v1_max_users","software_profile_id":"jsonb:migration.v1_software_profile_id","time_clock_enabled":"-"}',
35
+ 'ADR 0002: a licence is a tenant (kind business; network when general_settings.company_type = franquia). The tenant is provisioned by a human before batch_open; this row documents the map, the destination does not create tenants.');
36
+ SELECT migration._allow('licenses_admin', 'public.tenants', 'tenancy', NULL, '{}', '{}', '{"license_id":"jsonb:migration.license_id"}',
37
+ 'Platform-admin view of licences: tenant settings/plan metadata; no writer.');
38
+ SELECT migration._allow('license_users', 'app.memberships', 'tenancy', NULL, '{}', '{}',
39
+ '{"uuid":"id","license_id":"tenant_id","user_id":"user_id","role":"role_id","email":"-","active":"active"}',
40
+ 'A membership needs an auth.users row the destination cannot mint for a real login: the human invites (transactional invite RPC, #134); role mapped via the permission catalog (#136).');
41
+ SELECT migration._allow('profiles', 'public.people', 'tenancy', NULL, '{}', '{}', '{"id":"-","full_name":"name","email":"email","avatar_url":"avatar_url"}',
42
+ 'Login profile: folded into the Person of the member (memberships.person_id, PRD 03 R3); no writer.');
43
+ SELECT migration._allow('modules', 'public.tenants', 'tenancy', NULL, '{}', '{}', '{"code":"jsonb:modules"}', 'PRD 02 R3: modules become tenant configuration (enabled plugins), not a table.');
44
+ SELECT migration._allow('software_profiles', 'public.tenants', 'tenancy', NULL, '{}', '{}', '{"name":"jsonb:software_profile"}', 'PRD 02 R3: software profile → tenant configuration.');
45
+ SELECT migration._allow('software_profile_modules', 'public.tenants', 'tenancy', NULL, '{}', '{}', '{}', 'PRD 02 R3: profile × module → tenant configuration.');
46
+ SELECT migration._allow('tenant_domains', 'public.tenants', 'tenancy', NULL, '{}', '{}', '{"domain":"jsonb:branding.domain","logo_url":"jsonb:branding.logo_url"}', 'PRD 02 R5: branding/domain into tenant settings; login must survive a branding failure.');
47
+ SELECT migration._allow('companies', 'app.units', 'tenancy', 'unit', '{"default_kind":"branch"}', '{}',
48
+ '{"uuid":"id","tenant_id":"-","legal_name":"name","trade_name":"jsonb:trade_name","abbreviation":"jsonb:abbreviation","is_headquarters":"kind","active":"active","cnpj":"jsonb:legal.cnpj","location_group_id":"parent_unit_id@location_groups","address":"jsonb:address.street","address_number":"jsonb:address.number","address_complement":"jsonb:address.complement","neighborhood":"jsonb:address.neighborhood","city":"jsonb:address.city","state":"jsonb:address.state","postal_code":"jsonb:address.postal_code","phone_1":"jsonb:contact.phone_1","phone_2":"jsonb:contact.phone_2","mobile_1":"jsonb:contact.mobile_1","email_1":"jsonb:contact.email_1","whatsapp_number":"jsonb:contact.whatsapp","timezone":"jsonb:timezone","logo_url":"jsonb:branding.logo_url","brand_colors":"jsonb:branding.colors","certificate_cn":"-","certificate_plugnotas_id":"-","dfe_sync_cursor":"-","dfe_last_sync_at":"-"}',
49
+ 'ADR 0002 / PRD 02 R1: a company is a unit (is_headquarters → hq, else branch). CNPJ/legal data go to the legal entity (Empresa, #138) — kept in metadata.legal until that writer exists. A second hq in a tenant is a state quarantine, never a silent demotion.');
50
+ SELECT migration._allow('service_locations', 'app.units', 'tenancy', NULL, '{}', '{}', '{"uuid":"id","company_id":"unit_id@companies","name":"jsonb:name"}',
51
+ 'PRD 02 R1: a service location is a place INSIDE a unit, not a unit — extension of the owning plugin in Phase 2; recorded pending until then.');
52
+ SELECT migration._allow('location_groups', 'app.units', 'tenancy', 'unit', '{"default_kind":"branch","grouping":true}', '{}',
53
+ '{"uuid":"id","tenant_id":"-","name":"name","description":"jsonb:description","active":"active"}',
54
+ 'PRD 02 R1: a location group is a parent unit (franchisee/region); companies point at it through parent_unit_id.');
55
+
56
+ -- ── permissions ─────────────────────────────────────────────────────────
57
+ SELECT migration._allow('system_permissions', 'app.permissions', 'authz', NULL, '{}', '{}', '{"code":"key"}', '#136: only the 83 enforced codes map to {plugin}.{action} keys; the catalog is seeded by migrations, not migrated row by row.');
58
+ SELECT migration._allow('permission_rules', 'app.roles', 'authz', NULL, '{}', '{}', '{"uuid":"id","name":"name"}', '#136: a rule is a tenant role.');
59
+ SELECT migration._allow('permission_rule_details', 'app.role_permissions', 'authz', NULL, '{}', '{}', '{"rule_id":"role_id@permission_rules","permission_code":"permission"}', '#136: rule × permission → role_permissions.');
60
+ SELECT migration._allow('employee_permission_rules', 'app.memberships', 'authz', NULL, '{}', '{}', '{"employee_id":"person_id@employees","rule_id":"role_id@permission_rules"}', 'Role of the employee member; through the Person of the employee (PRD 03 R3).');
61
+ SELECT migration._allow('professional_permission_rules', 'app.memberships', 'authz', NULL, '{}', '{}', '{"professional_id":"person_id@professionals","rule_id":"role_id@permission_rules"}', 'Role of the professional member; through the Person of the professional.');
62
+
63
+ -- ── base configuration ──────────────────────────────────────────────────
64
+ SELECT migration._allow('general_settings', 'public.tenants', 'config', NULL,
65
+ '{"by_key":true,"never":["permissions_enforcement_enabled"],"later_phase":["attendance_step_order","pos_stock_deduction_enabled","client_menu_config"]}', '{}',
66
+ '{"setting_key":"jsonb:settings.<key>","setting_value":"jsonb:settings.<key>","category":"-","display_name":"-","options":"-"}',
67
+ 'PRD 02 R4 (default g): migrated BY KEY, never as a table. Core keys → tenants.settings; vertical keys → the owning plugin; permissions_enforcement_enabled never (RLS is the law); beauty/chef keys wait for phases 3/4.');
68
+ SELECT migration._allow('required_field_rules', 'config.field_rules', 'config', NULL, '{}', '{}', '{"context":"context","entity":"entity","field_key":"field_key","visibility":"state"}', 'PRD 07 / #144: 3-state FieldRules per context; the config slice owns the destination table name.');
69
+ SELECT migration._allow('field_visibility_rules', 'config.field_rules', 'config', NULL, '{}', '{}', '{}', 'PRD 07 / #144: folded into FieldRules.');
70
+ SELECT migration._allow('holidays', 'config.holidays', 'config', NULL, '{}', '{}', '{"uuid":"id","date":"date","name":"name","recurring":"recurring","unit_id":"unit_id@companies","units":"jsonb:units"}', 'PRD 07: tenant/unit holidays.');
71
+ SELECT migration._allow('central_holidays', 'config.holidays', 'config', NULL, '{}', '{}', '{"date":"date","name":"name"}', 'National calendar; platform-level rows.');
72
+ -- The three source names below are V1 spelling and stay as V1 wrote them; V2 calls
73
+ -- the destination `access_grants` (the QR is one delivery channel, not the concept).
74
+ SELECT migration._allow('qr_access_configs', 'config.access_grant', 'config', NULL, '{}', '{}', '{"enabled":"enabled","hours_valid":"hours_valid","scan_window_minutes":"scan_window_minutes"}', 'PRD 07 / #144: the V1 config is opaque; it becomes one grant per (unit, role) with channel ''qr''.');
75
+ SELECT migration._allow('qr_access_grants', 'config.access_grant', 'config', NULL, '{}', '{}', '{}', 'PRD 07 / #144: V1 issued codes become access_grants on the ''qr'' channel with a freshly minted secret.');
76
+ SELECT migration._allow('qr_access_sessions', 'config.access_grant', 'config', NULL, '{}', '{}', '{}', 'PRD 07 / #144: live sessions are not migrated (they expire); documented for completeness.');
77
+
78
+ -- ── people ──────────────────────────────────────────────────────────────
79
+ SELECT migration._allow('clients', 'public.people', 'people', 'person', '{"role":"customer"}', '{}',
80
+ '{"uuid":"id","tenant_id":"-","name":"name","social_name":"jsonb:social_name","cpf":"document_number","rg":"jsonb:documents.rg","email":"email","phone":"phone","mobile":"jsonb:contact.mobile","birth_date":"date_of_birth","gender":"jsonb:profile.gender","marital_status":"jsonb:profile.marital_status","profession":"jsonb:profile.profession","education":"jsonb:profile.education","nationality":"jsonb:profile.nationality","avatar_url":"avatar_url","address":"address","address_number":"jsonb:address.number","address_complement":"jsonb:address.complement","neighborhood":"jsonb:address.neighborhood","city":"city","state":"state","country":"country","postal_code":"postal_code","notes":"notes","origin":"jsonb:customer.origin","lead_source":"jsonb:customer.lead_source","lead_source_ref":"jsonb:customer.lead_source_ref","referral":"jsonb:customer.referral","origin_referrer_client_id":"jsonb:customer.referrer_client_ref","responsible_client_id":"jsonb:customer.responsible_client_ref","customer_since":"jsonb:customer.since","first_contact_at":"jsonb:customer.first_contact_at","last_contact_at":"jsonb:customer.last_contact_at","last_visit":"jsonb:customer.last_visit","total_visits":"jsonb:customer.total_visits","stage":"jsonb:customer.stage","stage_changed_at":"jsonb:customer.stage_changed_at","identification_color":"jsonb:customer.color","skin_color":"jsonb:beauty.skin_color","hair_color":"jsonb:beauty.hair_color","hair_type":"jsonb:beauty.hair_type","warnings":"jsonb:beauty.warnings","search_text":"-","stage_changed_by":"-"}',
81
+ 'ADR 0003 / PRD 03: a client is a Person with the customer role. Dedup by exact CPF (merge), no CPF ⇒ new Person unless someone already shares email+phone (identity quarantine). Clinical columns → metadata.beauty (ADR 0009).');
82
+ SELECT migration._allow('professionals', 'public.people', 'people', 'person', '{"role":"professional"}', '{}',
83
+ '{"uuid":"id","tenant_id":"-","name":"name","social_name":"jsonb:social_name","cpf":"document_number","rg":"jsonb:documents.rg","email":"email","phone":"phone","birth_date":"date_of_birth","gender":"jsonb:profile.gender","avatar_url":"avatar_url","avatar_color":"jsonb:profile.avatar_color","active":"is_active","specialty":"jsonb:professional.specialty","job_title":"jsonb:professional.job_title","cbo_code":"jsonb:professional.cbo_code","cbo_description":"jsonb:professional.cbo_description","commission_rate":"jsonb:professional.commission_rate","is_salesperson":"jsonb:professional.is_salesperson","service_ids":"jsonb:professional.v1_service_refs","units":"jsonb:professional.v1_unit_refs","agenda_notes":"jsonb:professional.agenda_notes","user_id":"jsonb:migration.v1_user_id","street":"address","number":"jsonb:address.number","complement":"jsonb:address.complement","neighborhood":"jsonb:address.neighborhood","city":"city","state":"state","cep":"postal_code","base_salary":"-","bank_account":"-","bank_agency":"-","bank_code":"-","pix_key":"-","ctps_number":"-","pis_pasep":"-","esocial_registration":"-","health_plan_value":"-","dental_plan_value":"-","food_voucher_value":"-","meal_voucher_value":"-","transport_voucher_value":"-","dismissal_reason":"-","mother_name":"-","father_name":"-","voter_id":"-","cnh_number":"-","passport_number":"-","reservist_number":"-","search_text":"-"}',
84
+ 'ADR 0003: a professional is a role of a Person (same human as an employee/client merges by CPF). HR-sensitive data (salary, bank, CTPS, PIS, eSocial, family) does NOT migrate in Phase 0 (PRD 03 R4/R7 — Phase 2 hr).');
85
+ SELECT migration._allow('employees', 'public.people', 'people', 'person', '{"role":"employee"}', '{}',
86
+ '{"uuid":"id","tenant_id":"-","name":"name","social_name":"jsonb:social_name","cpf":"document_number","rg":"jsonb:documents.rg","email":"email","phone":"phone","mobile":"jsonb:contact.mobile","birth_date":"date_of_birth","gender":"jsonb:profile.gender","avatar_url":"avatar_url","active":"is_active","job_title":"jsonb:employee.job_title","department":"jsonb:employee.department","units":"jsonb:employee.v1_unit_refs","professional_access":"jsonb:employee.v1_professional_access","user_id":"jsonb:migration.v1_user_id","address":"address","city":"city","state":"state","country":"country","postal_code":"postal_code","observations":"notes","base_salary":"-","bank_account":"-","bank_agency":"-","bank_code":"-","pix_key":"-","ctps_number":"-","pis_pasep":"-","esocial_registration":"-","health_plan_value":"-","dental_plan_value":"-","food_voucher_value":"-","meal_voucher_value":"-","transport_voucher_value":"-","dismissal_reason":"-","mother_name":"-","father_name":"-","voter_id":"-","cnh_number":"-","passport_number":"-","reservist_number":"-"}',
87
+ 'ADR 0003: employee role of a Person; email/phone/mobile are arrays in V1 (the extractor sends the first). HR-sensitive data does NOT migrate in Phase 0.');
88
+ SELECT migration._allow('contacts', 'public.people', 'people', 'person', '{"role":"contact","role_by":"contact_type"}', '{}',
89
+ '{"uuid":"id","tenant_id":"-","name":"name","cpf_cnpj":"document_number","document":"jsonb:documents.other","email":"email","email_2":"jsonb:contact.email_2","phone":"phone","phone_2":"jsonb:contact.phone_2","mobile":"jsonb:contact.mobile","mobile_2":"jsonb:contact.mobile_2","contact_type":"jsonb:contact.type","service_provider_type":"jsonb:contact.service_provider_type","group_name":"jsonb:contact.group","gender":"jsonb:profile.gender","active":"is_active","address":"address","address_number":"jsonb:address.number","address_complement":"jsonb:address.complement","neighborhood":"jsonb:address.neighborhood","city":"city","state":"state","postal_code":"postal_code","observations":"notes","unit_id":"jsonb:contact.v1_unit_ref","units":"jsonb:contact.v1_unit_refs","municipal_state_registration":"jsonb:legal.state_registration","default_chart_account_id":"jsonb:financial.v1_default_chart_account_ref","cost_center_id":"jsonb:financial.v1_cost_center_ref","allowed_chart_accounts":"jsonb:financial.v1_allowed_chart_accounts","limit_chart_accounts":"jsonb:financial.limit_chart_accounts","add_items_automatically":"jsonb:financial.add_items_automatically","bank_account":"-","bank_agency":"-","bank_code":"-","pix_key":"-","search_text":"-"}',
90
+ 'ADR 0003: contact role (contact_type supplier ⇒ supplier role). CNPJ contacts are legal-entity counterparties (Pessoa como Conta, ADR 0005).');
91
+ SELECT migration._allow('suppliers', 'public.people', 'people', 'person', '{"role":"supplier"}', '{}',
92
+ '{"uuid":"id","name":"name","document":"document_number","email":"email","phone":"phone"}',
93
+ 'Listed in §3.1; in the V1 schema of 2026-08 suppliers are contacts with contact_type = supplier — the extractor sends whichever exists, the destination writes the supplier role either way.');
94
+ SELECT migration._allow('client_registrations', 'public.people', 'people', NULL, '{}', '{}', '{"uuid":"id","name":"name","cpf":"document_number","email":"email","phone":"phone"}', 'PRD 03 R6: pre-registration → Person creation queue with dedup; contract only in Phase 0.');
95
+ SELECT migration._allow('client_access_tokens', 'public.people', 'people', NULL, '{}', '{}', '{"client_id":"person_id@clients","token":"-"}', 'PRD 03 R6: opaque portal tokens; the token value never migrates (re-issued in V2).');
96
+ SELECT migration._allow('professions', 'public.people', 'people', NULL, '{}', '{}', '{"name":"jsonb:professional.profession"}', 'Reference list folded into the professional role JSONB.');
97
+ SELECT migration._allow('origins', 'public.people', 'people', NULL, '{}', '{}', '{"name":"jsonb:customer.origin"}', 'Reference list of the customer role.');
98
+ SELECT migration._allow('partnerships', 'public.people', 'people', NULL, '{}', '{}', '{"name":"jsonb:customer.partnership"}', 'Reference list of the customer role (price variations reference it).');
99
+
100
+ -- ── catalog ─────────────────────────────────────────────────────────────
101
+ SELECT migration._allow('product_categories', 'public.categories', 'catalog', 'category', '{"kind":"product"}', '{}',
102
+ '{"uuid":"id","tenant_id":"-","name":"name","description":"jsonb:description","active":"is_active","menu_order":"sort_order","image_url":"icon","show_in_menu":"jsonb:chef.show_in_menu","delivery_available":"jsonb:chef.delivery_available","pdv_available":"jsonb:chef.pdv_available","prep_time":"jsonb:chef.prep_time"}',
103
+ 'PRD 04 R2: shared categories(kind). Chef-only flags → metadata.chef.');
104
+ SELECT migration._allow('service_categories', 'public.categories', 'catalog', 'category', '{"kind":"service"}', '{}',
105
+ '{"uuid":"id","tenant_id":"-","name":"name","description":"jsonb:description","color":"color","active":"is_active","numeric_id":"-"}', 'PRD 04 R2.');
106
+ SELECT migration._allow('products', 'public.products', 'catalog', 'product', '{"kind":"good"}', '{}',
107
+ '{"uuid":"id","tenant_id":"-","name":"name","description":"description","internal_code":"sku","barcode":"jsonb:catalog.barcode","brand":"jsonb:catalog.brand","category_id":"category_id@product_categories","sale_price":"price","cost_price":"cost","avg_purchase_price":"jsonb:inventory.avg_purchase_price","active":"is_active","is_for_sale":"jsonb:catalog.is_for_sale","is_resale":"jsonb:catalog.is_resale","is_asset":"jsonb:catalog.is_asset","is_ingredient":"jsonb:chef.is_ingredient","is_intermediate":"jsonb:chef.is_intermediate","is_produced":"jsonb:chef.is_produced","product_type":"jsonb:catalog.v1_product_type","purpose":"jsonb:catalog.v1_purpose","image_url":"image_url","measurement_unit_id":"jsonb:inventory.v1_measurement_unit_ref","content_per_unit":"jsonb:inventory.content_per_unit","content_unit_id":"jsonb:inventory.v1_content_unit_ref","conversion_factor":"jsonb:inventory.conversion_factor","commission_percent":"jsonb:catalog.commission_percent","professional_resale_price":"jsonb:catalog.professional_resale_price","menu_available":"jsonb:chef.menu_available","menu_order":"jsonb:chef.menu_order","delivery_available":"jsonb:chef.delivery_available","delivery_price":"jsonb:chef.delivery_price","pdv_available":"jsonb:chef.pdv_available","current_quantity":"-","minimum_quantity":"-","maximum_quantity":"-","default_stock_location_id":"-","default_stock_unit_id":"-","supplier_id":"jsonb:inventory.v1_supplier_ref","allowed_supplier_ids":"jsonb:inventory.v1_allowed_supplier_refs","search_text":"-","numeric_id":"-"}',
108
+ 'PRD 04 R1/R5: a product is a catalog item kind=good; quantities are NOT catalog (stock_positions, PRD 06) and are dropped here.');
109
+ SELECT migration._allow('services', 'public.products', 'catalog', 'product', '{"kind":"service"}', '{}',
110
+ '{"uuid":"id","tenant_id":"-","name":"name","description":"description","internal_code":"sku","category_id":"category_id@service_categories","price":"price","active":"is_active","duration_minutes":"jsonb:catalog.duration_minutes","color":"jsonb:catalog.color","service_kind":"jsonb:beauty.service_kind","revision_count":"jsonb:beauty.revision_count","revision_price":"jsonb:beauty.revision_price","revision_deadline_days":"jsonb:beauty.revision_deadline_days","validity_days":"jsonb:catalog.validity_days","contract_text":"jsonb:beauty.contract_text","post_service_text":"jsonb:beauty.post_service_text","reminder_text":"jsonb:beauty.reminder_text","scheduling_alert_text":"jsonb:beauty.scheduling_alert_text","always_ask_used_products":"jsonb:beauty.always_ask_used_products","suggest_quote_on_execution":"jsonb:beauty.suggest_quote_on_execution","category":"-","search_text":"-"}',
111
+ 'PRD 04 R1: a service is a catalog item kind=service (public.services is drift, folded by #139). service_kind is beauty semantics on a core column → metadata.beauty until #139 promotes it (R6).');
112
+ SELECT migration._allow('product_barcodes', 'public.products', 'catalog', NULL, '{}', '{}', '{"product_id":"id@products","barcode":"jsonb:catalog.barcodes[]"}', 'Facet of the product (Matriz D7).');
113
+ SELECT migration._allow('product_sale_info', 'public.products', 'catalog', NULL, '{}', '{}', '{"product_id":"id@products"}', 'Facet of the product.');
114
+ SELECT migration._allow('product_sale_channels', 'public.products', 'catalog', NULL, '{}', '{}', '{"product_id":"id@products"}', 'Facet of the product.');
115
+ SELECT migration._allow('product_fiscal_info', 'public.products', 'catalog', NULL, '{}', '{}', '{"product_id":"id@products"}', 'Facet of the product (fiscal; NF is core-B).');
116
+ SELECT migration._allow('product_asset_info', 'public.products', 'catalog', NULL, '{}', '{}', '{"product_id":"id@products"}', 'Facet of the product.');
117
+ SELECT migration._allow('product_ingredient_info', 'public.products', 'catalog', NULL, '{}', '{}', '{"product_id":"id@products"}', 'Facet of the product (chef).');
118
+ SELECT migration._allow('product_intermediate_info', 'public.products', 'catalog', NULL, '{}', '{}', '{"product_id":"id@products"}', 'Facet of the product (chef).');
119
+ SELECT migration._allow('measurement_units', 'public.plg_inventory_measurement_units', 'catalog', NULL, '{}', '{}', '{"uuid":"id","name":"name","abbreviation":"abbreviation","active":"is_active"}', 'PRD 06 R2: units of measure live in the inventory core.');
120
+ SELECT migration._allow('unit_conversions', 'public.plg_inventory_unit_conversions', 'catalog', NULL, '{}', '{}', '{"from_unit_id":"from_unit_id@measurement_units","to_unit_id":"to_unit_id@measurement_units","conversion_factor":"factor"}', 'PRD 06 R2.');
121
+ SELECT migration._allow('price_tables', 'catalog.price_tables', 'catalog', NULL, '{}', '{}', '{"uuid":"id","description":"name","type":"kind","valid_from":"valid_from","valid_until":"valid_until","unit_ids":"jsonb:scope.v1_unit_refs","category_ids":"jsonb:scope.v1_category_refs","professional_ids":"jsonb:scope.v1_professional_refs","partnership_ids":"jsonb:scope.v1_partnership_refs","active":"is_active"}', 'PRD 04 R3 / #139 owns the destination.');
122
+ SELECT migration._allow('price_table_items', 'catalog.price_table_items', 'catalog', NULL, '{}', '{}', '{"uuid":"id","price_table_id":"price_table_id@price_tables","service_id":"product_id@services","table_price":"price"}', 'PRD 04 R3.');
123
+ SELECT migration._allow('price_variations', 'catalog.price_variations', 'catalog', NULL, '{}', '{}', '{"uuid":"id","variation_type":"kind","value_type":"value_type","value":"value","first_appointment_only":"jsonb:rules.first_appointment_only","active":"is_active"}', 'PRD 04 R3.');
124
+ SELECT migration._allow('service_packages', 'catalog.packages', 'catalog', NULL, '{}', '{}', '{"uuid":"id","name":"name","description":"description","total_price":"price","status":"status","validity_days":"jsonb:validity.days","validity_months":"jsonb:validity.months","validity_enabled":"jsonb:validity.enabled","replaces_uuid":"jsonb:v1_replaces","replaced_by_uuid":"jsonb:v1_replaced_by","active":"is_active"}', 'PRD 04 R4: packages facet — contract only, Phase 3 uses.');
125
+ SELECT migration._allow('service_package_items', 'catalog.package_items', 'catalog', NULL, '{}', '{}', '{"uuid":"id","package_id":"package_id@service_packages","service_id":"product_id@services","product_id":"product_id@products","item_type":"item_type","quantity":"quantity","unit_price":"unit_price","package_price":"price"}', 'PRD 04 R4.');
126
+ SELECT migration._allow('package_settings', 'catalog.packages', 'catalog', NULL, '{}', '{}', '{}', 'PRD 04 R4: tenant-level package settings → configuration.');
127
+ SELECT migration._allow('package_unit_shares', 'catalog.packages', 'catalog', NULL, '{}', '{}', '{}', 'PRD 04 R4: package revenue share by unit → facet.');
128
+ SELECT migration._allow('discount_reasons', 'config.orders', 'catalog', NULL, '{}', '{}', '{"uuid":"id","name":"name"}', 'PRD 04 R7: configuration of the owning plugin (orders/financial), not catalog.');
129
+ SELECT migration._allow('discount_users', 'config.orders', 'catalog', NULL, '{}', '{}', '{}', 'PRD 04 R7.');
130
+ SELECT migration._allow('cancellation_reasons', 'config.orders', 'catalog', NULL, '{}', '{}', '{"uuid":"id","name":"name"}', 'PRD 04 R7.');
131
+ SELECT migration._allow('cancellation_users', 'config.orders', 'catalog', NULL, '{}', '{}', '{}', 'PRD 04 R7.');
132
+
133
+ -- ── financial base ──────────────────────────────────────────────────────
134
+ SELECT migration._allow('invoices', 'public.orders', 'financial', 'order',
135
+ '{"kind":"sale","status_map":{"draft":"draft","canceled":"cancelled","cancelled":"cancelled","*":"completed"},"split_invoice":true}',
136
+ ARRAY['subtotal', 'discount', 'tax', 'total'],
137
+ '{"uuid":"id","tenant_id":"-","type":"jsonb:financial.v1_type","status":"status","invoice_date":"completed_at","total_amount":"total","paid_amount":"jsonb:financial.v1_paid_amount","total_installments":"jsonb:financial.v1_total_installments","client_id":"party_id@clients","unit_id":"unit_id@companies","cash_session_id":"jsonb:financial.v1_cash_session_ref","account_central_id":"jsonb:financial.v1_account_central_ref","opportunity_id":"jsonb:crm.v1_opportunity_ref","fiscal_number":"reference_number","observations":"notes","cancel_reason":"jsonb:cancellation.reason","cancel_reason_id":"jsonb:cancellation.v1_reason_ref","canceled_at":"jsonb:cancellation.at","canceled_by":"-"}',
138
+ 'ADR 0004 / PRD 05 R1+R13: an invoice of type service/receivable is a Venda (orders) + a receivable invoice (plg_financial_invoices, second ledger row, split); payable/purchase is an invoice only. Financial columns are never overwritten once written (value quarantine). V1 status is kept in metadata.migration.source_status.');
139
+ SELECT migration._allow('invoice_items', 'public.order_items', 'financial', NULL, '{}', ARRAY['unit_price', 'discount', 'total'],
140
+ '{"uuid":"id","invoice_id":"order_id@invoices","item_type":"jsonb:item_type","service_id":"product_id@services","product_id":"product_id@products","service_package_id":"jsonb:v1_package_ref","description":"name","quantity":"quantity","unit_price":"unit_price","discount":"discount","additional":"jsonb:financial.additional","total_amount":"total","is_executed":"jsonb:execution.is_executed","execution_date":"jsonb:execution.date","execution_start_time":"jsonb:execution.start_time","execution_end_time":"jsonb:execution.end_time","executed_by_professional_id":"jsonb:execution.v1_professional_ref","execution_photos":"-","appointment_id":"jsonb:agenda.v1_appointment_ref","gift_card_id":"jsonb:v1_gift_card_ref","canceled_at":"jsonb:cancellation.at","cancel_reason":"jsonb:cancellation.reason","account_id":"jsonb:financial.v1_account_ref","cost_center_id":"jsonb:financial.v1_cost_center_ref"}',
141
+ 'PRD 05 R3 / #140: order_items with scaffold columns; execution state → column when the vertical uses it (ADR 0009); execution status must follow the EF lesson #1 (antecipated-sale item status > procedure status), never "has a procedure id".');
142
+ SELECT migration._allow('invoice_items_discounted', 'public.order_items', 'financial', NULL, '{}', '{}', '{}', 'Facet of the item.');
143
+ SELECT migration._allow('invoice_item_adjustments', 'public.order_items', 'financial', NULL, '{}', '{}', '{}', 'Facet of the item.');
144
+ SELECT migration._allow('invoice_item_participants', 'public.order_items', 'financial', NULL, '{}', '{}', '{}', 'Facet of the item (commission participants).');
145
+ SELECT migration._allow('invoice_cancellation_logs', 'public.audit_logs', 'financial', NULL, '{}', '{}', '{}', 'Append-only history → audit.');
146
+ SELECT migration._allow('invoice_item_cancellation_logs', 'public.audit_logs', 'financial', NULL, '{}', '{}', '{}', 'Append-only history → audit.');
147
+ SELECT migration._allow('account_central', 'public.plg_financial_accounts', 'financial', NULL, '{}', '{}', '{"uuid":"id","name":"name","account_type":"kind"}', 'PRD 05 R6: an account of the two-leg ledger (#142).');
148
+ SELECT migration._allow('bank_accounts', 'public.plg_financial_accounts', 'financial', NULL, '{}', ARRAY['initial_balance'],
149
+ '{"uuid":"id","name":"name","account_type":"kind","bank_code":"jsonb:bank.code","bank_name":"jsonb:bank.name","agency_number":"jsonb:bank.agency","account_number":"jsonb:bank.account","holder":"jsonb:bank.holder","document":"jsonb:bank.document","initial_balance":"initial_balance","current_balance":"-","credit_limit":"jsonb:card.credit_limit","closing_day":"jsonb:card.closing_day","due_day":"jsonb:card.due_day","card_brand_id":"jsonb:card.v1_brand_ref","default_fee_rate":"jsonb:card.default_fee_rate","days_to_credit":"jsonb:card.days_to_credit","unit_id":"unit_id@companies","active":"is_active","integration_password":"-","cnab_agreement_code":"-","cnab_company_code":"-","cnab_next_sequence":"-"}',
150
+ 'PRD 05 R6/R13: bank_accounts by account_type → accounts (bank, cofre, cartão crédito/débito). Balances are views; current_balance is dropped and reconciled instead. Integration secrets never migrate.');
151
+ SELECT migration._allow('card_brands', 'public.plg_financial_card_brands', 'financial', NULL, '{}', '{}', '{"uuid":"id","name":"name"}', 'PRD 05 R11.');
152
+ SELECT migration._allow('cc_receiving_fee_configs', 'public.plg_financial_card_fee_configs', 'financial', NULL, '{}', '{}', '{}', 'PRD 05 R11: brand × installment range × fee per receiving account (#142).');
153
+ SELECT migration._allow('payment_methods', 'public.plg_financial_payment_methods', 'financial', NULL, '{}', '{}', '{"uuid":"id","name":"name","payment_method_type_id":"type_id@payment_method_types","max_installments":"jsonb:installments.max","min_installments":"jsonb:installments.min","discount":"jsonb:pricing.discount","interest":"jsonb:pricing.interest","active":"is_active"}', 'PRD 05.');
154
+ SELECT migration._allow('payment_method_types', 'public.plg_financial_payment_method_types', 'financial', NULL, '{}', '{}', '{"uuid":"id","name":"name"}', 'PRD 05.');
155
+ SELECT migration._allow('chart_of_accounts', 'public.plg_financial_chart_of_accounts', 'financial', NULL, '{}', '{}', '{"uuid":"id","code":"code","name":"name","account_type":"kind","parent_id":"parent_id@chart_of_accounts","active":"is_active"}', 'PRD 05.');
156
+ SELECT migration._allow('cost_centers', 'public.plg_financial_cost_centers', 'financial', NULL, '{}', '{}', '{"uuid":"id","code":"code","name":"name","description":"description","active":"is_active"}', 'PRD 05.');
157
+ SELECT migration._allow('financial_movements', 'public.plg_financial_movements', 'financial', NULL, '{}', ARRAY['amount', 'paid_amount'],
158
+ '{"uuid":"id","invoice_id":"financial_invoice_id@invoices","type":"movement_kind","movement_type":"jsonb:v1_movement_type","amount":"amount","paid_amount":"paid_amount","status":"status","due_date":"due_date","payment_date":"paid_at","installment_number":"installment_number","debit_account_id":"debit_account_id@account_central","credit_account_id":"credit_account_id@account_central","bank_account_id":"jsonb:v1_bank_account_ref","payment_method_id":"payment_method_id@payment_methods","payment_method_type_id":"payment_method_type_id@payment_method_types","card_brand":"jsonb:card.brand","card_installments":"jsonb:card.installments","cash_session_id":"cash_session_id@cash_register_sessions","parent_movement_id":"parent_movement_id@financial_movements","external_id":"external_id","external_source":"external_source","is_confirmed":"jsonb:confirmed","confirmed_at":"confirmed_at","barcode":"jsonb:barcode","notes":"notes","order_guest_id":"-"}',
159
+ 'PRD 05 R7/R13: append-only two-leg movements (debit + credit account). Never overwritten. Paid invoices without movements receive an identifiable synthetic payment (Crédito Legado, R14) — the extractor emits it, the destination records it as a normal row.');
160
+ SELECT migration._allow('payment_allocations', 'public.plg_financial_allocations', 'financial', NULL, '{}', ARRAY['allocated_amount'],
161
+ '{"uuid":"id","payment_movement_id":"payment_movement_id@financial_movements","bill_movement_id":"installment_id@financial_movements","allocated_amount":"allocated_amount","origin":"origin"}', 'PRD 05 R8.');
162
+ SELECT migration._allow('payment_deletion_logs', 'public.audit_logs', 'financial', NULL, '{}', '{}', '{}', 'Append-only history → audit.');
163
+ SELECT migration._allow('payment_unlink_logs', 'public.audit_logs', 'financial', NULL, '{}', '{}', '{}', 'Append-only history → audit.');
164
+ SELECT migration._allow('cash_register_sessions', 'public.plg_financial_cash_register_sessions', 'financial', NULL, '{}', ARRAY['opening_balance', 'closing_balance', 'counted_cash', 'counted_card'],
165
+ '{"uuid":"id","unit_id":"unit_id@companies","bank_account_id":"account_id@bank_accounts","opened_at":"opened_at","opened_by_user_id":"jsonb:v1_opened_by","closed_at":"closed_at","closed_by_user_id":"jsonb:v1_closed_by","opening_balance":"opening_balance","closing_balance":"closing_balance","expected_cash":"expected_cash","expected_card":"expected_card","counted_cash":"counted_cash","counted_card":"counted_card","difference_cash":"difference_cash","difference_card":"difference_card","status":"status","review_status":"jsonb:review.status","notes":"notes"}',
166
+ 'PRD 05 R10: caixinha per user and unit.');
167
+ SELECT migration._allow('cash_session_reviewers', 'public.plg_financial_cash_register_sessions', 'financial', NULL, '{}', '{}', '{}', 'PRD 05 R10: optional reviewers → facet.');
168
+ SELECT migration._allow('client_credits', 'derived', 'financial', NULL, '{"verify_only":true}', ARRAY['amount'], '{"client_id":"party_id@clients","amount":"amount"}', 'PRD 05 R9/R13: customer credit is the balance of the Person account (view) — not written, VERIFIED by the reconciliation.');
169
+ SELECT migration._allow('client_credit_wallets', 'derived', 'financial', NULL, '{"verify_only":true}', ARRAY['balance'], '{}', 'PRD 05 R13: derived; verified, not written.');
170
+ SELECT migration._allow('client_credit_ledger', 'derived', 'financial', NULL, '{"verify_only":true}', ARRAY['amount'], '{}', 'PRD 05 R13: derived; verified, not written.');
171
+ SELECT migration._allow('bill_attachments', 'storage', 'financial', NULL, '{}', '{}', '{}', 'D13 minimum: invoice attachments into the classified bucket; files move out of band.');
172
+ SELECT migration._allow('recurring_bills', 'public.plg_financial_recurring_bills', 'financial', NULL, '{}', ARRAY['amount'], '{}', 'PRD 05: recurring payables (#141/#142 own the destination).');
173
+ SELECT migration._allow('recurring_bill_items', 'public.plg_financial_recurring_bills', 'financial', NULL, '{}', '{}', '{}', 'PRD 05.');
174
+ SELECT migration._allow('recurring_bill_occurrences', 'public.plg_financial_recurring_bills', 'financial', NULL, '{}', '{}', '{}', 'PRD 05.');
175
+
176
+ -- ── inventory base ──────────────────────────────────────────────────────
177
+ SELECT migration._allow('stock_locations', 'public.plg_inventory_stock_locations', 'inventory', NULL, '{}', '{}', '{"uuid":"id","name":"name","description":"description","unit_id":"unit_id@companies","active":"is_active"}', 'PRD 06 R1: locations per unit (unit_id mandatory).');
178
+ SELECT migration._allow('stock_positions', 'public.plg_inventory_stock_positions', 'inventory', NULL, '{}', ARRAY['quantity', 'unit_cost'],
179
+ '{"uuid":"id","product_id":"product_id@products","stock_location_id":"location_id@stock_locations","unit_id":"unit_id@companies","batch_number":"batch","expiration_date":"expires_at","quantity":"quantity","unit_cost":"unit_cost","unit_type":"jsonb:unit_type","location":"jsonb:v1_location_label"}', 'PRD 06 R1: position per lot/location/cost.');
180
+ SELECT migration._allow('stock_movements', 'public.plg_inventory_stock_movements', 'inventory', NULL, '{}', ARRAY['quantity', 'unit_cost', 'total_cost'],
181
+ '{"uuid":"id","product_id":"product_id@products","stock_location_id":"from_location_id@stock_locations","destination_stock_location_id":"to_location_id@stock_locations","stock_position_id":"position_id@stock_positions","unit_id":"unit_id@companies","destination_unit_id":"to_unit_id@companies","movement_type":"kind","quantity":"quantity","original_quantity":"jsonb:original_quantity","unit_cost":"unit_cost","total_cost":"total_cost","movement_date":"moved_at","document_number":"document_number","reason":"reason","notes":"notes","supplier_id":"jsonb:v1_supplier_ref","appointment_id":"jsonb:agenda.v1_appointment_ref","danfe_import_item_id":"-","unit_label_id":"jsonb:v1_unit_label_ref","user_id":"jsonb:v1_user_id"}', 'PRD 06 R1: append-only movements with reversal.');
182
+ SELECT migration._allow('stock_unit_labels', 'public.plg_inventory_stock_unit_labels', 'inventory', NULL, '{}', '{}', '{}', 'PRD 06 R5.');
183
+ SELECT migration._allow('supplier_product_mappings', 'public.plg_inventory_supplier_products', 'inventory', NULL, '{}', '{}', '{"product_id":"product_id@products","supplier_id":"supplier_person_id@contacts"}', 'PRD 06 R5: supplier reference = Person.');
184
+
185
+ -- ── never sent ──────────────────────────────────────────────────────────
186
+ INSERT INTO migration.excluded_tables (source_table, reason) VALUES
187
+ ('orders', 'V1 legacy table without app use (references only in CRM); the V2 orders table is a different thing (Venda). Confirm empty in production.'),
188
+ ('guests', 'V1 legacy table without app use.'),
189
+ ('payments', 'V1 legacy table without app use; money lives in financial_movements.'),
190
+ ('financial_accounts', 'V1 legacy table without app use; superseded by account_central/bank_accounts.'),
191
+ ('financial_transactions', 'V1 legacy table without app use; superseded by financial_movements.'),
192
+ ('transaction_categories', 'V1 legacy table without app use.'),
193
+ ('transaction_category_relations', 'V1 legacy table without app use.'),
194
+ ('sales_journey', 'V1 legacy table without app use.'),
195
+ ('pending_appointments', 'V1 legacy table without app use.'),
196
+ ('for', 'V1 legacy table without app use (accidental name).'),
197
+ ('to', 'V1 legacy table without app use (accidental name).'),
198
+ ('user_roles', 'Legacy role model without use in the V1 engine (core-beauty-chef §3.1); RBAC comes from the permission catalog (#136).'),
199
+ ('import_ef_*', 'The previous vendor''s one-off import for one specific network: never ported (core-beauty-chef §3.3). Any source_table starting with import_ef_ is refused.')
200
+ ON CONFLICT (source_table) DO UPDATE SET reason = EXCLUDED.reason;
201
+
202
+ SELECT set_config('migration.allowlist_unlock', 'off', false);