@flowselections/floriday-voorraad 1.0.145 → 1.0.149

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 (100) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.d.ts +3 -1
  2. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  3. package/dist-lib/components/voorraad/ArtikelWizard.js +56 -52
  4. package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
  5. package/dist-lib/components/voorraad/EditArtikelPage.js +15 -23
  6. package/dist-lib/components/voorraad/OrderHistoryCard.d.ts +3 -1
  7. package/dist-lib/components/voorraad/OrderHistoryCard.d.ts.map +1 -1
  8. package/dist-lib/components/voorraad/OrderHistoryCard.js +7 -3
  9. package/dist-lib/components/voorraad/ProductCustomFieldsCard.d.ts +5 -1
  10. package/dist-lib/components/voorraad/ProductCustomFieldsCard.d.ts.map +1 -1
  11. package/dist-lib/components/voorraad/ProductCustomFieldsCard.js +37 -18
  12. package/dist-lib/components/voorraad/ProductSalePriceField.d.ts.map +1 -1
  13. package/dist-lib/components/voorraad/ProductSalePriceField.js +11 -8
  14. package/dist-lib/components/voorraad/StepSamenstelling.d.ts.map +1 -1
  15. package/dist-lib/components/voorraad/StepSamenstelling.js +35 -10
  16. package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts +3 -1
  17. package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
  18. package/dist-lib/components/voorraad/StockMutationHistoryCard.js +16 -10
  19. package/dist-lib/components/voorraad/TrayStickerCard.d.ts +5 -1
  20. package/dist-lib/components/voorraad/TrayStickerCard.d.ts.map +1 -1
  21. package/dist-lib/components/voorraad/TrayStickerCard.js +64 -66
  22. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts +0 -5
  23. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
  24. package/dist-lib/components/voorraad/ValidationOverviewTable.js +10 -4
  25. package/dist-lib/components/voorraad/VoorraadBeheerCard.d.ts +3 -0
  26. package/dist-lib/components/voorraad/VoorraadBeheerCard.d.ts.map +1 -0
  27. package/dist-lib/components/voorraad/VoorraadBeheerCard.js +44 -0
  28. package/dist-lib/components/voorraad/VoorraadPage.d.ts +1 -13
  29. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  30. package/dist-lib/components/voorraad/VoorraadPage.js +102 -140
  31. package/dist-lib/components/voorraad/VoorraadSturingCard.d.ts +6 -0
  32. package/dist-lib/components/voorraad/VoorraadSturingCard.d.ts.map +1 -0
  33. package/dist-lib/components/voorraad/VoorraadSturingCard.js +47 -0
  34. package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts +6 -0
  35. package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts.map +1 -0
  36. package/dist-lib/components/voorraad/artikel/ArtikelPagina.js +313 -0
  37. package/dist-lib/components/voorraad/artikel/TwijfelKiezen.d.ts +7 -0
  38. package/dist-lib/components/voorraad/artikel/TwijfelKiezen.d.ts.map +1 -0
  39. package/dist-lib/components/voorraad/artikel/TwijfelKiezen.js +54 -0
  40. package/dist-lib/components/voorraad/artikel/opslaan-context.d.ts +21 -0
  41. package/dist-lib/components/voorraad/artikel/opslaan-context.d.ts.map +1 -0
  42. package/dist-lib/components/voorraad/artikel/opslaan-context.js +15 -0
  43. package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts +5 -0
  44. package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts.map +1 -0
  45. package/dist-lib/components/voorraad/lijst/ImporteerKnop.js +20 -0
  46. package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts +6 -0
  47. package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts.map +1 -0
  48. package/dist-lib/components/voorraad/lijst/LijstExportKnop.js +43 -0
  49. package/dist-lib/components/voorraad/lijst/SnelActieDialogen.d.ts +16 -0
  50. package/dist-lib/components/voorraad/lijst/SnelActieDialogen.d.ts.map +1 -0
  51. package/dist-lib/components/voorraad/lijst/SnelActieDialogen.js +95 -0
  52. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts +21 -0
  53. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts.map +1 -0
  54. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.js +39 -0
  55. package/dist-lib/hooks/useVoorraadCustomFields.d.ts +2 -1
  56. package/dist-lib/hooks/useVoorraadCustomFields.d.ts.map +1 -1
  57. package/dist-lib/hooks/useVoorraadCustomFields.js +8 -4
  58. package/dist-lib/hooks/useVoorraadLijst.d.ts +21 -0
  59. package/dist-lib/hooks/useVoorraadLijst.d.ts.map +1 -0
  60. package/dist-lib/hooks/useVoorraadLijst.js +71 -0
  61. package/dist-lib/index.d.ts.map +1 -1
  62. package/dist-lib/index.js +4 -0
  63. package/dist-lib/integrations/supabase/types.d.ts +748 -1
  64. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  65. package/dist-lib/lib/artikel-gewijzigd.d.ts +4 -0
  66. package/dist-lib/lib/artikel-gewijzigd.d.ts.map +1 -0
  67. package/dist-lib/lib/artikel-gewijzigd.js +7 -0
  68. package/dist-lib/lib/artikel-pagina.d.ts +68 -0
  69. package/dist-lib/lib/artikel-pagina.d.ts.map +1 -0
  70. package/dist-lib/lib/artikel-pagina.js +97 -0
  71. package/dist-lib/lib/count-stock.d.ts +17 -0
  72. package/dist-lib/lib/count-stock.d.ts.map +1 -0
  73. package/dist-lib/lib/count-stock.js +26 -0
  74. package/dist-lib/lib/floriday-import-validator.d.ts +9 -0
  75. package/dist-lib/lib/floriday-import-validator.d.ts.map +1 -1
  76. package/dist-lib/lib/floriday-import-validator.js +13 -0
  77. package/dist-lib/lib/floriday-versie-ronde.d.ts +59 -0
  78. package/dist-lib/lib/floriday-versie-ronde.d.ts.map +1 -0
  79. package/dist-lib/lib/floriday-versie-ronde.functions.d.ts +10 -0
  80. package/dist-lib/lib/floriday-versie-ronde.functions.d.ts.map +1 -0
  81. package/dist-lib/lib/floriday-versie-ronde.functions.js +22 -0
  82. package/dist-lib/lib/floriday-versie-ronde.js +106 -0
  83. package/dist-lib/lib/pending-photos.d.ts +20 -0
  84. package/dist-lib/lib/pending-photos.d.ts.map +1 -0
  85. package/dist-lib/lib/pending-photos.js +90 -0
  86. package/dist-lib/lib/voorraad-custom-fields.d.ts +16 -1
  87. package/dist-lib/lib/voorraad-custom-fields.d.ts.map +1 -1
  88. package/dist-lib/lib/voorraad-custom-fields.js +12 -6
  89. package/dist-lib/lib/voorraad-lijst.d.ts +92 -0
  90. package/dist-lib/lib/voorraad-lijst.d.ts.map +1 -0
  91. package/dist-lib/lib/voorraad-lijst.js +86 -0
  92. package/dist-lib/server-hooks/floriday-stock-reconcile.d.ts.map +1 -1
  93. package/dist-lib/server-hooks/floriday-stock-reconcile.js +5 -0
  94. package/dist-lib/server-hooks/floriday-versieronde.d.ts +2 -0
  95. package/dist-lib/server-hooks/floriday-versieronde.d.ts.map +1 -0
  96. package/dist-lib/server-hooks/floriday-versieronde.js +29 -0
  97. package/dist-lib/server-hooks/guard.js +1 -1
  98. package/package.json +1 -1
  99. package/sql/AGENTS.md +6 -0
  100. package/sql/floriday-voorraad-schema.sql +1355 -22
@@ -1,9 +1,24 @@
1
- -- @flowselections/floriday-voorraad 1.0.144 – volledig schema (idempotent, geen gegevens).
2
- -- Laatste migratie: 20261102161116_305f3804-253b-46f3-b4e7-2b25fa06c211.sql
1
+ -- @flowselections/floriday-voorraad 1.0.148 – volledig schema (idempotent, geen gegevens).
2
+ -- Laatste migratie: 20261104132044_b849caf0-1e96-4514-99da-94e4074d56b0.sql
3
3
  -- Gegenereerd uit de database met scripts/generate-voorraad-schema.py. Niet met de hand wijzigen.
4
4
  -- Volgorde 20. Ontbrekende tabellen van andere modules worden overgeslagen met een NOTICE; draai opnieuw na die module.
5
5
  -- De statuscontrole van floriday_continuous_stock_queue is van Voorraad en wordt hier opnieuw gemaakt.
6
6
  SET check_function_bodies = off;
7
+ -- eigen kolommen/indexen op tabellen van andere modules
8
+ DO $fk$ BEGIN IF to_regclass('public.products') IS NOT NULL THEN EXECUTE 'CREATE INDEX IF NOT EXISTS idx_products_trade_item_key ON public.products(trade_item_key);'; ELSE RAISE NOTICE 'Voorraad: tabel products ontbreekt, overgeslagen'; END IF; END $fk$;
9
+ DO $fk$ BEGIN IF to_regclass('public.product_groups') IS NOT NULL THEN EXECUTE 'CREATE UNIQUE INDEX IF NOT EXISTS product_groups_one_active_per_head_product ON public.product_groups(head_product_id) WHERE is_active AND head_product_id IS NOT NULL;'; ELSE RAISE NOTICE 'Voorraad: tabel product_groups ontbreekt, overgeslagen'; END IF; END $fk$;
10
+ DO $fk$ BEGIN IF to_regclass('public.product_groups') IS NOT NULL THEN EXECUTE 'CREATE UNIQUE INDEX IF NOT EXISTS product_groups_one_active_per_head_floriday ON public.product_groups(head_trade_item_floriday_id) WHERE is_active AND head_trade_item_floriday_id IS NOT NULL;'; ELSE RAISE NOTICE 'Voorraad: tabel product_groups ontbreekt, overgeslagen'; END IF; END $fk$;
11
+ DO $fk$ BEGIN IF to_regclass('public.product_groups') IS NOT NULL THEN EXECUTE 'CREATE UNIQUE INDEX IF NOT EXISTS product_groups_active_name_uq ON public.product_groups (lower(btrim(name))) WHERE is_active IS TRUE;'; ELSE RAISE NOTICE 'Voorraad: tabel product_groups ontbreekt, overgeslagen'; END IF; END $fk$;
12
+ DO $fk$ BEGIN IF to_regclass('public.products') IS NOT NULL THEN EXECUTE 'ALTER TABLE public.products ADD COLUMN IF NOT EXISTS floriday_velden_aangepast text[];'; ELSE RAISE NOTICE 'Voorraad: tabel products ontbreekt, overgeslagen'; END IF; END $fk$;
13
+ DO $fk$ BEGIN IF to_regclass('public.products') IS NOT NULL THEN EXECUTE 'ALTER TABLE public.products ADD COLUMN IF NOT EXISTS floriday_verwijderd_at timestamptz;'; ELSE RAISE NOTICE 'Voorraad: tabel products ontbreekt, overgeslagen'; END IF; END $fk$;
14
+ DO $fk$ BEGIN IF to_regclass('public.products') IS NOT NULL THEN EXECUTE 'ALTER TABLE public.products ADD COLUMN IF NOT EXISTS gearchiveerd_at timestamptz;'; ELSE RAISE NOTICE 'Voorraad: tabel products ontbreekt, overgeslagen'; END IF; END $fk$;
15
+ DO $fk$ BEGIN IF to_regclass('public.products') IS NOT NULL THEN EXECUTE 'ALTER TABLE public.products ADD COLUMN IF NOT EXISTS gearchiveerd_door uuid;'; ELSE RAISE NOTICE 'Voorraad: tabel products ontbreekt, overgeslagen'; END IF; END $fk$;
16
+ DO $fk$ BEGIN IF to_regclass('public.products') IS NOT NULL THEN EXECUTE 'CREATE INDEX IF NOT EXISTS products_gearchiveerd_at_idx ON public.products (gearchiveerd_at) WHERE gearchiveerd_at IS NOT NULL;'; ELSE RAISE NOTICE 'Voorraad: tabel products ontbreekt, overgeslagen'; END IF; END $fk$;
17
+ DO $fk$ BEGIN IF to_regclass('public.product_bom_items') IS NOT NULL THEN EXECUTE 'CREATE INDEX IF NOT EXISTS product_bom_items_component_idx ON public.product_bom_items (component_product_id);'; ELSE RAISE NOTICE 'Voorraad: tabel product_bom_items ontbreekt, overgeslagen'; END IF; END $fk$;
18
+ DO $fk$ BEGIN IF to_regclass('public.product_bom_items') IS NOT NULL THEN EXECUTE 'CREATE INDEX IF NOT EXISTS product_bom_items_parent_idx ON public.product_bom_items (parent_product_id);'; ELSE RAISE NOTICE 'Voorraad: tabel product_bom_items ontbreekt, overgeslagen'; END IF; END $fk$;
19
+ DO $fk$ BEGIN IF to_regclass('public.products') IS NOT NULL THEN EXECUTE 'CREATE INDEX IF NOT EXISTS products_id_text_idx ON public.products ((id::text));'; ELSE RAISE NOTICE 'Voorraad: tabel products ontbreekt, overgeslagen'; END IF; END $fk$;
20
+ DO $fk$ BEGIN IF to_regclass('public.products') IS NOT NULL THEN EXECUTE 'ALTER TABLE public.products ADD COLUMN IF NOT EXISTS gearchiveerd_floriday_nul boolean NOT NULL DEFAULT false;'; ELSE RAISE NOTICE 'Voorraad: tabel products ontbreekt, overgeslagen'; END IF; END $fk$;
21
+ DO $fk$ BEGIN IF to_regclass('public.floriday_salesorders_cache') IS NOT NULL THEN EXECUTE 'CREATE INDEX IF NOT EXISTS voorraad_fsc_floriday_id_idx ON public.floriday_salesorders_cache (floriday_id);'; ELSE RAISE NOTICE 'Voorraad: tabel floriday_salesorders_cache ontbreekt, overgeslagen'; END IF; END $fk$;
7
22
 
8
23
  -- floriday_continuous_stock_cache
9
24
  CREATE TABLE IF NOT EXISTS public.floriday_continuous_stock_cache ();
@@ -564,12 +579,81 @@ ALTER TABLE public.voorraad_defaults_state ADD COLUMN IF NOT EXISTS versie integ
564
579
  ALTER TABLE public.voorraad_defaults_state ADD COLUMN IF NOT EXISTS toegepast_at timestamp with time zone DEFAULT now() NOT NULL;
565
580
  ALTER TABLE public.voorraad_defaults_state ADD COLUMN IF NOT EXISTS resultaat jsonb DEFAULT '{}'::jsonb NOT NULL;
566
581
 
582
+ -- voorraad_floriday_alias
583
+ CREATE TABLE IF NOT EXISTS public.voorraad_floriday_alias ();
584
+ ALTER TABLE public.voorraad_floriday_alias ADD COLUMN IF NOT EXISTS floriday_id text NOT NULL;
585
+ ALTER TABLE public.voorraad_floriday_alias ADD COLUMN IF NOT EXISTS product_id uuid NOT NULL;
586
+ ALTER TABLE public.voorraad_floriday_alias ADD COLUMN IF NOT EXISTS bron text NOT NULL;
587
+ ALTER TABLE public.voorraad_floriday_alias ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
588
+
567
589
  -- voorraad_floriday_artikel_wachtrij
568
590
  CREATE TABLE IF NOT EXISTS public.voorraad_floriday_artikel_wachtrij ();
569
591
  ALTER TABLE public.voorraad_floriday_artikel_wachtrij ADD COLUMN IF NOT EXISTS floriday_id text NOT NULL;
570
592
  ALTER TABLE public.voorraad_floriday_artikel_wachtrij ADD COLUMN IF NOT EXISTS connection_id uuid;
571
593
  ALTER TABLE public.voorraad_floriday_artikel_wachtrij ADD COLUMN IF NOT EXISTS aangemeld_at timestamp with time zone DEFAULT now() NOT NULL;
572
594
 
595
+ -- voorraad_floriday_versie_fouten
596
+ CREATE TABLE IF NOT EXISTS public.voorraad_floriday_versie_fouten ();
597
+ ALTER TABLE public.voorraad_floriday_versie_fouten ADD COLUMN IF NOT EXISTS connection_id uuid NOT NULL;
598
+ ALTER TABLE public.voorraad_floriday_versie_fouten ADD COLUMN IF NOT EXISTS floriday_id text NOT NULL;
599
+ ALTER TABLE public.voorraad_floriday_versie_fouten ADD COLUMN IF NOT EXISTS opeenvolgend integer DEFAULT 0 NOT NULL;
600
+ ALTER TABLE public.voorraad_floriday_versie_fouten ADD COLUMN IF NOT EXISTS laatste_reden text;
601
+ ALTER TABLE public.voorraad_floriday_versie_fouten ADD COLUMN IF NOT EXISTS onbekend_sinds timestamp with time zone;
602
+ ALTER TABLE public.voorraad_floriday_versie_fouten ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT now() NOT NULL;
603
+
604
+ -- voorraad_floriday_versie_rondes
605
+ CREATE TABLE IF NOT EXISTS public.voorraad_floriday_versie_rondes ();
606
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS id uuid DEFAULT gen_random_uuid() NOT NULL;
607
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS connection_id uuid;
608
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS uitgevoerd_at timestamp with time zone DEFAULT clock_timestamp() NOT NULL;
609
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS volledig boolean DEFAULT false NOT NULL;
610
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS fouten integer DEFAULT 0 NOT NULL;
611
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS gecontroleerd integer DEFAULT 0 NOT NULL;
612
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS in_cache integer DEFAULT 0 NOT NULL;
613
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS actueel integer DEFAULT 0 NOT NULL;
614
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS vervangen integer DEFAULT 0 NOT NULL;
615
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS vervallen integer DEFAULT 0 NOT NULL;
616
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS gemarkeerd boolean DEFAULT false NOT NULL;
617
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS melding text;
618
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
619
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS status text;
620
+ ALTER TABLE public.voorraad_floriday_versie_rondes ADD COLUMN IF NOT EXISTS onbekend integer DEFAULT 0 NOT NULL;
621
+
622
+ -- voorraad_floriday_versieronde_stand
623
+ CREATE TABLE IF NOT EXISTS public.voorraad_floriday_versieronde_stand ();
624
+ ALTER TABLE public.voorraad_floriday_versieronde_stand ADD COLUMN IF NOT EXISTS connection_id uuid NOT NULL;
625
+ ALTER TABLE public.voorraad_floriday_versieronde_stand ADD COLUMN IF NOT EXISTS ronde_id uuid;
626
+ ALTER TABLE public.voorraad_floriday_versieronde_stand ADD COLUMN IF NOT EXISTS gestart_at timestamp with time zone;
627
+ ALTER TABLE public.voorraad_floriday_versieronde_stand ADD COLUMN IF NOT EXISTS laatste_poging_at timestamp with time zone;
628
+ ALTER TABLE public.voorraad_floriday_versieronde_stand ADD COLUMN IF NOT EXISTS laatste_einde_at timestamp with time zone;
629
+ ALTER TABLE public.voorraad_floriday_versieronde_stand ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT now() NOT NULL;
630
+
631
+ -- voorraad_floriday_versieronde_uitkomst
632
+ CREATE TABLE IF NOT EXISTS public.voorraad_floriday_versieronde_uitkomst ();
633
+ ALTER TABLE public.voorraad_floriday_versieronde_uitkomst ADD COLUMN IF NOT EXISTS ronde_id uuid NOT NULL;
634
+ ALTER TABLE public.voorraad_floriday_versieronde_uitkomst ADD COLUMN IF NOT EXISTS floriday_id text NOT NULL;
635
+ ALTER TABLE public.voorraad_floriday_versieronde_uitkomst ADD COLUMN IF NOT EXISTS status text NOT NULL;
636
+ ALTER TABLE public.voorraad_floriday_versieronde_uitkomst ADD COLUMN IF NOT EXISTS opvolger_id text;
637
+ ALTER TABLE public.voorraad_floriday_versieronde_uitkomst ADD COLUMN IF NOT EXISTS bron text;
638
+ ALTER TABLE public.voorraad_floriday_versieronde_uitkomst ADD COLUMN IF NOT EXISTS reden text;
639
+ ALTER TABLE public.voorraad_floriday_versieronde_uitkomst ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
640
+
641
+ -- voorraad_floriday_vervallen
642
+ CREATE TABLE IF NOT EXISTS public.voorraad_floriday_vervallen ();
643
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS floriday_id text NOT NULL;
644
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS connection_id uuid;
645
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS status text NOT NULL;
646
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS opvolger_id text;
647
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS bron text NOT NULL;
648
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS ronde_id uuid;
649
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS vastgesteld_at timestamp with time zone DEFAULT now() NOT NULL;
650
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS verwerkt_at timestamp with time zone;
651
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS uitkomst text;
652
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS product_id uuid;
653
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS opgeheven_at timestamp with time zone;
654
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
655
+ ALTER TABLE public.voorraad_floriday_vervallen ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT now() NOT NULL;
656
+
573
657
  -- voorraad_instellingen
574
658
  CREATE TABLE IF NOT EXISTS public.voorraad_instellingen ();
575
659
  ALTER TABLE public.voorraad_instellingen ADD COLUMN IF NOT EXISTS id boolean DEFAULT true NOT NULL;
@@ -1085,6 +1169,10 @@ BEGIN
1085
1169
  IF v_type IS DISTINCT FROM 'GROUP' AND cardinality(public.product_member_group_ids(p_product_id)) > 0 THEN
1086
1170
  desired_quantity := 0; desired_availability := false; reason := 'GROUP_MEMBER_OFF'; RETURN NEXT; RETURN;
1087
1171
  END IF;
1172
+ -- 1.0.146: gearchiveerd met "Ook in Floriday" = 0 op Floriday (nooit raisen).
1173
+ IF EXISTS (SELECT 1 FROM public.products ap WHERE ap.id = p_product_id AND ap.gearchiveerd_at IS NOT NULL AND ap.gearchiveerd_floriday_nul) THEN
1174
+ desired_quantity := 0; desired_availability := false; reason := 'ARCHIVED'; RETURN NEXT; RETURN;
1175
+ END IF;
1088
1176
  IF stock_mode = 'DISABLED' THEN
1089
1177
  desired_quantity := 0; desired_availability := false; RETURN NEXT; RETURN;
1090
1178
  END IF;
@@ -2994,7 +3082,7 @@ CREATE OR REPLACE FUNCTION public.floriday_desired_stock_regression_check()
2994
3082
  AS $function$
2995
3083
  WITH b AS (
2996
3084
  SELECT s.connection_id, s.product_id, p.product, s.stock_mode, s.buffer_quantity, s.maximum_quantity, s.fixed_quantity, s.manual_quantity,
2997
- public.product_primary_pieces(p.id) AS pack,
3085
+ public.product_primary_pieces(p.id) AS pack, (p.gearchiveerd_at IS NOT NULL AND p.gearchiveerd_floriday_nul) AS archief_nul,
2998
3086
  (SELECT GREATEST(0, floor(ts.physical_stock - ts.reserved_stock))::int FROM public.trade_item_stock ts
2999
3087
  WHERE ts.connection_id = s.connection_id
3000
3088
  AND ts.trade_item_key = COALESCE(NULLIF(btrim(p.floriday_trade_item_id),''), NULLIF(btrim(p.trade_item_key),''))) AS avail
@@ -3006,6 +3094,7 @@ AS $function$
3006
3094
  ), e AS (
3007
3095
  SELECT b.*, (public.calculate_floriday_desired_stock(b.connection_id, b.product_id)).desired_quantity AS act,
3008
3096
  CASE WHEN b.avail IS NULL OR b.pack IS NULL THEN 0
3097
+ WHEN b.archief_nul THEN 0
3009
3098
  WHEN b.stock_mode = 'DISABLED' THEN 0
3010
3099
  ELSE GREATEST(0, (GREATEST(0, CASE b.stock_mode
3011
3100
  WHEN 'MAXIMUM' THEN LEAST(b.avail - b.buffer_quantity, COALESCE(b.maximum_quantity, b.avail - b.buffer_quantity))
@@ -3716,7 +3805,7 @@ CREATE OR REPLACE FUNCTION public.floriday_relink_products(p_connection_id uuid
3716
3805
  AS $function$
3717
3806
  DECLARE
3718
3807
  v_conn uuid; v_cache_count int; v_row RECORD; v_cf jsonb; v_cf_id text; v_match text;
3719
- v_linked int := 0; v_repaired int := 0; v_cleared int := 0;
3808
+ v_linked int := 0; v_verwijderd int := 0; v_repaired int := 0; v_cleared int := 0;
3720
3809
  BEGIN
3721
3810
  PERFORM public.voorraad_vereis('ingelogd', 'floriday_relink_products');
3722
3811
  v_conn := p_connection_id;
@@ -3730,7 +3819,7 @@ BEGIN
3730
3819
  SELECT count(*) INTO v_cache_count FROM public.floriday_trade_items_cache
3731
3820
  WHERE connection_id = v_conn AND COALESCE(is_deleted,false) = false;
3732
3821
 
3733
- FOR v_row IN SELECT id, trade_item_key, floriday_trade_item_id, custom_fields FROM public.products LOOP
3822
+ FOR v_row IN SELECT id, trade_item_key, floriday_trade_item_id, custom_fields, floriday_verwijderd_at FROM public.products LOOP
3734
3823
  v_cf := CASE WHEN jsonb_typeof(v_row.custom_fields) = 'object' THEN v_row.custom_fields ELSE '{}'::jsonb END;
3735
3824
  v_cf_id := NULLIF(btrim(COALESCE(v_cf->>'floriday_id','')), '');
3736
3825
  v_match := NULL;
@@ -3752,6 +3841,18 @@ BEGIN
3752
3841
  v_match := NULL;
3753
3842
  END IF;
3754
3843
 
3844
+ IF v_match IS NULL AND v_row.floriday_trade_item_id IS NOT NULL AND EXISTS (SELECT 1 FROM public.floriday_trade_items_cache c
3845
+ WHERE c.connection_id = v_conn AND lower(c.floriday_id) = lower(btrim(v_row.floriday_trade_item_id))
3846
+ AND (COALESCE(c.is_deleted,false) OR COALESCE((c.data->>'isDeleted')::boolean,false))) THEN
3847
+ -- Verwijderd in Floriday: koppeling behouden, artikel op niet actief (status "Verwijderd in Floriday").
3848
+ IF v_row.floriday_verwijderd_at IS NULL THEN
3849
+ UPDATE public.products SET floriday_verwijderd_at = now(), updated_at = now() WHERE id = v_row.id;
3850
+ INSERT INTO public.voorraad_koppel_log(run_id, soort, floriday_id, product_id, oud, nieuw)
3851
+ VALUES (NULL, 'niet_actief', v_row.floriday_trade_item_id, v_row.id, jsonb_build_object('floriday_verwijderd_at', NULL), jsonb_build_object('floriday_verwijderd_at', now(), 'bron', 'relink'));
3852
+ v_verwijderd := v_verwijderd + 1;
3853
+ END IF;
3854
+ CONTINUE;
3855
+ END IF;
3755
3856
  IF v_match IS NULL AND v_row.floriday_trade_item_id IS NOT NULL AND v_cache_count > 0 THEN
3756
3857
  UPDATE public.products SET floriday_trade_item_id = NULL,
3757
3858
  custom_fields = jsonb_set(v_cf, '{floriday_id}', 'null'::jsonb, true), updated_at = now()
@@ -3770,7 +3871,7 @@ BEGIN
3770
3871
  END LOOP;
3771
3872
 
3772
3873
  RETURN jsonb_build_object('ok', true, 'connection_id', v_conn, 'cache_items', v_cache_count,
3773
- 'linked', v_linked, 'repaired', v_repaired, 'cleared', v_cleared, 'match', 'floriday_id_only');
3874
+ 'linked', v_linked, 'repaired', v_repaired, 'cleared', v_cleared, 'verwijderd_in_floriday', v_verwijderd, 'match', 'floriday_id_only');
3774
3875
  END; $function$
3775
3876
  ;
3776
3877
  REVOKE ALL ON FUNCTION floriday_relink_products(uuid) FROM PUBLIC;
@@ -5894,6 +5995,41 @@ END $function$
5894
5995
  REVOKE ALL ON FUNCTION run_floriday_stock_reconcile() FROM PUBLIC;
5895
5996
  REVOKE ALL ON FUNCTION run_floriday_stock_reconcile() FROM anon;
5896
5997
  GRANT EXECUTE ON FUNCTION run_floriday_stock_reconcile() TO service_role;
5998
+ CREATE OR REPLACE FUNCTION public.run_floriday_versieronde(p_nieuw boolean DEFAULT false)
5999
+ RETURNS jsonb
6000
+ LANGUAGE plpgsql
6001
+ SECURITY DEFINER
6002
+ SET search_path TO 'public'
6003
+ AS $function$
6004
+ DECLARE v_conn uuid; v_bezig boolean; v_einde timestamptz; v_ok boolean; v_doen boolean;
6005
+ BEGIN
6006
+ PERFORM public.voorraad_vereis('server', 'run_floriday_versieronde');
6007
+ BEGIN SELECT x.connection_id INTO v_conn FROM public.get_active_floriday_context() x LIMIT 1;
6008
+ EXCEPTION WHEN others THEN v_conn := NULL; END;
6009
+ IF v_conn IS NULL THEN RETURN jsonb_build_object('ok', true, 'melding', 'geen Floriday-koppeling'); END IF;
6010
+ SELECT ronde_id IS NOT NULL, laatste_einde_at INTO v_bezig, v_einde FROM public.voorraad_floriday_versieronde_stand WHERE connection_id = v_conn;
6011
+ v_bezig := COALESCE(v_bezig, false);
6012
+ v_ok := public.voorraad_floriday_versieronde_geslaagd(v_conn);
6013
+ v_doen := p_nieuw OR v_bezig OR (NOT v_ok AND (v_einde IS NULL OR v_einde < now() - interval '15 minutes'));
6014
+ IF v_doen THEN
6015
+ BEGIN
6016
+ IF NOT EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'voorraad-floriday-versieronde-herhaal') THEN
6017
+ PERFORM cron.schedule('voorraad-floriday-versieronde-herhaal', '*/5 * * * *', 'SELECT public.run_floriday_versieronde(false);');
6018
+ END IF;
6019
+ EXCEPTION WHEN others THEN NULL; END;
6020
+ INSERT INTO public.voorraad_floriday_versieronde_stand(connection_id, laatste_poging_at) VALUES (v_conn, now())
6021
+ ON CONFLICT (connection_id) DO UPDATE SET laatste_poging_at = now(), updated_at = now();
6022
+ PERFORM public.voorraad_hook_post('/api/public/hooks/floriday-stock-reconcile?taak=versies', 55000);
6023
+ ELSIF v_ok AND NOT v_bezig THEN
6024
+ BEGIN PERFORM cron.unschedule('voorraad-floriday-versieronde-herhaal'); EXCEPTION WHEN others THEN NULL; END;
6025
+ END IF;
6026
+ RETURN jsonb_build_object('ok', true, 'gewekt', v_doen, 'bezig', v_bezig, 'geslaagd', v_ok);
6027
+ END $function$
6028
+ ;
6029
+ REVOKE ALL ON FUNCTION run_floriday_versieronde(boolean) FROM PUBLIC;
6030
+ REVOKE ALL ON FUNCTION run_floriday_versieronde(boolean) FROM anon;
6031
+ GRANT EXECUTE ON FUNCTION run_floriday_versieronde(boolean) TO authenticated;
6032
+ GRANT EXECUTE ON FUNCTION run_floriday_versieronde(boolean) TO service_role;
5897
6033
  CREATE OR REPLACE FUNCTION public.run_logistics_tasks_dispatch()
5898
6034
  RETURNS void
5899
6035
  LANGUAGE plpgsql
@@ -6813,25 +6949,32 @@ AS $function$
6813
6949
  DECLARE v_resolved uuid; v_m jsonb;
6814
6950
  BEGIN
6815
6951
  -- Floriday-orderregel: koppelen op het Floriday-artikel (eenduidig), nooit op naam.
6952
+ -- Een al ingevuld artikel wordt nooit overschreven (Engine of gebruiker koos het).
6816
6953
  IF NEW.source_line_key IS NOT NULL AND btrim(NEW.source_line_key) <> '' THEN
6817
6954
  BEGIN
6818
6955
  v_m := public.voorraad_orderregel_trade_item_bron(NEW.order_id, NEW.source_line_key);
6819
6956
  EXCEPTION WHEN others THEN v_m := NULL;
6820
6957
  END;
6821
6958
  IF v_m->>'status' = 'eenduidig' THEN
6822
- IF NEW.product_id IS DISTINCT FROM (v_m->>'product_id')::uuid THEN
6959
+ IF NEW.product_id IS NULL THEN
6823
6960
  BEGIN
6824
6961
  INSERT INTO public.voorraad_koppel_log(soort, floriday_id, product_id, order_item_id, oud, nieuw)
6825
6962
  VALUES ('orderregel_op_floriday_artikel', v_m->>'trade_item_id', (v_m->>'product_id')::uuid, NEW.id,
6826
- jsonb_build_object('product_id', NEW.product_id), jsonb_build_object('product_id', v_m->>'product_id'));
6963
+ jsonb_build_object('product_id', NULL), jsonb_build_object('product_id', v_m->>'product_id'));
6827
6964
  EXCEPTION WHEN others THEN NULL;
6828
6965
  END;
6829
6966
  NEW.product_id := (v_m->>'product_id')::uuid;
6967
+ ELSIF NEW.product_id IS DISTINCT FROM (v_m->>'product_id')::uuid THEN
6968
+ BEGIN
6969
+ INSERT INTO public.voorraad_koppel_log(soort, floriday_id, product_id, order_item_id, oud, nieuw)
6970
+ VALUES ('orderregel_afwijkend_behouden', v_m->>'trade_item_id', NEW.product_id, NEW.id,
6971
+ jsonb_build_object('product_id', NEW.product_id), jsonb_build_object('floriday_product_id', v_m->>'product_id'));
6972
+ EXCEPTION WHEN others THEN NULL;
6973
+ END;
6830
6974
  END IF;
6831
6975
  END IF;
6832
6976
  RETURN NEW;
6833
6977
  END IF;
6834
- -- Mailbox- en handmatige orders: naam als terugval.
6835
6978
  IF NEW.product_id IS NULL AND NEW.product_name IS NOT NULL THEN
6836
6979
  v_resolved := public.resolve_product_id_for_item(NEW.product_name);
6837
6980
  IF v_resolved IS NOT NULL THEN NEW.product_id := v_resolved; END IF;
@@ -7354,7 +7497,8 @@ AS $function$
7354
7497
  BEGIN
7355
7498
  IF COALESCE(NEW.sale_price,0) > 0
7356
7499
  AND lower(trim(COALESCE(NEW.unit,'stuks'))) IN ('stuks','stuk','')
7357
- AND (NEW.sale_price IS DISTINCT FROM OLD.sale_price OR NEW.unit IS DISTINCT FROM OLD.unit) THEN
7500
+ AND (NEW.sale_price IS DISTINCT FROM OLD.sale_price OR NEW.unit IS DISTINCT FROM OLD.unit)
7501
+ AND public.voorraad_floriday_volgt(NEW.id) THEN
7358
7502
  WITH latest AS (
7359
7503
  SELECT DISTINCT ON (target_year, target_week) id, target_year, target_week
7360
7504
  FROM public.floriday_continuous_stock_queue
@@ -7368,12 +7512,10 @@ BEGIN
7368
7512
  AND q.id <> l.id
7369
7513
  AND q.target_year IS NOT DISTINCT FROM l.target_year
7370
7514
  AND q.target_week IS NOT DISTINCT FROM l.target_week;
7371
-
7372
7515
  UPDATE public.floriday_continuous_stock_queue q
7373
7516
  SET status = 'pending', next_attempt_at = now(), skip_reason = NULL,
7374
7517
  last_error = NULL, result_label = NULL, updated_at = now()
7375
7518
  WHERE q.product_id = NEW.id AND q.status = 'skipped_no_price';
7376
-
7377
7519
  PERFORM public.enqueue_structure_recompute(ARRAY[NEW.id], 'price_change', true);
7378
7520
  END IF;
7379
7521
  RETURN NEW;
@@ -8035,6 +8177,192 @@ REVOKE ALL ON FUNCTION voorraad_article_vbn(uuid) FROM PUBLIC;
8035
8177
  REVOKE ALL ON FUNCTION voorraad_article_vbn(uuid) FROM anon;
8036
8178
  GRANT EXECUTE ON FUNCTION voorraad_article_vbn(uuid) TO authenticated;
8037
8179
  GRANT EXECUTE ON FUNCTION voorraad_article_vbn(uuid) TO service_role;
8180
+ CREATE OR REPLACE FUNCTION public.voorraad_artikel_archiveren(p_id uuid, p_archiveren boolean, p_bevestigd boolean DEFAULT false, p_ook_floriday boolean DEFAULT false)
8181
+ RETURNS jsonb
8182
+ LANGUAGE plpgsql
8183
+ SECURITY DEFINER
8184
+ SET search_path TO 'public'
8185
+ AS $function$
8186
+ DECLARE v_d jsonb; v_res numeric; v_open int; v_fl text; v_was boolean;
8187
+ BEGIN
8188
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_artikel_archiveren');
8189
+ IF NOT public.voorraad_can_write() THEN
8190
+ RAISE EXCEPTION 'Je hebt geen schrijfrechten op de voorraad.' USING ERRCODE = '42501'; END IF;
8191
+ SELECT NULLIF(btrim(COALESCE(floriday_trade_item_id,'')),''), gearchiveerd_floriday_nul INTO v_fl, v_was
8192
+ FROM public.products WHERE id = p_id FOR UPDATE;
8193
+ IF NOT FOUND THEN RAISE EXCEPTION 'Artikel niet gevonden.' USING ERRCODE = 'P0002'; END IF;
8194
+ IF p_archiveren THEN
8195
+ IF p_ook_floriday AND v_fl IS NULL THEN
8196
+ RAISE EXCEPTION 'Dit artikel is niet aan Floriday gekoppeld; kies "Alleen hier".' USING ERRCODE = '22023'; END IF;
8197
+ v_d := public.voorraad_artikel_detail(p_id);
8198
+ v_res := COALESCE((v_d->>'gereserveerd')::numeric, 0); v_open := COALESCE((v_d->>'open_orders')::int, 0);
8199
+ IF (v_res > 0 OR v_open > 0) AND NOT COALESCE(p_bevestigd,false) THEN
8200
+ RETURN jsonb_build_object('ok', false, 'waarschuwing', true, 'gereserveerd', v_res, 'open_orders', v_open);
8201
+ END IF;
8202
+ UPDATE public.products SET gearchiveerd_at = COALESCE(gearchiveerd_at, now()), gearchiveerd_door = auth.uid(),
8203
+ gearchiveerd_floriday_nul = COALESCE(p_ook_floriday, false) WHERE id = p_id;
8204
+ ELSE
8205
+ UPDATE public.products SET gearchiveerd_at = NULL, gearchiveerd_door = NULL, gearchiveerd_floriday_nul = false WHERE id = p_id;
8206
+ END IF;
8207
+ -- Floriday-voorraad opnieuw laten berekenen via de gewone wachtrij (schrijfstop en ERP-leidend-controle blijven gelden).
8208
+ IF v_fl IS NOT NULL AND (COALESCE(p_ook_floriday,false) OR COALESCE(v_was,false)) THEN
8209
+ BEGIN PERFORM public.enqueue_structure_recompute(ARRAY[p_id], 'ARCHIVED', true);
8210
+ EXCEPTION WHEN others THEN NULL; END;
8211
+ END IF;
8212
+ RETURN jsonb_build_object('ok', true, 'ook_floriday', COALESCE(p_ook_floriday,false) AND p_archiveren);
8213
+ END $function$
8214
+ ;
8215
+ REVOKE ALL ON FUNCTION voorraad_artikel_archiveren(uuid,boolean,boolean,boolean) FROM PUBLIC;
8216
+ REVOKE ALL ON FUNCTION voorraad_artikel_archiveren(uuid,boolean,boolean,boolean) FROM anon;
8217
+ GRANT EXECUTE ON FUNCTION voorraad_artikel_archiveren(uuid,boolean,boolean,boolean) TO authenticated;
8218
+ GRANT EXECUTE ON FUNCTION voorraad_artikel_archiveren(uuid,boolean,boolean,boolean) TO service_role;
8219
+ CREATE OR REPLACE FUNCTION public.voorraad_artikel_detail(p_id uuid)
8220
+ RETURNS jsonb
8221
+ LANGUAGE plpgsql
8222
+ STABLE SECURITY DEFINER
8223
+ SET search_path TO 'public'
8224
+ AS $function$
8225
+ DECLARE
8226
+ p public.products%ROWTYPE; v_conn uuid; v_ctx uuid; v_key text; s public.trade_item_stock%ROWTYPE;
8227
+ v_soort text; v_vrij numeric; v_open int; v_erp boolean; v_tel timestamptz; v_pak boolean; v_pak_ok boolean;
8228
+ BEGIN
8229
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_artikel_detail');
8230
+ SELECT * INTO p FROM public.products WHERE id = p_id;
8231
+ IF NOT FOUND THEN RETURN jsonb_build_object('gevonden', false); END IF;
8232
+ BEGIN SELECT x.connection_id INTO v_conn FROM public.get_active_floriday_context() x LIMIT 1;
8233
+ EXCEPTION WHEN others THEN v_conn := NULL; END;
8234
+ BEGIN SELECT c.connection_id INTO v_ctx FROM public.get_stock_context() c LIMIT 1;
8235
+ EXCEPTION WHEN others THEN v_ctx := NULL; END;
8236
+
8237
+ SELECT x.* INTO s FROM public.trade_item_stock x
8238
+ WHERE x.archived_at IS NULL AND x.trade_item_key IN (
8239
+ NULLIF(btrim(COALESCE(p.trade_item_key,'')),''), NULLIF(btrim(COALESCE(p.floriday_trade_item_id,'')),''), 'product:' || p.id::text)
8240
+ ORDER BY CASE x.trade_item_key WHEN NULLIF(btrim(COALESCE(p.trade_item_key,'')),'') THEN 1
8241
+ WHEN NULLIF(btrim(COALESCE(p.floriday_trade_item_id,'')),'') THEN 2 ELSE 3 END,
8242
+ (x.connection_id IS NOT DISTINCT FROM v_ctx) DESC, x.updated_at DESC
8243
+ LIMIT 1;
8244
+ v_key := s.trade_item_key;
8245
+
8246
+ v_soort := CASE WHEN EXISTS (SELECT 1 FROM public.product_groups g WHERE g.head_product_id = p.id AND g.is_active) THEN 'GROUP'
8247
+ WHEN EXISTS (SELECT 1 FROM public.product_bom_items b WHERE b.parent_product_id = p.id) THEN 'SET' ELSE 'NORMAL' END;
8248
+ v_vrij := GREATEST(COALESCE(s.physical_stock,0) - COALESCE(s.reserved_stock,0), 0);
8249
+ IF v_soort <> 'NORMAL' AND v_ctx IS NOT NULL THEN
8250
+ BEGIN v_vrij := COALESCE((public.calculate_structure_available(v_ctx, p.id)->>'available')::numeric, v_vrij);
8251
+ EXCEPTION WHEN others THEN NULL; END;
8252
+ END IF;
8253
+
8254
+ SELECT count(*) INTO v_open FROM public.order_items oi JOIN public.orders o ON o.id = oi.order_id
8255
+ WHERE oi.product_id = p.id AND oi.quarantined_at IS NULL
8256
+ AND NOT public.voorraad_order_geannuleerd(o.status, o.cancelled_at, o.deleted_at)
8257
+ AND lower(COALESCE(o.status,'')) NOT IN ('verzonden','gereed voor facturatie');
8258
+
8259
+ IF NULLIF(btrim(COALESCE(p.floriday_trade_item_id,'')),'') IS NOT NULL THEN
8260
+ SELECT bool_or(f.erp_leading) INTO v_erp FROM public.floriday_inventory_settings f
8261
+ WHERE f.product_id = p.id;
8262
+ END IF;
8263
+ SELECT max(t.telmoment) INTO v_tel FROM public.voorraad_telmoment t;
8264
+ v_pak := EXISTS (SELECT 1 FROM public.product_packings k WHERE k.product_id = p.id AND k.is_primary);
8265
+ v_pak_ok := COALESCE((p.custom_fields#>>'{voorraad_packing_link,ok}')::boolean, true);
8266
+
8267
+ RETURN jsonb_build_object(
8268
+ 'gevonden', true, 'id', p.id,
8269
+ 'velden', jsonb_build_object('product', p.product, 'article_number', p.article_number, 'sale_price', p.sale_price,
8270
+ 'unit', p.unit, 'barcode', p.barcode),
8271
+ 'naam', COALESCE(NULLIF(btrim(p.product),''), NULLIF(p.custom_fields#>>'{floriday_payload,name}',''), '(naamloos)'),
8272
+ 'artikelnummer', COALESCE(NULLIF(btrim(p.article_number),''), NULLIF(p.custom_fields#>>'{floriday_payload,articleCode}','')),
8273
+ 'foto', COALESCE(NULLIF(p.image_url,''), NULLIF(p.custom_fields#>>'{floriday_payload,images,0,url}',''), NULLIF(p.custom_fields#>>'{floriday_payload,images,0,previewUrl}','')),
8274
+ 'floriday_id', NULLIF(btrim(COALESCE(p.floriday_trade_item_id,'')),''),
8275
+ 'heeft_koppeling', v_conn IS NOT NULL,
8276
+ 'soort', v_soort,
8277
+ 'is_onderdeel', EXISTS (SELECT 1 FROM public.product_bom_items b WHERE b.component_product_id = p.id),
8278
+ 'voorraad_sleutel', v_key,
8279
+ 'liggend', COALESCE(s.physical_stock,0), 'gereserveerd', COALESCE(s.reserved_stock,0),
8280
+ 'inkomend', COALESCE(s.incoming_stock,0), 'minimum', COALESCE(s.minimum_stock,0), 'vrij', v_vrij,
8281
+ 'notitie', s.notes, 'open_orders', v_open,
8282
+ 'floriday_stuurt', COALESCE(v_erp, false),
8283
+ 'telmoment', v_tel,
8284
+ 'verpakking_ok', (v_soort = 'GROUP' OR (v_pak AND v_pak_ok)),
8285
+ 'gearchiveerd_at', p.gearchiveerd_at, 'orders_gearchiveerd', CASE WHEN p.gearchiveerd_at IS NULL THEN 0 ELSE COALESCE((public.voorraad_orders_gearchiveerd(p.id)->0->>'regels')::int, 0) END,
8286
+ 'floriday_verwijderd_at', p.floriday_verwijderd_at,
8287
+ 'trade_item_key', p.trade_item_key,
8288
+ 'controle', jsonb_build_object(
8289
+ 'mutatie_sleutels', (SELECT to_jsonb(array_agg(DISTINCT k)) FROM (
8290
+ SELECT unnest(ARRAY[v_key, p.trade_item_key, p.id::text]) AS k
8291
+ UNION SELECT 'group:' || gi.product_group_id::text FROM public.product_group_items gi
8292
+ WHERE gi.trade_item_id IN (v_key, p.trade_item_key, p.id::text)) x WHERE k IS NOT NULL),
8293
+ 'zelfde_sleutel_ids', (SELECT COALESCE(to_jsonb(array_agg(q.id)), '[]'::jsonb) FROM (SELECT q.id FROM public.products q
8294
+ WHERE q.id = p.id OR (p.trade_item_key IS NOT NULL AND q.trade_item_key = p.trade_item_key) LIMIT 50) q)),
8295
+ 'eigen_velden', jsonb_build_object(
8296
+ 'definities', (SELECT COALESCE(jsonb_agg(jsonb_build_object('id', f.id, 'field_key', f.field_key, 'field_label', f.field_label,
8297
+ 'field_type', f.field_type, 'options', f.options, 'show_in_template', f.show_in_template, 'show_in_columns', f.show_in_columns,
8298
+ 'show_in_detail', f.show_in_detail, 'sort_order', f.sort_order) ORDER BY f.sort_order, f.created_at), '[]'::jsonb)
8299
+ FROM public.voorraad_custom_fields f),
8300
+ 'waarden', (SELECT COALESCE(jsonb_object_agg(f.field_key, p.custom_fields->f.field_key), '{}'::jsonb)
8301
+ FROM public.voorraad_custom_fields f WHERE jsonb_typeof(p.custom_fields) = 'object' AND p.custom_fields ? f.field_key)),
8302
+ 'bijgewerkt', now());
8303
+ END $function$
8304
+ ;
8305
+ REVOKE ALL ON FUNCTION voorraad_artikel_detail(uuid) FROM PUBLIC;
8306
+ REVOKE ALL ON FUNCTION voorraad_artikel_detail(uuid) FROM anon;
8307
+ GRANT EXECUTE ON FUNCTION voorraad_artikel_detail(uuid) TO authenticated;
8308
+ GRANT EXECUTE ON FUNCTION voorraad_artikel_detail(uuid) TO service_role;
8309
+ CREATE OR REPLACE FUNCTION public.voorraad_artikel_opslaan(p_id uuid, p_oud jsonb, p_nieuw jsonb)
8310
+ RETURNS jsonb
8311
+ LANGUAGE plpgsql
8312
+ SECURITY DEFINER
8313
+ SET search_path TO 'public'
8314
+ AS $function$
8315
+ DECLARE
8316
+ v_cur jsonb; k text; v_conf jsonb := '[]'::jsonb; v_set jsonb := '{}'::jsonb;
8317
+ v_toegestaan text[] := ARRAY['product','article_number','sale_price','unit','barcode'];
8318
+ BEGIN
8319
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_artikel_opslaan');
8320
+ IF NOT public.voorraad_can_write() THEN
8321
+ RAISE EXCEPTION 'Je hebt geen schrijfrechten op de voorraad.' USING ERRCODE = '42501'; END IF;
8322
+ IF jsonb_typeof(p_nieuw) IS DISTINCT FROM 'object' OR jsonb_typeof(p_oud) IS DISTINCT FROM 'object' THEN
8323
+ RAISE EXCEPTION 'Ongeldige invoer voor opslaan.' USING ERRCODE = '22023'; END IF;
8324
+
8325
+ SELECT jsonb_build_object('product', product, 'article_number', article_number, 'sale_price', sale_price,
8326
+ 'unit', unit, 'barcode', barcode)
8327
+ INTO v_cur FROM public.products WHERE id = p_id FOR UPDATE;
8328
+ IF v_cur IS NULL THEN RAISE EXCEPTION 'Artikel niet gevonden.' USING ERRCODE = 'P0002'; END IF;
8329
+
8330
+ FOR k IN SELECT jsonb_object_keys(p_nieuw) LOOP
8331
+ IF NOT k = ANY(v_toegestaan) THEN
8332
+ RAISE EXCEPTION 'Veld "%" kan hier niet worden opgeslagen.', k USING ERRCODE = '22023'; END IF;
8333
+ IF NOT (p_oud ? k) THEN
8334
+ RAISE EXCEPTION 'Stand bij openen ontbreekt voor veld "%".', k USING ERRCODE = '22023'; END IF;
8335
+ -- Alleen velden die de gebruiker echt wijzigde.
8336
+ CONTINUE WHEN (p_nieuw->k) = (p_oud->k);
8337
+ IF (v_cur->k) IS DISTINCT FROM (p_oud->k) AND (v_cur->k) IS DISTINCT FROM (p_nieuw->k) THEN
8338
+ v_conf := v_conf || jsonb_build_object('veld', k, 'bij_openen', p_oud->k, 'jouw', p_nieuw->k, 'hun', v_cur->k);
8339
+ ELSE
8340
+ v_set := v_set || jsonb_build_object(k, p_nieuw->k);
8341
+ END IF;
8342
+ END LOOP;
8343
+
8344
+ IF jsonb_array_length(v_conf) > 0 THEN
8345
+ RETURN jsonb_build_object('ok', false, 'conflicten', v_conf);
8346
+ END IF;
8347
+ IF v_set ? 'sale_price' AND jsonb_typeof(v_set->'sale_price') = 'number' AND (v_set->>'sale_price')::numeric < 0 THEN
8348
+ RAISE EXCEPTION 'Verkoopprijs kan niet negatief zijn.' USING ERRCODE = '22023'; END IF;
8349
+
8350
+ IF v_set <> '{}'::jsonb THEN
8351
+ UPDATE public.products SET
8352
+ product = CASE WHEN v_set ? 'product' THEN NULLIF(btrim(v_set->>'product'),'') ELSE product END,
8353
+ article_number = CASE WHEN v_set ? 'article_number' THEN NULLIF(btrim(v_set->>'article_number'),'') ELSE article_number END,
8354
+ sale_price = CASE WHEN v_set ? 'sale_price' THEN (v_set->>'sale_price')::numeric ELSE sale_price END,
8355
+ unit = CASE WHEN v_set ? 'unit' THEN NULLIF(btrim(v_set->>'unit'),'') ELSE unit END,
8356
+ barcode = CASE WHEN v_set ? 'barcode' THEN NULLIF(btrim(v_set->>'barcode'),'') ELSE barcode END
8357
+ WHERE id = p_id;
8358
+ END IF;
8359
+ RETURN jsonb_build_object('ok', true, 'opgeslagen', v_set);
8360
+ END $function$
8361
+ ;
8362
+ REVOKE ALL ON FUNCTION voorraad_artikel_opslaan(uuid,jsonb,jsonb) FROM PUBLIC;
8363
+ REVOKE ALL ON FUNCTION voorraad_artikel_opslaan(uuid,jsonb,jsonb) FROM anon;
8364
+ GRANT EXECUTE ON FUNCTION voorraad_artikel_opslaan(uuid,jsonb,jsonb) TO authenticated;
8365
+ GRANT EXECUTE ON FUNCTION voorraad_artikel_opslaan(uuid,jsonb,jsonb) TO service_role;
8038
8366
  CREATE OR REPLACE FUNCTION public.voorraad_artikel_prijs(p_id text)
8039
8367
  RETURNS TABLE(product_id uuid, sale_price numeric, unit text)
8040
8368
  LANGUAGE plpgsql
@@ -9103,6 +9431,50 @@ END $function$
9103
9431
  REVOKE ALL ON FUNCTION voorraad_correctie_1144(boolean,integer,integer) FROM PUBLIC;
9104
9432
  REVOKE ALL ON FUNCTION voorraad_correctie_1144(boolean,integer,integer) FROM anon;
9105
9433
  GRANT EXECUTE ON FUNCTION voorraad_correctie_1144(boolean,integer,integer) TO service_role;
9434
+ CREATE OR REPLACE FUNCTION public.voorraad_correctie_1148(p_uitvoeren boolean DEFAULT false)
9435
+ RETURNS jsonb
9436
+ LANGUAGE plpgsql
9437
+ SECURITY DEFINER
9438
+ SET search_path TO 'public'
9439
+ AS $function$
9440
+ DECLARE r record; v_opv uuid; v_lijst jsonb := '[]'::jsonb; n_arch int := 0; n_overslaan int := 0; v_reden text; v_open int; v_stock numeric;
9441
+ BEGIN
9442
+ PERFORM public.voorraad_vereis('server', 'voorraad_correctie_1148');
9443
+ FOR r IN
9444
+ SELECT p.id, p.product, p.custom_fields, v.floriday_id, v.opvolger_id
9445
+ FROM public.voorraad_floriday_vervallen v JOIN public.products p ON p.id = public.voorraad_floriday_product_van(v.floriday_id)
9446
+ WHERE v.opgeheven_at IS NULL AND p.gearchiveerd_at IS NULL AND p.custom_fields->>'voorraad_auto_aangemaakt' = 'true'
9447
+ ORDER BY p.product
9448
+ LOOP
9449
+ SELECT COALESCE(max(s.physical_stock),0) INTO v_stock FROM public.trade_item_stock s
9450
+ WHERE s.archived_at IS NULL AND s.trade_item_key IN (r.floriday_id, 'product:' || r.id::text);
9451
+ SELECT count(*) INTO v_open FROM public.order_items oi JOIN public.orders o ON o.id = oi.order_id
9452
+ WHERE oi.product_id = r.id AND NOT public.voorraad_order_geannuleerd(o.status, o.cancelled_at, o.deleted_at)
9453
+ AND NOT public.voorraad_order_vertrokken_voor(o.id, now());
9454
+ v_reden := CASE WHEN v_stock <> 0 THEN 'heeft voorraad' WHEN v_open > 0 THEN 'heeft open orders'
9455
+ WHEN jsonb_typeof(r.custom_fields->'floriday_velden_aangepast') = 'array' AND jsonb_array_length(r.custom_fields->'floriday_velden_aangepast') > 0 THEN 'door gebruiker aangepast' END;
9456
+ v_opv := CASE WHEN r.opvolger_id IS NOT NULL THEN public.voorraad_floriday_product_van(r.opvolger_id) END;
9457
+ v_lijst := v_lijst || jsonb_build_object('product_id', r.id, 'naam', r.product, 'floriday_id', r.floriday_id, 'opvolger_artikel', v_opv,
9458
+ 'actie', CASE WHEN v_reden IS NULL THEN 'archiveren' ELSE 'overslaan' END, 'reden', v_reden);
9459
+ IF v_reden IS NOT NULL THEN n_overslaan := n_overslaan + 1; CONTINUE; END IF;
9460
+ n_arch := n_arch + 1;
9461
+ IF p_uitvoeren THEN
9462
+ UPDATE public.products SET gearchiveerd_at = now(), custom_fields = custom_fields || jsonb_build_object('voorraad_floriday_vervallen', true), updated_at = now()
9463
+ WHERE id = r.id AND gearchiveerd_at IS NULL;
9464
+ UPDATE public.floriday_inventory_settings SET erp_leading = NULL, stock_mode = 'DISABLED', updated_at = now() WHERE product_id = r.id;
9465
+ UPDATE public.voorraad_floriday_vervallen SET verwerkt_at = now(), uitkomst = 'gearchiveerd_correctie', product_id = r.id WHERE floriday_id = r.floriday_id;
9466
+ END IF;
9467
+ END LOOP;
9468
+ INSERT INTO public.voorraad_correcties(correctie, uitgevoerd_door, proef, aantallen, hash_ok)
9469
+ VALUES ('1148', COALESCE(auth.uid()::text, session_user::text), NOT p_uitvoeren,
9470
+ jsonb_build_object('archiveren', n_arch, 'overslaan', n_overslaan), true);
9471
+ RETURN jsonb_build_object('uitgevoerd', p_uitvoeren, 'archiveren', n_arch, 'overslaan', n_overslaan, 'regels', v_lijst);
9472
+ END $function$
9473
+ ;
9474
+ REVOKE ALL ON FUNCTION voorraad_correctie_1148(boolean) FROM PUBLIC;
9475
+ REVOKE ALL ON FUNCTION voorraad_correctie_1148(boolean) FROM anon;
9476
+ GRANT EXECUTE ON FUNCTION voorraad_correctie_1148(boolean) TO authenticated;
9477
+ GRANT EXECUTE ON FUNCTION voorraad_correctie_1148(boolean) TO service_role;
9106
9478
  CREATE OR REPLACE FUNCTION public.voorraad_correctie_status(p_correctie text DEFAULT '1098'::text)
9107
9479
  RETURNS jsonb
9108
9480
  LANGUAGE plpgsql
@@ -9166,7 +9538,7 @@ CREATE OR REPLACE FUNCTION public.voorraad_defaults_versie()
9166
9538
  LANGUAGE sql
9167
9539
  IMMUTABLE
9168
9540
  SET search_path TO 'public'
9169
- AS $function$ SELECT 2 $function$
9541
+ AS $function$ SELECT 3 $function$
9170
9542
  ;
9171
9543
  REVOKE ALL ON FUNCTION voorraad_defaults_versie() FROM PUBLIC;
9172
9544
  REVOKE ALL ON FUNCTION voorraad_defaults_versie() FROM anon;
@@ -9203,6 +9575,43 @@ REVOKE ALL ON FUNCTION voorraad_dubbele_voorraadregels() FROM PUBLIC;
9203
9575
  REVOKE ALL ON FUNCTION voorraad_dubbele_voorraadregels() FROM anon;
9204
9576
  GRANT EXECUTE ON FUNCTION voorraad_dubbele_voorraadregels() TO authenticated;
9205
9577
  GRANT EXECUTE ON FUNCTION voorraad_dubbele_voorraadregels() TO service_role;
9578
+ CREATE OR REPLACE FUNCTION public.voorraad_eigen_velden_opslaan(p_id uuid, p_oud jsonb, p_nieuw jsonb)
9579
+ RETURNS jsonb
9580
+ LANGUAGE plpgsql
9581
+ SECURITY DEFINER
9582
+ SET search_path TO 'public'
9583
+ AS $function$
9584
+ DECLARE v_cur jsonb; k text; v_conf jsonb := '[]'::jsonb; v_set jsonb := '{}'::jsonb;
9585
+ BEGIN
9586
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_eigen_velden_opslaan');
9587
+ IF NOT public.voorraad_can_write() THEN
9588
+ RAISE EXCEPTION 'Je hebt geen schrijfrechten op de voorraad.' USING ERRCODE = '42501'; END IF;
9589
+ IF jsonb_typeof(p_nieuw) IS DISTINCT FROM 'object' OR jsonb_typeof(p_oud) IS DISTINCT FROM 'object' THEN
9590
+ RAISE EXCEPTION 'Ongeldige invoer voor opslaan.' USING ERRCODE = '22023'; END IF;
9591
+ SELECT COALESCE(custom_fields, '{}'::jsonb) INTO v_cur FROM public.products WHERE id = p_id FOR UPDATE;
9592
+ IF NOT FOUND THEN RAISE EXCEPTION 'Artikel niet gevonden.' USING ERRCODE = 'P0002'; END IF;
9593
+ FOR k IN SELECT jsonb_object_keys(p_nieuw) LOOP
9594
+ IF NOT EXISTS (SELECT 1 FROM public.voorraad_custom_fields f WHERE f.field_key = k) THEN
9595
+ RAISE EXCEPTION 'Veld "%" is geen eigen veld.', k USING ERRCODE = '22023'; END IF;
9596
+ CONTINUE WHEN COALESCE(p_nieuw->k, 'null'::jsonb) = COALESCE(p_oud->k, 'null'::jsonb);
9597
+ IF COALESCE(v_cur->k,'null'::jsonb) IS DISTINCT FROM COALESCE(p_oud->k,'null'::jsonb)
9598
+ AND COALESCE(v_cur->k,'null'::jsonb) IS DISTINCT FROM COALESCE(p_nieuw->k,'null'::jsonb) THEN
9599
+ v_conf := v_conf || jsonb_build_object('veld', k, 'bij_openen', p_oud->k, 'jouw', p_nieuw->k, 'hun', v_cur->k);
9600
+ ELSE
9601
+ v_set := v_set || jsonb_build_object(k, p_nieuw->k);
9602
+ END IF;
9603
+ END LOOP;
9604
+ IF jsonb_array_length(v_conf) > 0 THEN RETURN jsonb_build_object('ok', false, 'conflicten', v_conf); END IF;
9605
+ IF v_set <> '{}'::jsonb THEN
9606
+ UPDATE public.products SET custom_fields = COALESCE(custom_fields,'{}'::jsonb) || v_set, updated_at = now() WHERE id = p_id;
9607
+ END IF;
9608
+ RETURN jsonb_build_object('ok', true, 'opgeslagen', v_set);
9609
+ END $function$
9610
+ ;
9611
+ REVOKE ALL ON FUNCTION voorraad_eigen_velden_opslaan(uuid,jsonb,jsonb) FROM PUBLIC;
9612
+ REVOKE ALL ON FUNCTION voorraad_eigen_velden_opslaan(uuid,jsonb,jsonb) FROM anon;
9613
+ GRANT EXECUTE ON FUNCTION voorraad_eigen_velden_opslaan(uuid,jsonb,jsonb) TO authenticated;
9614
+ GRANT EXECUTE ON FUNCTION voorraad_eigen_velden_opslaan(uuid,jsonb,jsonb) TO service_role;
9206
9615
  CREATE OR REPLACE FUNCTION public.voorraad_ensure_defaults()
9207
9616
  RETURNS jsonb
9208
9617
  LANGUAGE plpgsql
@@ -9219,6 +9628,7 @@ DECLARE
9219
9628
  v_idx text := 'bestaat'; v_job text := 'bestaat'; v_res jsonb; v_job_ok boolean;
9220
9629
  BEGIN
9221
9630
  PERFORM public.voorraad_vereis('ingelogd', 'voorraad_ensure_defaults');
9631
+ BEGIN PERFORM public.voorraad_floriday_versieronde_inplannen(); EXCEPTION WHEN others THEN RAISE LOG '[voorraad] versieronde inplannen: %', SQLERRM; END;
9222
9632
  BEGIN
9223
9633
  BEGIN v_job_ok := EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'voorraad-floriday-artikelen-uurlijks'); EXCEPTION WHEN others THEN v_job_ok := true; END;
9224
9634
  IF EXISTS (SELECT 1 FROM public.voorraad_defaults_state s WHERE s.id AND s.versie = v_versie)
@@ -9342,7 +9752,7 @@ DECLARE
9342
9752
  c record; v_name text; v_gtin text; v_code text; v_photo text; v_ver text; v_del boolean;
9343
9753
  v_p record; v_c uuid[]; v_tier text; v_new uuid; v_sturing text; v_own_key text;
9344
9754
  n_aan int := 0; n_kop int := 0; n_twijfel int := 0; n_uit int := 0; n_bij int := 0; n_fout int := 0; n_rest int := 0;
9345
- v_vb jsonb := '[]'::jsonb; v_aangepast text[];
9755
+ v_vb jsonb := '[]'::jsonb; v_aangepast text[]; v_ronde_ok boolean; n_wacht int := 0;
9346
9756
  BEGIN
9347
9757
  PERFORM public.voorraad_vereis('ingelogd', 'voorraad_floriday_artikelen_aanmaken');
9348
9758
  v_dups := public.voorraad_floriday_dubbele_ids();
@@ -9351,6 +9761,7 @@ BEGIN
9351
9761
  END IF;
9352
9762
  v_conn := COALESCE(p_connection_id, (SELECT x.connection_id FROM public.get_active_floriday_context() x LIMIT 1));
9353
9763
  IF v_conn IS NULL THEN RETURN jsonb_build_object('ok', true, 'melding', 'Geen actieve Floriday-koppeling'); END IF;
9764
+ v_ronde_ok := public.voorraad_floriday_versieronde_geslaagd(v_conn);
9354
9765
  IF p_uitvoeren AND NOT pg_try_advisory_xact_lock(hashtext('voorraad_floriday_artikelen_aanmaken')) THEN
9355
9766
  RETURN jsonb_build_object('ok', false, 'melding', 'Er loopt al een ronde');
9356
9767
  END IF;
@@ -9365,9 +9776,10 @@ BEGIN
9365
9776
  AND (
9366
9777
  (NOT (COALESCE(t.is_deleted,false) OR COALESCE((t.data->>'isDeleted')::boolean,false))
9367
9778
  AND NOT EXISTS (SELECT 1 FROM public.products p WHERE lower(btrim(p.floriday_trade_item_id)) = lower(t.floriday_id))
9368
- AND NOT EXISTS (SELECT 1 FROM public.voorraad_koppel_twijfel w WHERE w.floriday_id = t.floriday_id AND w.opgelost_at IS NULL AND w.laatst_gezien > now() - interval '1 hour' AND p_uitvoeren))
9779
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_koppel_twijfel w WHERE w.floriday_id = t.floriday_id AND w.opgelost_at IS NULL AND w.laatst_gezien > now() - interval '1 hour' AND p_uitvoeren)
9780
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_floriday_vervallen v WHERE v.floriday_id = t.floriday_id AND v.opgeheven_at IS NULL))
9369
9781
  OR ((COALESCE(t.is_deleted,false) OR COALESCE((t.data->>'isDeleted')::boolean,false))
9370
- AND EXISTS (SELECT 1 FROM public.products p WHERE lower(btrim(p.floriday_trade_item_id)) = lower(t.floriday_id) AND p.floriday_verwijderd_at IS NULL))
9782
+ AND EXISTS (SELECT 1 FROM public.products p WHERE p.id = public.voorraad_floriday_product_van(t.floriday_id) AND p.floriday_verwijderd_at IS NULL))
9371
9783
  OR (NOT (COALESCE(t.is_deleted,false) OR COALESCE((t.data->>'isDeleted')::boolean,false))
9372
9784
  AND EXISTS (SELECT 1 FROM public.products p WHERE lower(btrim(p.floriday_trade_item_id)) = lower(t.floriday_id)
9373
9785
  AND (p.floriday_verwijderd_at IS NOT NULL
@@ -9387,13 +9799,19 @@ BEGIN
9387
9799
  SELECT e->>'url' INTO v_photo FROM jsonb_array_elements(CASE WHEN jsonb_typeof(c.data->'photos') = 'array' THEN c.data->'photos' ELSE '[]'::jsonb END) e
9388
9800
  ORDER BY COALESCE((e->>'primary')::boolean, false) DESC LIMIT 1;
9389
9801
  v_ver := c.data->>'tradeItemVersion';
9390
- SELECT p.* INTO v_p FROM public.products p WHERE lower(btrim(p.floriday_trade_item_id)) = lower(c.floriday_id) LIMIT 1;
9802
+ SELECT p.* INTO v_p FROM public.products p
9803
+ WHERE p.id = CASE WHEN c.del THEN public.voorraad_floriday_product_van(c.floriday_id)
9804
+ ELSE (SELECT q.id FROM public.products q WHERE lower(btrim(q.floriday_trade_item_id)) = lower(c.floriday_id) LIMIT 1) END
9805
+ LIMIT 1;
9391
9806
 
9392
9807
  BEGIN
9393
9808
  IF c.del THEN
9394
9809
  -- Verwijderd in Floriday: eigen artikel op niet actief, nooit verwijderen.
9395
9810
  IF p_uitvoeren THEN
9396
- UPDATE public.products SET floriday_verwijderd_at = now(), updated_at = now() WHERE id = v_p.id;
9811
+ UPDATE public.products SET floriday_verwijderd_at = now(),
9812
+ floriday_trade_item_id = CASE WHEN floriday_trade_item_id IS NULL AND NOT EXISTS (SELECT 1 FROM public.products q
9813
+ WHERE q.id <> v_p.id AND lower(btrim(q.floriday_trade_item_id)) = lower(c.floriday_id)) THEN c.floriday_id ELSE floriday_trade_item_id END,
9814
+ updated_at = now() WHERE id = v_p.id;
9397
9815
  INSERT INTO public.voorraad_koppel_log(run_id, soort, floriday_id, product_id, oud, nieuw)
9398
9816
  VALUES (v_run, 'niet_actief', c.floriday_id, v_p.id, jsonb_build_object('floriday_verwijderd_at', NULL), jsonb_build_object('floriday_verwijderd_at', now()));
9399
9817
  END IF;
@@ -9472,7 +9890,11 @@ BEGIN
9472
9890
  END IF;
9473
9891
  ELSE
9474
9892
  -- Geen match: eigen artikel aanmaken op dezelfde voorraadsleutel als het Floriday-product.
9475
- IF v_name IS NULL THEN
9893
+ IF NOT v_ronde_ok OR EXISTS (SELECT 1 FROM public.voorraad_floriday_versie_fouten f
9894
+ WHERE f.connection_id = v_conn AND f.floriday_id = c.floriday_id AND f.onbekend_sinds IS NOT NULL) THEN
9895
+ -- Nog geen geslaagde versieronde: niets nieuws aanmaken (koppelen gebeurde hierboven al).
9896
+ n_wacht := n_wacht + 1;
9897
+ ELSIF v_name IS NULL THEN
9476
9898
  IF p_uitvoeren THEN
9477
9899
  INSERT INTO public.voorraad_koppel_twijfel(floriday_id, connection_id, naam, reden)
9478
9900
  VALUES (c.floriday_id, v_conn, NULL, 'Floriday-product heeft geen naam')
@@ -9513,7 +9935,7 @@ BEGIN
9513
9935
 
9514
9936
  RETURN jsonb_build_object('ok', true, 'uitgevoerd', p_uitvoeren, 'run_id', CASE WHEN p_uitvoeren THEN v_run END,
9515
9937
  'aangemaakt', n_aan, 'gekoppeld', n_kop, 'twijfel', n_twijfel, 'niet_actief', n_uit, 'bijgewerkt', n_bij,
9516
- 'fouten', n_fout, 'resterend', n_rest, 'sturing', v_sturing,
9938
+ 'fouten', n_fout, 'resterend', n_rest, 'wacht_op_versieronde', n_wacht, 'sturing', v_sturing,
9517
9939
  'voorbeelden', (SELECT COALESCE(jsonb_agg(e), '[]'::jsonb) FROM (SELECT e FROM jsonb_array_elements(v_vb) e LIMIT 25) z));
9518
9940
  END $function$
9519
9941
  ;
@@ -9531,6 +9953,7 @@ DECLARE v jsonb; v_t timestamptz := now();
9531
9953
  BEGIN
9532
9954
  PERFORM public.voorraad_vereis('server', 'voorraad_floriday_artikelen_vangnet');
9533
9955
  v := public.voorraad_floriday_artikelen_aanmaken(NULL, 500, true, 45);
9956
+ v := v || jsonb_build_object('vervallen', public.voorraad_floriday_vervallen_verwerken(true));
9534
9957
  IF COALESCE((v->>'resterend')::int, 0) = 0 THEN
9535
9958
  DELETE FROM public.voorraad_floriday_artikel_wachtrij WHERE aangemeld_at <= v_t;
9536
9959
  END IF;
@@ -9602,6 +10025,356 @@ $function$
9602
10025
  REVOKE ALL ON FUNCTION voorraad_floriday_leads_key(text) FROM PUBLIC;
9603
10026
  REVOKE ALL ON FUNCTION voorraad_floriday_leads_key(text) FROM anon;
9604
10027
  GRANT EXECUTE ON FUNCTION voorraad_floriday_leads_key(text) TO service_role;
10028
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_product_van(p_floriday_id text)
10029
+ RETURNS uuid
10030
+ LANGUAGE plpgsql
10031
+ STABLE SECURITY DEFINER
10032
+ SET search_path TO 'public'
10033
+ AS $function$
10034
+ DECLARE v uuid; v_k text := lower(btrim(COALESCE(p_floriday_id,''))); v_ids uuid[];
10035
+ BEGIN
10036
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_floriday_product_van');
10037
+ IF v_k = '' THEN RETURN NULL; END IF;
10038
+ SELECT p.id INTO v FROM public.products p WHERE lower(btrim(p.floriday_trade_item_id)) = v_k LIMIT 1;
10039
+ IF v IS NOT NULL THEN RETURN v; END IF;
10040
+ SELECT array_agg(DISTINCT p.id) INTO v_ids FROM public.products p
10041
+ WHERE p.floriday_trade_item_id IS NULL
10042
+ AND (lower(btrim(COALESCE(p.trade_item_key,''))) = v_k
10043
+ OR lower(COALESCE(p.custom_fields->>'floriday_id','')) = v_k
10044
+ OR p.id IN (SELECT l.product_id FROM public.voorraad_koppel_log l
10045
+ WHERE lower(l.floriday_id) = v_k AND l.soort IN ('aangemaakt','gekoppeld') AND l.product_id IS NOT NULL));
10046
+ IF array_length(v_ids, 1) = 1 THEN RETURN v_ids[1]; END IF;
10047
+ RETURN NULL;
10048
+ END $function$
10049
+ ;
10050
+ REVOKE ALL ON FUNCTION voorraad_floriday_product_van(text) FROM PUBLIC;
10051
+ REVOKE ALL ON FUNCTION voorraad_floriday_product_van(text) FROM anon;
10052
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_product_van(text) TO authenticated;
10053
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_product_van(text) TO service_role;
10054
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_versieronde_geslaagd(p_connection_id uuid)
10055
+ RETURNS boolean
10056
+ LANGUAGE plpgsql
10057
+ STABLE SECURITY DEFINER
10058
+ SET search_path TO 'public'
10059
+ AS $function$
10060
+ BEGIN
10061
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_floriday_versieronde_geslaagd');
10062
+ RETURN EXISTS (SELECT 1 FROM public.voorraad_floriday_versie_rondes r WHERE r.connection_id = p_connection_id AND r.gemarkeerd);
10063
+ END $function$
10064
+ ;
10065
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_geslaagd(uuid) FROM PUBLIC;
10066
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_geslaagd(uuid) FROM anon;
10067
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_geslaagd(uuid) TO authenticated;
10068
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_geslaagd(uuid) TO service_role;
10069
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_versieronde_inplannen()
10070
+ RETURNS jsonb
10071
+ LANGUAGE plpgsql
10072
+ SECURITY DEFINER
10073
+ SET search_path TO 'public'
10074
+ AS $function$
10075
+ DECLARE v_conn uuid; v_res jsonb;
10076
+ BEGIN
10077
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_versieronde_inplannen');
10078
+ BEGIN SELECT x.connection_id INTO v_conn FROM public.get_active_floriday_context() x LIMIT 1;
10079
+ EXCEPTION WHEN others THEN v_conn := NULL; END;
10080
+ IF v_conn IS NULL THEN RETURN jsonb_build_object('ok', true, 'melding', 'geen Floriday-koppeling'); END IF;
10081
+ BEGIN
10082
+ IF NOT EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'voorraad-floriday-versieronde-dagelijks') THEN
10083
+ -- 03:30 UTC = 04:30 Amsterdam (wintertijd), 05:30 in de zomer.
10084
+ PERFORM cron.schedule('voorraad-floriday-versieronde-dagelijks', '30 3 * * *', 'SELECT public.run_floriday_versieronde(true);');
10085
+ END IF;
10086
+ EXCEPTION WHEN others THEN RETURN jsonb_build_object('ok', false, 'melding', 'inplannen mislukt: ' || SQLERRM); END;
10087
+ IF NOT public.voorraad_floriday_versieronde_geslaagd(v_conn)
10088
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_floriday_versieronde_stand s WHERE s.connection_id = v_conn AND s.laatste_poging_at > now() - interval '5 minutes') THEN
10089
+ v_res := public.run_floriday_versieronde(false);
10090
+ END IF;
10091
+ RETURN jsonb_build_object('ok', true, 'gestart', v_res);
10092
+ END $function$
10093
+ ;
10094
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_inplannen() FROM PUBLIC;
10095
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_inplannen() FROM anon;
10096
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_inplannen() TO authenticated;
10097
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_inplannen() TO service_role;
10098
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_versieronde_melding()
10099
+ RETURNS text
10100
+ LANGUAGE plpgsql
10101
+ STABLE SECURITY DEFINER
10102
+ SET search_path TO 'public'
10103
+ AS $function$
10104
+ DECLARE r record; v_conn uuid;
10105
+ BEGIN
10106
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_floriday_versieronde_melding');
10107
+ BEGIN SELECT x.connection_id INTO v_conn FROM public.get_active_floriday_context() x LIMIT 1;
10108
+ EXCEPTION WHEN others THEN v_conn := NULL; END;
10109
+ IF v_conn IS NULL THEN RETURN NULL; END IF;
10110
+ SELECT * INTO r FROM public.voorraad_floriday_versie_rondes WHERE connection_id = v_conn AND COALESCE(status,'') <> 'onvolledig'
10111
+ ORDER BY uitgevoerd_at DESC LIMIT 1;
10112
+ IF r.status = 'mislukt' THEN
10113
+ RETURN 'Controle van Floriday-artikelen mislukt: ' || COALESCE(r.melding, 'onbekende oorzaak') || '; wordt automatisch opnieuw geprobeerd.';
10114
+ END IF;
10115
+ RETURN NULL;
10116
+ END $function$
10117
+ ;
10118
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_melding() FROM PUBLIC;
10119
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_melding() FROM anon;
10120
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_melding() TO authenticated;
10121
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_melding() TO service_role;
10122
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_versieronde_portie(p_connection_id uuid, p_ronde_id uuid, p_uitkomsten jsonb, p_verbindingsfout text DEFAULT NULL::text)
10123
+ RETURNS jsonb
10124
+ LANGUAGE plpgsql
10125
+ SECURITY DEFINER
10126
+ SET search_path TO 'public'
10127
+ AS $function$
10128
+ DECLARE v_huidig uuid; v_rest int; n_blok int; n_onb int; v_res jsonb; v_rid uuid; v_uit jsonb; v_status text; v_meld text;
10129
+ BEGIN
10130
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_versieronde_portie');
10131
+ SELECT ronde_id INTO v_huidig FROM public.voorraad_floriday_versieronde_stand WHERE connection_id = p_connection_id FOR UPDATE;
10132
+ IF v_huidig IS DISTINCT FROM p_ronde_id THEN RETURN jsonb_build_object('ok', false, 'melding', 'Ronde is al afgerond'); END IF;
10133
+
10134
+ IF NULLIF(btrim(COALESCE(p_verbindingsfout,'')),'') IS NOT NULL THEN
10135
+ INSERT INTO public.voorraad_floriday_versie_rondes(connection_id, volledig, fouten, status, gemarkeerd, melding)
10136
+ VALUES (p_connection_id, false, 1, 'mislukt', false, 'geen verbinding met Floriday (' || left(p_verbindingsfout, 200) || ')');
10137
+ DELETE FROM public.voorraad_floriday_versieronde_uitkomst WHERE ronde_id = p_ronde_id;
10138
+ UPDATE public.voorraad_floriday_versieronde_stand SET ronde_id = NULL, laatste_einde_at = now(), updated_at = now() WHERE connection_id = p_connection_id;
10139
+ RETURN jsonb_build_object('ok', false, 'afgerond', true, 'status', 'mislukt');
10140
+ END IF;
10141
+
10142
+ INSERT INTO public.voorraad_floriday_versieronde_uitkomst(ronde_id, floriday_id, status, opvolger_id, bron, reden)
10143
+ SELECT p_ronde_id, e->>'floriday_id', e->>'status', NULLIF(e->>'opvolger_id',''), NULLIF(e->>'bron',''), NULLIF(e->>'reden','')
10144
+ FROM jsonb_array_elements(CASE WHEN jsonb_typeof(p_uitkomsten) = 'array' THEN p_uitkomsten ELSE '[]'::jsonb END) e
10145
+ WHERE e->>'status' IN ('actueel','vervangen','vervallen','fout') AND NULLIF(e->>'floriday_id','') IS NOT NULL
10146
+ ON CONFLICT (ronde_id, floriday_id) DO NOTHING;
10147
+
10148
+ SELECT count(*) INTO v_rest FROM public.floriday_trade_items_cache t
10149
+ WHERE t.connection_id = p_connection_id AND NOT (COALESCE(t.is_deleted,false) OR COALESCE((t.data->>'isDeleted')::boolean,false))
10150
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_floriday_versieronde_uitkomst u WHERE u.ronde_id = p_ronde_id AND u.floriday_id = t.floriday_id);
10151
+ IF v_rest > 0 THEN RETURN jsonb_build_object('ok', true, 'afgerond', false, 'resterend', v_rest); END IF;
10152
+
10153
+ -- Afronden: foutentellers per artikel (één keer per ronde).
10154
+ INSERT INTO public.voorraad_floriday_versie_fouten(connection_id, floriday_id, opeenvolgend, laatste_reden)
10155
+ SELECT p_connection_id, u.floriday_id, 1, u.reden FROM public.voorraad_floriday_versieronde_uitkomst u WHERE u.ronde_id = p_ronde_id AND u.status = 'fout'
10156
+ ON CONFLICT (connection_id, floriday_id) DO UPDATE SET opeenvolgend = public.voorraad_floriday_versie_fouten.opeenvolgend + 1,
10157
+ laatste_reden = EXCLUDED.laatste_reden, updated_at = now();
10158
+ UPDATE public.voorraad_floriday_versie_fouten SET onbekend_sinds = now()
10159
+ WHERE connection_id = p_connection_id AND opeenvolgend >= 3 AND onbekend_sinds IS NULL;
10160
+ DELETE FROM public.voorraad_floriday_versie_fouten f USING public.voorraad_floriday_versieronde_uitkomst u
10161
+ WHERE f.connection_id = p_connection_id AND u.ronde_id = p_ronde_id AND u.floriday_id = f.floriday_id AND u.status <> 'fout';
10162
+ SELECT count(*) FILTER (WHERE f.opeenvolgend < 3), count(*) FILTER (WHERE f.opeenvolgend >= 3) INTO n_blok, n_onb
10163
+ FROM public.voorraad_floriday_versieronde_uitkomst u JOIN public.voorraad_floriday_versie_fouten f
10164
+ ON f.connection_id = p_connection_id AND f.floriday_id = u.floriday_id
10165
+ WHERE u.ronde_id = p_ronde_id AND u.status = 'fout';
10166
+
10167
+ IF n_blok > 0 THEN
10168
+ INSERT INTO public.voorraad_floriday_versie_rondes(connection_id, volledig, fouten, status, gemarkeerd, onbekend, melding)
10169
+ VALUES (p_connection_id, false, n_blok, 'onvolledig', false, n_onb,
10170
+ n_blok || CASE WHEN n_blok = 1 THEN ' artikel gaf' ELSE ' artikelen gaven' END || ' geen antwoord; volgende ronde opnieuw')
10171
+ RETURNING id INTO v_rid;
10172
+ v_status := 'onvolledig';
10173
+ ELSE
10174
+ SELECT COALESCE(jsonb_agg(jsonb_build_object('floriday_id', u.floriday_id, 'status', u.status, 'opvolger_id', u.opvolger_id, 'bron', u.bron)), '[]'::jsonb)
10175
+ INTO v_uit FROM public.voorraad_floriday_versieronde_uitkomst u WHERE u.ronde_id = p_ronde_id AND u.status <> 'fout';
10176
+ v_res := public.voorraad_floriday_versies_vastleggen(p_connection_id, jsonb_build_object('volledig', true, 'fouten', 0, 'uitkomsten', v_uit));
10177
+ v_rid := (v_res->>'ronde_id')::uuid;
10178
+ v_status := CASE WHEN (v_res->>'ok')::boolean THEN 'geslaagd' ELSE 'mislukt' END;
10179
+ v_meld := CASE WHEN v_status = 'mislukt' THEN 'te veel artikelen lijken tegelijk vervallen; ter controle is niets aangepast' END;
10180
+ UPDATE public.voorraad_floriday_versie_rondes SET status = v_status, onbekend = n_onb, melding = COALESCE(v_meld, melding) WHERE id = v_rid;
10181
+ END IF;
10182
+ DELETE FROM public.voorraad_floriday_versieronde_uitkomst WHERE ronde_id = p_ronde_id;
10183
+ UPDATE public.voorraad_floriday_versieronde_stand SET ronde_id = NULL, laatste_einde_at = now(), updated_at = now() WHERE connection_id = p_connection_id;
10184
+ IF v_status = 'geslaagd' THEN
10185
+ PERFORM public.voorraad_floriday_artikelen_aanmaken(p_connection_id, 500, true, 30);
10186
+ PERFORM public.voorraad_floriday_vervallen_verwerken(true);
10187
+ END IF;
10188
+ RETURN jsonb_build_object('ok', v_status = 'geslaagd', 'afgerond', true, 'status', v_status, 'onbekend', n_onb, 'blokkerend', n_blok, 'ronde', v_res);
10189
+ END $function$
10190
+ ;
10191
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_portie(uuid,uuid,jsonb,text) FROM PUBLIC;
10192
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_portie(uuid,uuid,jsonb,text) FROM anon;
10193
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_portie(uuid,uuid,jsonb,text) TO authenticated;
10194
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_portie(uuid,uuid,jsonb,text) TO service_role;
10195
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_versieronde_status()
10196
+ RETURNS jsonb
10197
+ LANGUAGE plpgsql
10198
+ STABLE SECURITY DEFINER
10199
+ SET search_path TO 'public'
10200
+ AS $function$
10201
+ DECLARE v_conn uuid; v_beh boolean;
10202
+ BEGIN
10203
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_floriday_versieronde_status');
10204
+ v_beh := public.voorraad_is_beheerder();
10205
+ IF NOT v_beh THEN RETURN jsonb_build_object('beheerder', false); END IF;
10206
+ BEGIN SELECT x.connection_id INTO v_conn FROM public.get_active_floriday_context() x LIMIT 1;
10207
+ EXCEPTION WHEN others THEN v_conn := NULL; END;
10208
+ RETURN jsonb_build_object('beheerder', true, 'heeft_koppeling', v_conn IS NOT NULL,
10209
+ 'laatste', (SELECT jsonb_build_object('uitgevoerd_at', r.uitgevoerd_at, 'status', COALESCE(r.status, CASE WHEN r.gemarkeerd THEN 'geslaagd' ELSE 'mislukt' END),
10210
+ 'melding', r.melding, 'gecontroleerd', r.gecontroleerd, 'vervangen', r.vervangen, 'vervallen', r.vervallen, 'onbekend', r.onbekend)
10211
+ FROM public.voorraad_floriday_versie_rondes r WHERE r.connection_id = v_conn ORDER BY r.uitgevoerd_at DESC LIMIT 1),
10212
+ 'bezig', EXISTS (SELECT 1 FROM public.voorraad_floriday_versieronde_stand s WHERE s.connection_id = v_conn AND s.ronde_id IS NOT NULL),
10213
+ 'geslaagd_ooit', COALESCE(public.voorraad_floriday_versieronde_geslaagd(v_conn), false),
10214
+ 'vervangen', (SELECT count(*) FROM public.voorraad_floriday_vervallen v WHERE v.opgeheven_at IS NULL AND v.status = 'vervangen'),
10215
+ 'vervallen', (SELECT count(*) FROM public.voorraad_floriday_vervallen v WHERE v.opgeheven_at IS NULL AND v.status = 'vervallen'),
10216
+ 'onbekend', (SELECT count(*) FROM public.voorraad_floriday_versie_fouten f WHERE f.connection_id = v_conn AND f.onbekend_sinds IS NOT NULL));
10217
+ END $function$
10218
+ ;
10219
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_status() FROM PUBLIC;
10220
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_status() FROM anon;
10221
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_status() TO authenticated;
10222
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_status() TO service_role;
10223
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_versieronde_volgende(p_connection_id uuid, p_aantal integer DEFAULT 200)
10224
+ RETURNS jsonb
10225
+ LANGUAGE plpgsql
10226
+ SECURITY DEFINER
10227
+ SET search_path TO 'public'
10228
+ AS $function$
10229
+ DECLARE v_ronde uuid; v_ids jsonb;
10230
+ BEGIN
10231
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_versieronde_volgende');
10232
+ INSERT INTO public.voorraad_floriday_versieronde_stand(connection_id) VALUES (p_connection_id) ON CONFLICT (connection_id) DO NOTHING;
10233
+ SELECT ronde_id INTO v_ronde FROM public.voorraad_floriday_versieronde_stand WHERE connection_id = p_connection_id FOR UPDATE;
10234
+ IF v_ronde IS NULL THEN
10235
+ v_ronde := gen_random_uuid();
10236
+ UPDATE public.voorraad_floriday_versieronde_stand SET ronde_id = v_ronde, gestart_at = now(), updated_at = now() WHERE connection_id = p_connection_id;
10237
+ END IF;
10238
+ SELECT COALESCE(jsonb_agg(x.floriday_id), '[]'::jsonb) INTO v_ids FROM (
10239
+ SELECT t.floriday_id FROM public.floriday_trade_items_cache t
10240
+ WHERE t.connection_id = p_connection_id AND NOT (COALESCE(t.is_deleted,false) OR COALESCE((t.data->>'isDeleted')::boolean,false))
10241
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_floriday_versieronde_uitkomst u WHERE u.ronde_id = v_ronde AND u.floriday_id = t.floriday_id)
10242
+ ORDER BY t.floriday_id LIMIT GREATEST(LEAST(COALESCE(p_aantal, 200), 500), 1)) x;
10243
+ RETURN jsonb_build_object('ronde_id', v_ronde, 'ids', v_ids);
10244
+ END $function$
10245
+ ;
10246
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_volgende(uuid,integer) FROM PUBLIC;
10247
+ REVOKE ALL ON FUNCTION voorraad_floriday_versieronde_volgende(uuid,integer) FROM anon;
10248
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_volgende(uuid,integer) TO authenticated;
10249
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versieronde_volgende(uuid,integer) TO service_role;
10250
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_versies_vastleggen(p_connection_id uuid, p_ronde jsonb, p_max_aandeel numeric DEFAULT 0.5)
10251
+ RETURNS jsonb
10252
+ LANGUAGE plpgsql
10253
+ SECURITY DEFINER
10254
+ SET search_path TO 'public'
10255
+ AS $function$
10256
+ DECLARE v_vol boolean; v_fout int; v_gec int; v_cache int; n_act int; n_verv int; n_vall int; v_ok boolean := false; v_meld text;
10257
+ v_ronde uuid; v_uit jsonb; v_res jsonb := NULL;
10258
+ BEGIN
10259
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_versies_vastleggen');
10260
+ IF p_connection_id IS NULL THEN RAISE EXCEPTION 'Geen Floriday-koppeling opgegeven' USING ERRCODE = '22023'; END IF;
10261
+ v_uit := CASE WHEN jsonb_typeof(p_ronde->'uitkomsten') = 'array' THEN p_ronde->'uitkomsten' ELSE '[]'::jsonb END;
10262
+ v_vol := COALESCE((p_ronde->>'volledig')::boolean, false);
10263
+ v_fout := COALESCE((p_ronde->>'fouten')::int, 0);
10264
+ v_gec := jsonb_array_length(v_uit);
10265
+ SELECT count(*) INTO v_cache FROM public.floriday_trade_items_cache t
10266
+ WHERE t.connection_id = p_connection_id AND NOT (COALESCE(t.is_deleted,false) OR COALESCE((t.data->>'isDeleted')::boolean,false));
10267
+ SELECT count(*) FILTER (WHERE e->>'status' = 'actueel'), count(*) FILTER (WHERE e->>'status' = 'vervangen'), count(*) FILTER (WHERE e->>'status' = 'vervallen')
10268
+ INTO n_act, n_verv, n_vall FROM jsonb_array_elements(v_uit) e;
10269
+ IF NOT v_vol OR v_fout > 0 THEN
10270
+ v_meld := 'Onvolledige of mislukte ronde: niets als vervallen gemarkeerd';
10271
+ ELSIF v_gec + (SELECT count(*) FROM public.voorraad_floriday_versie_fouten f JOIN public.floriday_trade_items_cache t
10272
+ ON t.connection_id = f.connection_id AND t.floriday_id = f.floriday_id
10273
+ WHERE f.connection_id = p_connection_id AND f.onbekend_sinds IS NOT NULL
10274
+ AND NOT EXISTS (SELECT 1 FROM jsonb_array_elements(v_uit) e WHERE e->>'floriday_id' = f.floriday_id)) < v_cache THEN
10275
+ v_meld := format('Ronde controleerde %s van %s Floriday-artikelen: niets als vervallen gemarkeerd', v_gec, v_cache);
10276
+ ELSIF v_gec > 0 AND (n_verv + n_vall)::numeric / v_gec > p_max_aandeel THEN
10277
+ v_meld := format('%s van %s artikelen zouden vervallen (meer dan %s%%): niets gemarkeerd, controleer de koppeling', n_verv + n_vall, v_gec, round(p_max_aandeel * 100));
10278
+ ELSE
10279
+ v_ok := true;
10280
+ END IF;
10281
+ INSERT INTO public.voorraad_floriday_versie_rondes(connection_id, volledig, fouten, gecontroleerd, in_cache, actueel, vervangen, vervallen, gemarkeerd, melding)
10282
+ VALUES (p_connection_id, v_vol, v_fout, v_gec, v_cache, n_act, n_verv, n_vall, v_ok, v_meld) RETURNING id INTO v_ronde;
10283
+ IF v_ok THEN
10284
+ INSERT INTO public.voorraad_floriday_vervallen(floriday_id, connection_id, status, opvolger_id, bron, ronde_id)
10285
+ SELECT e->>'floriday_id', p_connection_id, e->>'status', NULLIF(e->>'opvolger_id',''), COALESCE(NULLIF(e->>'bron',''), 'volledige_ronde'), v_ronde
10286
+ FROM jsonb_array_elements(v_uit) e WHERE e->>'status' IN ('vervangen','vervallen') AND NULLIF(e->>'floriday_id','') IS NOT NULL
10287
+ ON CONFLICT (floriday_id) DO UPDATE SET status = EXCLUDED.status, opvolger_id = EXCLUDED.opvolger_id, bron = EXCLUDED.bron,
10288
+ ronde_id = EXCLUDED.ronde_id, opgeheven_at = NULL,
10289
+ verwerkt_at = CASE WHEN public.voorraad_floriday_vervallen.opgeheven_at IS NOT NULL THEN NULL ELSE public.voorraad_floriday_vervallen.verwerkt_at END;
10290
+ -- Weer actueel: markering opheffen (gearchiveerde artikelen blijven gearchiveerd).
10291
+ UPDATE public.voorraad_floriday_vervallen v SET opgeheven_at = now()
10292
+ FROM jsonb_array_elements(v_uit) e
10293
+ WHERE e->>'status' = 'actueel' AND v.floriday_id = e->>'floriday_id' AND v.opgeheven_at IS NULL;
10294
+ v_res := public.voorraad_floriday_vervallen_verwerken(true);
10295
+ END IF;
10296
+ RETURN jsonb_build_object('ok', v_ok, 'ronde_id', v_ronde, 'melding', v_meld, 'gecontroleerd', v_gec, 'in_cache', v_cache,
10297
+ 'actueel', n_act, 'vervangen', n_verv, 'vervallen', n_vall, 'verwerkt', v_res);
10298
+ END $function$
10299
+ ;
10300
+ REVOKE ALL ON FUNCTION voorraad_floriday_versies_vastleggen(uuid,jsonb,numeric) FROM PUBLIC;
10301
+ REVOKE ALL ON FUNCTION voorraad_floriday_versies_vastleggen(uuid,jsonb,numeric) FROM anon;
10302
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versies_vastleggen(uuid,jsonb,numeric) TO authenticated;
10303
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_versies_vastleggen(uuid,jsonb,numeric) TO service_role;
10304
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_vervallen_verwerken(p_uitvoeren boolean DEFAULT true)
10305
+ RETURNS jsonb
10306
+ LANGUAGE plpgsql
10307
+ SECURITY DEFINER
10308
+ SET search_path TO 'public'
10309
+ AS $function$
10310
+ DECLARE r record; v_best uuid; v_opv uuid; v_new uuid; c record; v_name text; v_code text; v_gtin text;
10311
+ n_alias int := 0; n_arch int := 0; n_best int := 0; n_wacht int := 0; v_run uuid := gen_random_uuid();
10312
+ BEGIN
10313
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_vervallen_verwerken');
10314
+ IF p_uitvoeren THEN PERFORM set_config('voorraad.floriday_sync', 'on', true); END IF;
10315
+ FOR r IN SELECT * FROM public.voorraad_floriday_vervallen WHERE opgeheven_at IS NULL AND verwerkt_at IS NULL ORDER BY floriday_id LOOP
10316
+ v_best := public.voorraad_floriday_product_van(r.floriday_id);
10317
+ IF v_best IS NOT NULL THEN
10318
+ n_best := n_best + 1;
10319
+ IF p_uitvoeren THEN
10320
+ UPDATE public.voorraad_floriday_vervallen SET verwerkt_at = now(), uitkomst = 'bestaand_artikel', product_id = v_best WHERE floriday_id = r.floriday_id;
10321
+ END IF;
10322
+ CONTINUE;
10323
+ END IF;
10324
+ IF r.opvolger_id IS NOT NULL THEN
10325
+ v_opv := public.voorraad_floriday_product_van(r.opvolger_id);
10326
+ IF v_opv IS NULL THEN n_wacht := n_wacht + 1; CONTINUE; END IF;
10327
+ n_alias := n_alias + 1;
10328
+ IF p_uitvoeren THEN
10329
+ INSERT INTO public.voorraad_floriday_alias(floriday_id, product_id, bron) VALUES (r.floriday_id, v_opv, r.bron)
10330
+ ON CONFLICT (floriday_id) DO NOTHING;
10331
+ UPDATE public.voorraad_floriday_vervallen SET verwerkt_at = now(), uitkomst = 'alias', product_id = v_opv WHERE floriday_id = r.floriday_id;
10332
+ INSERT INTO public.voorraad_koppel_log(run_id, soort, floriday_id, product_id, oud, nieuw)
10333
+ VALUES (v_run, 'vervallen_alias', r.floriday_id, v_opv, NULL, jsonb_build_object('opvolger', r.opvolger_id, 'bron', r.bron));
10334
+ END IF;
10335
+ CONTINUE;
10336
+ END IF;
10337
+ n_arch := n_arch + 1;
10338
+ IF p_uitvoeren THEN
10339
+ SELECT t.data INTO c FROM public.floriday_trade_items_cache t WHERE t.floriday_id = r.floriday_id LIMIT 1;
10340
+ v_name := NULLIF(btrim(COALESCE(CASE WHEN jsonb_typeof(c.data->'tradeItemName') = 'string' THEN c.data->>'tradeItemName' END,
10341
+ c.data->'tradeItemName'->>'nl', c.data->'tradeItemName'->>'en')), '');
10342
+ v_code := NULLIF(btrim(COALESCE(c.data->>'supplierArticleCode', c.data->>'articleCode')), '');
10343
+ v_gtin := NULLIF(btrim(COALESCE(c.data->>'articleGtin', c.data->>'gtin')), '');
10344
+ INSERT INTO public.products(product, batch, location, barcode, article_number, floriday_trade_item_id, trade_item_key, custom_fields, gearchiveerd_at)
10345
+ VALUES (COALESCE(v_name, '(vervallen Floriday-versie)'), '', '', v_gtin, v_code, r.floriday_id, r.floriday_id,
10346
+ jsonb_build_object('voorraad_auto_aangemaakt', true, 'voorraad_floriday_vervallen', true, 'voorraad_floriday_vervallen_bron', r.bron,
10347
+ 'voorraad_auto_aangemaakt_at', now()), now())
10348
+ RETURNING id INTO v_new;
10349
+ UPDATE public.floriday_inventory_settings SET erp_leading = NULL, stock_mode = 'DISABLED', updated_at = now() WHERE product_id = v_new;
10350
+ UPDATE public.voorraad_floriday_vervallen SET verwerkt_at = now(), uitkomst = 'gearchiveerd_aangemaakt', product_id = v_new WHERE floriday_id = r.floriday_id;
10351
+ INSERT INTO public.voorraad_koppel_log(run_id, soort, floriday_id, product_id, oud, nieuw)
10352
+ VALUES (v_run, 'vervallen_gearchiveerd', r.floriday_id, v_new, NULL, jsonb_build_object('naam', v_name, 'bron', r.bron));
10353
+ END IF;
10354
+ END LOOP;
10355
+ RETURN jsonb_build_object('alias', n_alias, 'gearchiveerd', n_arch, 'bestaand_artikel', n_best, 'wacht_op_opvolger', n_wacht, 'uitgevoerd', p_uitvoeren);
10356
+ END $function$
10357
+ ;
10358
+ REVOKE ALL ON FUNCTION voorraad_floriday_vervallen_verwerken(boolean) FROM PUBLIC;
10359
+ REVOKE ALL ON FUNCTION voorraad_floriday_vervallen_verwerken(boolean) FROM anon;
10360
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_vervallen_verwerken(boolean) TO authenticated;
10361
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_vervallen_verwerken(boolean) TO service_role;
10362
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_volgt(p_product_id uuid)
10363
+ RETURNS boolean
10364
+ LANGUAGE plpgsql
10365
+ STABLE SECURITY DEFINER
10366
+ SET search_path TO 'public'
10367
+ AS $function$
10368
+ BEGIN
10369
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_volgt');
10370
+ RETURN EXISTS (SELECT 1 FROM public.floriday_inventory_settings s
10371
+ WHERE s.product_id = p_product_id AND s.erp_leading IS TRUE
10372
+ AND COALESCE(s.stock_mode,'') <> 'DISABLED');
10373
+ END $function$
10374
+ ;
10375
+ REVOKE ALL ON FUNCTION voorraad_floriday_volgt(uuid) FROM PUBLIC;
10376
+ REVOKE ALL ON FUNCTION voorraad_floriday_volgt(uuid) FROM anon;
10377
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_volgt(uuid) TO service_role;
9605
10378
  CREATE OR REPLACE FUNCTION public.voorraad_fust_bijhouden(p_fust_type_id uuid)
9606
10379
  RETURNS boolean
9607
10380
  LANGUAGE sql
@@ -9664,6 +10437,29 @@ REVOKE ALL ON FUNCTION voorraad_fust_dimensions() FROM PUBLIC;
9664
10437
  REVOKE ALL ON FUNCTION voorraad_fust_dimensions() FROM anon;
9665
10438
  GRANT EXECUTE ON FUNCTION voorraad_fust_dimensions() TO authenticated;
9666
10439
  GRANT EXECUTE ON FUNCTION voorraad_fust_dimensions() TO service_role;
10440
+ CREATE OR REPLACE FUNCTION public.voorraad_fust_historisch_vervallen(p_order_item_id uuid)
10441
+ RETURNS boolean
10442
+ LANGUAGE plpgsql
10443
+ STABLE SECURITY DEFINER
10444
+ SET search_path TO 'public'
10445
+ AS $function$
10446
+ DECLARE v record; v_ti text;
10447
+ BEGIN
10448
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_fust_historisch_vervallen');
10449
+ SELECT oi.order_id, oi.product_id, oi.source_line_key, p.custom_fields->>'voorraad_floriday_vervallen' AS verv
10450
+ INTO v FROM public.order_items oi LEFT JOIN public.products p ON p.id = oi.product_id WHERE oi.id = p_order_item_id;
10451
+ IF v.order_id IS NULL OR v.product_id IS NULL THEN RETURN false; END IF;
10452
+ IF NOT public.voorraad_order_vertrokken_voor(v.order_id, now()) THEN RETURN false; END IF;
10453
+ IF v.verv = 'true' THEN RETURN true; END IF;
10454
+ IF NULLIF(btrim(COALESCE(v.source_line_key,'')),'') IS NULL THEN RETURN false; END IF;
10455
+ v_ti := lower(COALESCE(public.voorraad_orderregel_trade_item_bron(v.order_id, v.source_line_key)->>'trade_item_id',''));
10456
+ RETURN v_ti <> '' AND EXISTS (SELECT 1 FROM public.voorraad_floriday_alias a WHERE lower(a.floriday_id) = v_ti);
10457
+ END $function$
10458
+ ;
10459
+ REVOKE ALL ON FUNCTION voorraad_fust_historisch_vervallen(uuid) FROM PUBLIC;
10460
+ REVOKE ALL ON FUNCTION voorraad_fust_historisch_vervallen(uuid) FROM anon;
10461
+ GRANT EXECUTE ON FUNCTION voorraad_fust_historisch_vervallen(uuid) TO authenticated;
10462
+ GRANT EXECUTE ON FUNCTION voorraad_fust_historisch_vervallen(uuid) TO service_role;
9667
10463
  CREATE OR REPLACE FUNCTION public.voorraad_fust_instellingen_opslaan(p_fust_type_id uuid, p_saldo_bijhouden boolean, p_eigen_naam text)
9668
10464
  RETURNS jsonb
9669
10465
  LANGUAGE plpgsql
@@ -9746,6 +10542,9 @@ BEGIN
9746
10542
  -- Vóór het fust-startmoment: geen herberekening, het geboekte aantal blijft juist.
9747
10543
  aantal := COALESCE((SELECT sum(b.quantity)::int FROM public.order_fust_bookings b WHERE b.order_item_id = p_order_item_id AND b.fust_type_id = v_ft), 0);
9748
10544
  bron := 'vóór fust-startmoment: niet herberekend';
10545
+ ELSIF public.voorraad_fust_historisch_vervallen(p_order_item_id) THEN
10546
+ aantal := COALESCE((SELECT sum(b.quantity)::int FROM public.order_fust_bookings b WHERE b.order_item_id = p_order_item_id AND b.fust_type_id = v_ft), 0);
10547
+ bron := 'historische regel van vervallen Floriday-versie: niet herberekend';
9749
10548
  END IF;
9750
10549
  RETURN NEXT;
9751
10550
  END $function$
@@ -9989,7 +10788,7 @@ CREATE OR REPLACE FUNCTION public.voorraad_install_check()
9989
10788
  STABLE SECURITY DEFINER
9990
10789
  SET search_path TO 'public'
9991
10790
  AS $function$
9992
- DECLARE v_f text[] := ARRAY['voorraad_floriday_artikelen_aanmaken','voorraad_floriday_artikelen_vangnet','voorraad_orderregel_trade_item','voorraad_orderregel_trade_item_bron','voorraad_correctie_1144','voorraad_telmoment_lijst','voorraad_telmoment_opslaan','voorraad_order_vertrokken_voor','voorraad_fust_startmoment_opslaan','voorraad_fust_startmoment_auto','voorraad_fust_instellingen_opslaan','voorraad_koppeling_check','voorraad_instellingen_opslaan','voorraad_ensure_defaults','voorraad_defaults_check','voorraad_defaults_versie','voorraad_canoniek_artikel','voorraad_canoniek_sleutel','voorraad_artikel_prijs','voorraad_dubbele_voorraadregels','voorraad_correctie_1131','voorraad_correctie_1132','voorraad_gateway_routes_check','voorraad_placeholder_code_reason','trg_products_placeholder_code','voorraad_fust_juist','voorraad_correctie_1121','voorraad_correctie_1121_regels','voorraad_correctie_1123','voorraad_correctie_1124','voorraad_load_carriers','voorraad_packing_from_floriday','voorraad_packing_missing','voorraad_correctie_1126','voorraad_correctie_1127','trg_voorraad_link_primary_packing','voorraad_order_geannuleerd','voorraad_article_vbn','voorraad_correctie_1112','voorraad_reverse_order_mutations','voorraad_withdraw_stock_row','voorraad_floriday_klaarzetten_toegestaan','voorraad_afboeken','voorraad_voorraad_opslaan','voorraad_correctie_1140','voorraad_overzicht_orderregel_koppeling','voorraad_orderregel_sleutel','ensure_voorraad_local_context','get_stock_context','voorraad_stock_context_status','voorraad_fcsq_local_guard','_floriday_queue_cleanup_batch','_migrate_rewrite_stock_key','_stock_import_caller','apply_fust_stock_mutation','apply_stock_consumption','apply_stock_reservation','autolink_product_to_floriday','backfill_open_order_reservations','calculate_floriday_desired_stock','calculate_structure_available','canonicalize_trade_item_stock_key','check_product_structure','claim_floriday_base_supply','claim_floriday_stock_queue','cleanup_floriday_stock_queue','cleanup_voorraad_module_history','complete_floriday_base_supply','complete_floriday_stock_queue','complete_floriday_stock_queue_outcome','consume_bom_for_production','consume_order_item','drain_inventory_recompute_queue','enqueue_floriday_continuous_stock','enqueue_floriday_continuous_stock_after_warehouse_preference','enqueue_structure_recompute','ensure_inventory_recompute_drain','ensure_trade_item_stock_row','ensure_trade_item_stock_row_key','fcsq_block_disabled','fcsq_fallback_supersede','fcsq_wake_base_supply','find_trade_item_key','floriday_adopt_connection','floriday_autoheal_connections','floriday_capture_stock_snapshot','floriday_cleanup_stock_queue_v2','floriday_compact_stock_snapshots','floriday_default_owner','floriday_desired_stock_regression_check','floriday_ensure_inventory_settings','floriday_erp_leading_preview','floriday_fill_publish_horizon','floriday_follow_apply','floriday_ingest_sales_orders','floriday_leading_summary','floriday_make_erp_leading','floriday_norm_offer_forms','floriday_orphan_settings_count','floriday_publish_horizon_weeks','floriday_queue_erp_guard','floriday_queue_first_positive','floriday_recompute_inventory','floriday_relink_products','floriday_requeue_dead_letters','floriday_settings_change_recompute','floriday_settings_change_recompute_stmt','floriday_stock_change_recompute','floriday_stock_queue_supersede','floriday_takeover_apply','floriday_takeover_fetch_state','floriday_takeover_store_forms','floriday_takeover_undo','fust_negative_stock_report','fust_types_duplicate_guard','get_active_floriday_context','get_group_allocation_keys','get_group_head_key','get_product_stock','get_product_structure_totals','get_product_structure_totals_batch','inventory_inline_budget_ok','merge_products','normalize_order_source','process_inventory_recompute_queue','produce_designed_product','product_active_group_id','product_id_from_ref','product_is_set','product_label','product_member_group_ids','product_own_available','product_packings_derive','product_packings_replace','product_packings_sync_from_floriday','product_primary_pieces','product_set_depth_above','product_set_depth_below','product_structure_ancestors','product_structure_max_depth','product_structure_path','product_tray_stickers_touch','product_type','promote_trade_item_key','recompute_all_reserved_stock','recompute_order_status','recompute_reserved_for_order','recompute_reserved_stock','requeue_skipped_stock_rows','reset_stalled_floriday_stock_queue','resolve_group_allocation','resolve_group_member_stock_key','resolve_product_id_for_item','resolve_stock_trade_item_key','reverse_production','run_floriday_base_supply_process','run_floriday_continuous_stock_sync','run_floriday_follow','run_floriday_stock_queue_process','run_floriday_stock_reconcile','run_logistics_tasks_dispatch','self_heal_floriday_links','set_module_runtime_config_updated_at','set_product_bom_tenant','set_product_stock','set_updated_at_order_fust_bookings','stock_import_cleanup','stock_import_create','stock_import_process_chunk','stock_import_stop','stock_import_undo_chunk','sync_order_item_fusten','sync_products_quantity_from_stock','touch_fust_types_updated_at','trade_item_drafts_readonly','trade_item_prices_touch_updated_at','trade_item_stock_duplicates','trg_cart_numbers_sync_order_status_fn','trg_cart_session_consume_fn','trg_check_product_structure','trg_floriday_cache_relink_fn','trg_floriday_trade_items_autolink_fn','trg_order_items_autolink_fn','trg_order_items_fusten','trg_order_items_reserve_fn','trg_orders_cancel_release_fn','trg_orders_consume_on_complete_fn','trg_orders_default_phase_fn','trg_orders_external_fulfilled_fn','trg_orders_recompute_reserved_fn','trg_pgi_assign_sort_order','trg_pgi_order_taken_check','trg_process_recompute_on_commit','trg_product_packings_derive_fn','trg_products_autolink_fn','trg_products_floriday_link_enqueue','trg_products_seed_stock_fn','trg_requeue_no_warehouse_cache','trg_requeue_no_warehouse_connection','trg_requeue_no_warehouse_item_pref','trg_requeue_skipped_no_price','trg_stock_structure_recompute_ins','trg_stock_structure_recompute_upd','trg_structure_change_recompute','trg_trade_item_drafts_autolink_fn','trg_trade_item_stock_auto_bom','trg_trade_item_stock_auto_bom_reverse','trg_trade_item_stock_log_change','trg_trade_item_stock_reserve_fn','trg_trade_item_stock_zero_watch','trg_trade_items_cache_autolink_fn','update_updated_at_column','voorraad_alert_floriday_not_updated','voorraad_can_write','voorraad_cleanup_old_rows','voorraad_consume_external_fulfilled','voorraad_correctie_1098','voorraad_correctie_status','voorraad_external_fulfilled_ids','voorraad_floriday_leads_key','voorraad_fust_dimensions','voorraad_hook_base_url','voorraad_hook_on','voorraad_hook_post','voorraad_hook_switch_jobs','voorraad_install_check','voorraad_is_server','voorraad_mark_floriday_sold_before_switch','voorraad_production_enabled','voorraad_schema_ddl','voorraad_touch_updated_at','voorraad_vereis','voorraad_week_is_past'];
10791
+ DECLARE v_f text[] := ARRAY['voorraad_floriday_versieronde_geslaagd','voorraad_is_beheerder','voorraad_floriday_versieronde_volgende','voorraad_floriday_versieronde_portie','run_floriday_versieronde','voorraad_floriday_versieronde_inplannen','voorraad_floriday_versieronde_melding','voorraad_floriday_versieronde_status','voorraad_floriday_product_van','voorraad_floriday_vervallen_verwerken','voorraad_floriday_versies_vastleggen','voorraad_fust_historisch_vervallen','voorraad_correctie_1148','voorraad_instellingen_lezen','voorraad_twijfel_nieuw_artikel','voorraad_eigen_velden_opslaan','voorraad_traysticker_opslaan','voorraad_floriday_volgt','voorraad_artikel_archiveren','voorraad_orders_gearchiveerd','voorraad_twijfel_kiezen','voorraad_twijfel_lijst','voorraad_voorraad_instellingen_opslaan','voorraad_artikel_detail','voorraad_artikel_opslaan','voorraad_voorraad_corrigeren','voorraad_lijst','voorraad_floriday_artikelen_aanmaken','voorraad_floriday_artikelen_vangnet','voorraad_orderregel_trade_item','voorraad_orderregel_trade_item_bron','voorraad_correctie_1144','voorraad_telmoment_lijst','voorraad_telmoment_opslaan','voorraad_order_vertrokken_voor','voorraad_fust_startmoment_opslaan','voorraad_fust_startmoment_auto','voorraad_fust_instellingen_opslaan','voorraad_koppeling_check','voorraad_instellingen_opslaan','voorraad_ensure_defaults','voorraad_defaults_check','voorraad_defaults_versie','voorraad_canoniek_artikel','voorraad_canoniek_sleutel','voorraad_artikel_prijs','voorraad_dubbele_voorraadregels','voorraad_correctie_1131','voorraad_correctie_1132','voorraad_gateway_routes_check','voorraad_placeholder_code_reason','trg_products_placeholder_code','voorraad_fust_juist','voorraad_correctie_1121','voorraad_correctie_1121_regels','voorraad_correctie_1123','voorraad_correctie_1124','voorraad_load_carriers','voorraad_packing_from_floriday','voorraad_packing_missing','voorraad_correctie_1126','voorraad_correctie_1127','trg_voorraad_link_primary_packing','voorraad_order_geannuleerd','voorraad_article_vbn','voorraad_correctie_1112','voorraad_reverse_order_mutations','voorraad_withdraw_stock_row','voorraad_floriday_klaarzetten_toegestaan','voorraad_afboeken','voorraad_voorraad_opslaan','voorraad_correctie_1140','voorraad_overzicht_orderregel_koppeling','voorraad_orderregel_sleutel','ensure_voorraad_local_context','get_stock_context','voorraad_stock_context_status','voorraad_fcsq_local_guard','_floriday_queue_cleanup_batch','_migrate_rewrite_stock_key','_stock_import_caller','apply_fust_stock_mutation','apply_stock_consumption','apply_stock_reservation','autolink_product_to_floriday','backfill_open_order_reservations','calculate_floriday_desired_stock','calculate_structure_available','canonicalize_trade_item_stock_key','check_product_structure','claim_floriday_base_supply','claim_floriday_stock_queue','cleanup_floriday_stock_queue','cleanup_voorraad_module_history','complete_floriday_base_supply','complete_floriday_stock_queue','complete_floriday_stock_queue_outcome','consume_bom_for_production','consume_order_item','drain_inventory_recompute_queue','enqueue_floriday_continuous_stock','enqueue_floriday_continuous_stock_after_warehouse_preference','enqueue_structure_recompute','ensure_inventory_recompute_drain','ensure_trade_item_stock_row','ensure_trade_item_stock_row_key','fcsq_block_disabled','fcsq_fallback_supersede','fcsq_wake_base_supply','find_trade_item_key','floriday_adopt_connection','floriday_autoheal_connections','floriday_capture_stock_snapshot','floriday_cleanup_stock_queue_v2','floriday_compact_stock_snapshots','floriday_default_owner','floriday_desired_stock_regression_check','floriday_ensure_inventory_settings','floriday_erp_leading_preview','floriday_fill_publish_horizon','floriday_follow_apply','floriday_ingest_sales_orders','floriday_leading_summary','floriday_make_erp_leading','floriday_norm_offer_forms','floriday_orphan_settings_count','floriday_publish_horizon_weeks','floriday_queue_erp_guard','floriday_queue_first_positive','floriday_recompute_inventory','floriday_relink_products','floriday_requeue_dead_letters','floriday_settings_change_recompute','floriday_settings_change_recompute_stmt','floriday_stock_change_recompute','floriday_stock_queue_supersede','floriday_takeover_apply','floriday_takeover_fetch_state','floriday_takeover_store_forms','floriday_takeover_undo','fust_negative_stock_report','fust_types_duplicate_guard','get_active_floriday_context','get_group_allocation_keys','get_group_head_key','get_product_stock','get_product_structure_totals','get_product_structure_totals_batch','inventory_inline_budget_ok','merge_products','normalize_order_source','process_inventory_recompute_queue','produce_designed_product','product_active_group_id','product_id_from_ref','product_is_set','product_label','product_member_group_ids','product_own_available','product_packings_derive','product_packings_replace','product_packings_sync_from_floriday','product_primary_pieces','product_set_depth_above','product_set_depth_below','product_structure_ancestors','product_structure_max_depth','product_structure_path','product_tray_stickers_touch','product_type','promote_trade_item_key','recompute_all_reserved_stock','recompute_order_status','recompute_reserved_for_order','recompute_reserved_stock','requeue_skipped_stock_rows','reset_stalled_floriday_stock_queue','resolve_group_allocation','resolve_group_member_stock_key','resolve_product_id_for_item','resolve_stock_trade_item_key','reverse_production','run_floriday_base_supply_process','run_floriday_continuous_stock_sync','run_floriday_follow','run_floriday_stock_queue_process','run_floriday_stock_reconcile','run_logistics_tasks_dispatch','self_heal_floriday_links','set_module_runtime_config_updated_at','set_product_bom_tenant','set_product_stock','set_updated_at_order_fust_bookings','stock_import_cleanup','stock_import_create','stock_import_process_chunk','stock_import_stop','stock_import_undo_chunk','sync_order_item_fusten','sync_products_quantity_from_stock','touch_fust_types_updated_at','trade_item_drafts_readonly','trade_item_prices_touch_updated_at','trade_item_stock_duplicates','trg_cart_numbers_sync_order_status_fn','trg_cart_session_consume_fn','trg_check_product_structure','trg_floriday_cache_relink_fn','trg_floriday_trade_items_autolink_fn','trg_order_items_autolink_fn','trg_order_items_fusten','trg_order_items_reserve_fn','trg_orders_cancel_release_fn','trg_orders_consume_on_complete_fn','trg_orders_default_phase_fn','trg_orders_external_fulfilled_fn','trg_orders_recompute_reserved_fn','trg_pgi_assign_sort_order','trg_pgi_order_taken_check','trg_process_recompute_on_commit','trg_product_packings_derive_fn','trg_products_autolink_fn','trg_products_floriday_link_enqueue','trg_products_seed_stock_fn','trg_requeue_no_warehouse_cache','trg_requeue_no_warehouse_connection','trg_requeue_no_warehouse_item_pref','trg_requeue_skipped_no_price','trg_stock_structure_recompute_ins','trg_stock_structure_recompute_upd','trg_structure_change_recompute','trg_trade_item_drafts_autolink_fn','trg_trade_item_stock_auto_bom','trg_trade_item_stock_auto_bom_reverse','trg_trade_item_stock_log_change','trg_trade_item_stock_reserve_fn','trg_trade_item_stock_zero_watch','trg_trade_items_cache_autolink_fn','update_updated_at_column','voorraad_alert_floriday_not_updated','voorraad_can_write','voorraad_cleanup_old_rows','voorraad_consume_external_fulfilled','voorraad_correctie_1098','voorraad_correctie_status','voorraad_external_fulfilled_ids','voorraad_floriday_leads_key','voorraad_fust_dimensions','voorraad_hook_base_url','voorraad_hook_on','voorraad_hook_post','voorraad_hook_switch_jobs','voorraad_install_check','voorraad_is_server','voorraad_mark_floriday_sold_before_switch','voorraad_production_enabled','voorraad_schema_ddl','voorraad_touch_updated_at','voorraad_vereis','voorraad_week_is_past'];
9993
10792
  v_t text[] := ARRAY['trg_voorraad_link_primary_packing@products','aa0_fcsq_local_context_guard@floriday_continuous_stock_queue','aa_fcsq_erp_leading_guard@floriday_continuous_stock_queue','aa_fis_norm_offer_forms@floriday_inventory_settings','aa_pgi_assign_sort_order@product_group_items','fcsq_block_disabled@floriday_continuous_stock_queue','fcsq_wake_base_supply_ins@floriday_continuous_stock_queue','fcsq_wake_base_supply_upd@floriday_continuous_stock_queue','fust_stock_mutations_apply@fust_stock_mutations','fust_types_duplicate_guard@fust_types','fust_types_touch@fust_types','product_packings_derive_trg@product_packings','product_packings_updated_at@product_packings','set_product_bom_tenant_trigger@product_bom_items','trade_item_prices_set_updated_at@trade_item_prices','trg_cart_numbers_sync_order_status@cart_numbers','trg_cart_session_consume@cart_control_sessions','trg_check_product_structure@product_bom_items','trg_check_product_structure@product_group_items','trg_check_product_structure@product_groups','trg_fcs_queue_updated_at@floriday_continuous_stock_queue','trg_fcsq_fallback_supersede@floriday_continuous_stock_queue','trg_fcsq_supersede@floriday_continuous_stock_queue','trg_floriday_cache_relink@floriday_trade_items_cache','trg_floriday_ensure_inventory_settings@products','trg_floriday_gate_stock_queue@floriday_continuous_stock_queue','trg_floriday_settings_change_recompute_ins@floriday_inventory_settings','trg_floriday_settings_change_recompute_upd@floriday_inventory_settings','trg_floriday_stock_change_recompute@trade_item_stock','trg_floriday_takeover_runs_updated_at@floriday_takeover_runs','trg_floriday_trade_items_autolink@floriday_trade_items_cache','trg_fust_types_requeue_blocked@fust_types','trg_module_runtime_config_updated_at@module_runtime_config','trg_order_fust_bookings_updated_at@order_fust_bookings','trg_order_items_autolink@order_items','trg_order_items_fusten_aiud@order_items','trg_order_items_reserve@order_items','trg_orders_cancel_release@orders','trg_orders_consume_on_complete@orders','trg_orders_default_phase@orders','trg_orders_external_fulfilled@orders','trg_orders_recompute_reserved@orders','trg_prevent_duplicate_product_draft@trade_item_drafts','trg_product_tray_stickers_touch@product_tray_stickers','trg_products_autolink@products','trg_products_floriday_link_enqueue@products','trg_products_seed_stock@products','trg_requeue_no_warehouse_cache@floriday_trade_items_cache','trg_requeue_no_warehouse_connection@floriday_connections','trg_requeue_no_warehouse_item_pref@trade_item_warehouse_preferences','trg_requeue_skipped_no_price@products','trg_stock_mutation_alert@stock_mutation_log','trg_stock_structure_recompute_ins@trade_item_stock','trg_stock_structure_recompute_upd@trade_item_stock','trg_structure_change_recompute@product_bom_items','trg_structure_change_recompute@product_group_items','trg_structure_change_recompute@product_groups','trg_sync_layer_stock_from_trade_stock@trade_item_stock','trg_sync_products_quantity_from_stock_del@trade_item_stock','trg_sync_products_quantity_from_stock_iu@trade_item_stock','trg_trade_item_drafts_autolink@trade_item_drafts','trg_trade_item_drafts_readonly@trade_item_drafts','trg_trade_item_stock_canonical_key@trade_item_stock','trg_trade_item_stock_publish@trade_item_stock','trg_trade_item_stock_reserve@trade_item_stock','trg_trade_item_stock_updated_at@trade_item_stock','trg_trade_item_stock_zero_watch@trade_item_stock','trg_trade_item_supply_lines_touch@trade_item_supply_lines','trg_trade_item_warehouse_preference_enqueue_stock@trade_item_warehouse_preferences','trg_trade_items_cache_autolink@floriday_trade_items_cache','trg_voorraad_buffer_state_updated_at@voorraad_buffer_state','trg_voorraad_event_log_updated_at@voorraad_logistics_event_log','update_trade_item_drafts_updated_at@trade_item_drafts','update_trade_item_warehouse_preferences_updated_at@trade_item_warehouse_preferences','update_voorraad_custom_fields_updated_at@voorraad_custom_fields','update_voorraad_publish_jobs_updated_at@voorraad_publish_jobs','update_voorraad_publish_learnings_updated_at@voorraad_publish_learnings','update_voorraad_publish_steps_updated_at@voorraad_publish_steps','voorraad_hook_switch_jobs@module_runtime_config','zz_fcsq_first_positive@floriday_continuous_stock_queue'];
9994
10793
  v_p text[] := ARRAY['Authenticated can add learnings@voorraad_publish_learnings','Authenticated can delete voorraad custom fields@voorraad_custom_fields','Authenticated can insert vbn required characteristics@floriday_vbn_required_characteristics','Authenticated can insert voorraad custom fields@voorraad_custom_fields','Authenticated can read continuous stock cache@floriday_continuous_stock_cache','Authenticated can read learnings@voorraad_publish_learnings','Authenticated can read vbn required characteristics@floriday_vbn_required_characteristics','Authenticated can read voorraad custom fields@voorraad_custom_fields','Authenticated can update learnings@voorraad_publish_learnings','Authenticated can update vbn required characteristics@floriday_vbn_required_characteristics','Authenticated can update voorraad custom fields@voorraad_custom_fields','Authenticated delete trade_item_warehouse_preferences@trade_item_warehouse_preferences','Authenticated insert trade_item_warehouse_preferences@trade_item_warehouse_preferences','Authenticated select trade_item_warehouse_preferences@trade_item_warehouse_preferences','Authenticated update trade_item_warehouse_preferences@trade_item_warehouse_preferences','Eigen momentopnamen lezen@inventory_stock_snapshots','Eigen voorraadinstellingen beheren@floriday_inventory_settings','Eigenaar leest importporties@stock_import_chunks','Eigenaar leest importregels@stock_import_rows','Fust mutations delete@fust_stock_mutations','Fust mutations insert@fust_stock_mutations','Fust mutations read@fust_stock_mutations','Fust mutations update@fust_stock_mutations','Fust types delete@fust_types','Fust types insert@fust_types','Fust types read@fust_types','Fust types update@fust_types','Ingelogde gebruikers kunnen bufferstatus lezen@voorraad_buffer_state','Ingelogde gebruikers kunnen de eventlog lezen@voorraad_logistics_event_log','Ingelogde gebruikers kunnen fustboekingen lezen@order_fust_bookings','Ingelogde gebruikers lezen drempels@inventory_recompute_limits','Ingelogd mag standaardstatus lezen@voorraad_defaults_state','Ingelogde gebruikers lezen opruimlogboek@structure_cleanup_log','Ingelogde gebruikers lezen opruimruns@floriday_queue_cleanup_runs','Ingelogden beheren traystickers@product_tray_stickers','Ingelogden lezen herberekenwachtrij@inventory_recompute_queue','Ingelogden lezen voorraadmeldingen@stock_integrity_alerts','Ingelogden markeren voorraadmeldingen als gezien@stock_integrity_alerts','Own queue rows select@floriday_continuous_stock_queue','Owner delete trade_item_drafts@trade_item_drafts','Owner insert trade_item_drafts@trade_item_drafts','Owner select trade_item_drafts@trade_item_drafts','Owner update trade_item_drafts@trade_item_drafts','Signed-in users read reconcile runs@floriday_reconcile_runs','Stock delete@trade_item_stock','Stock insert@trade_item_stock','Stock read@trade_item_stock','Stock update@trade_item_stock','Synchronisatieposities lezen@floriday_sync_cursors','Users can insert own publish attempts@voorraad_publish_attempts','Users can read own publish attempts@voorraad_publish_attempts','Users can read own stock mutations@stock_mutation_log','Users manage own import runs@stock_import_runs','Users manage own supply lines@trade_item_supply_lines','Users manage own trade item prices@trade_item_prices','Users manage their own publish jobs@voorraad_publish_jobs','Users manage their own publish steps@voorraad_publish_steps','Verwerkte orders lezen@floriday_sales_order_ingest','Voorraadschrijvers lezen overnameregels@floriday_takeover_items','Voorraadschrijvers lezen overnames@floriday_takeover_runs','authenticated can insert merge log@product_merge_log','authenticated can read merge log@product_merge_log','service role only@voorraad_media_upload_failures','product_packings_read@product_packings','product_packings_write@product_packings'];
9995
10794
  v_open text[] := ARRAY['voorraad_is_server','voorraad_vereis'];
@@ -10034,6 +10833,22 @@ REVOKE ALL ON FUNCTION voorraad_install_check() FROM PUBLIC;
10034
10833
  REVOKE ALL ON FUNCTION voorraad_install_check() FROM anon;
10035
10834
  GRANT EXECUTE ON FUNCTION voorraad_install_check() TO authenticated;
10036
10835
  GRANT EXECUTE ON FUNCTION voorraad_install_check() TO service_role;
10836
+ CREATE OR REPLACE FUNCTION public.voorraad_instellingen_lezen()
10837
+ RETURNS jsonb
10838
+ LANGUAGE plpgsql
10839
+ STABLE SECURITY DEFINER
10840
+ SET search_path TO 'public'
10841
+ AS $function$
10842
+ BEGIN
10843
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_instellingen_lezen');
10844
+ RETURN jsonb_build_object('standaard_sturing',
10845
+ COALESCE((SELECT standaard_sturing_nieuwe_floriday FROM public.voorraad_instellingen WHERE id), 'lokaal'));
10846
+ END $function$
10847
+ ;
10848
+ REVOKE ALL ON FUNCTION voorraad_instellingen_lezen() FROM PUBLIC;
10849
+ REVOKE ALL ON FUNCTION voorraad_instellingen_lezen() FROM anon;
10850
+ GRANT EXECUTE ON FUNCTION voorraad_instellingen_lezen() TO authenticated;
10851
+ GRANT EXECUTE ON FUNCTION voorraad_instellingen_lezen() TO service_role;
10037
10852
  CREATE OR REPLACE FUNCTION public.voorraad_instellingen_opslaan(p_standaard_sturing text)
10038
10853
  RETURNS jsonb
10039
10854
  LANGUAGE plpgsql
@@ -10055,6 +10870,22 @@ REVOKE ALL ON FUNCTION voorraad_instellingen_opslaan(text) FROM PUBLIC;
10055
10870
  REVOKE ALL ON FUNCTION voorraad_instellingen_opslaan(text) FROM anon;
10056
10871
  GRANT EXECUTE ON FUNCTION voorraad_instellingen_opslaan(text) TO authenticated;
10057
10872
  GRANT EXECUTE ON FUNCTION voorraad_instellingen_opslaan(text) TO service_role;
10873
+ CREATE OR REPLACE FUNCTION public.voorraad_is_beheerder()
10874
+ RETURNS boolean
10875
+ LANGUAGE plpgsql
10876
+ STABLE SECURITY DEFINER
10877
+ SET search_path TO 'public'
10878
+ AS $function$
10879
+ BEGIN
10880
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_is_beheerder');
10881
+ IF public.voorraad_is_server() THEN RETURN true; END IF;
10882
+ RETURN EXISTS (SELECT 1 FROM public.user_roles r WHERE r.user_id = auth.uid() AND r.role::text IN ('admin','superadmin'));
10883
+ END $function$
10884
+ ;
10885
+ REVOKE ALL ON FUNCTION voorraad_is_beheerder() FROM PUBLIC;
10886
+ REVOKE ALL ON FUNCTION voorraad_is_beheerder() FROM anon;
10887
+ GRANT EXECUTE ON FUNCTION voorraad_is_beheerder() TO authenticated;
10888
+ GRANT EXECUTE ON FUNCTION voorraad_is_beheerder() TO service_role;
10058
10889
  CREATE OR REPLACE FUNCTION public.voorraad_is_server()
10059
10890
  RETURNS boolean
10060
10891
  LANGUAGE sql
@@ -10087,7 +10918,8 @@ BEGIN
10087
10918
  SELECT count(*) INTO v_zonder FROM public.floriday_trade_items_cache t
10088
10919
  WHERE NOT (COALESCE(t.is_deleted,false) OR COALESCE((t.data->>'isDeleted')::boolean,false))
10089
10920
  AND t.connection_id = (SELECT x.connection_id FROM public.get_active_floriday_context() x LIMIT 1)
10090
- AND NOT EXISTS (SELECT 1 FROM public.products p WHERE lower(btrim(p.floriday_trade_item_id)) = lower(t.floriday_id));
10921
+ AND NOT EXISTS (SELECT 1 FROM public.products p WHERE lower(btrim(p.floriday_trade_item_id)) = lower(t.floriday_id))
10922
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_floriday_vervallen v WHERE v.floriday_id = t.floriday_id AND v.opgeheven_at IS NULL);
10091
10923
  SELECT count(*) INTO v_twijfel FROM public.voorraad_koppel_twijfel WHERE opgelost_at IS NULL;
10092
10924
  v_start := EXISTS (SELECT 1 FROM public.fust_startmoment);
10093
10925
  IF jsonb_array_length(v_dups) > 0 THEN v_f := v_f || to_jsonb('Er zijn artikelen met hetzelfde Floriday-id; automatisch aanmaken staat stil'::text); END IF;
@@ -10120,6 +10952,165 @@ REVOKE ALL ON FUNCTION voorraad_lever_datum(text) FROM PUBLIC;
10120
10952
  REVOKE ALL ON FUNCTION voorraad_lever_datum(text) FROM anon;
10121
10953
  GRANT EXECUTE ON FUNCTION voorraad_lever_datum(text) TO authenticated;
10122
10954
  GRANT EXECUTE ON FUNCTION voorraad_lever_datum(text) TO service_role;
10955
+ CREATE OR REPLACE FUNCTION public.voorraad_lijst(p_zoek text DEFAULT NULL::text, p_filter text DEFAULT 'alles'::text, p_sort text DEFAULT 'naam'::text, p_oplopend boolean DEFAULT true, p_pagina integer DEFAULT 1, p_grootte integer DEFAULT 25)
10956
+ RETURNS jsonb
10957
+ LANGUAGE plpgsql
10958
+ STABLE SECURITY DEFINER
10959
+ SET search_path TO 'public'
10960
+ AS $function$
10961
+ DECLARE
10962
+ v_conn uuid; v_ctx uuid; v_kop boolean; v_gr int; v_off int; v_q text;
10963
+ v_tel jsonb; v_totaal int; v_rijen jsonb; v_meld jsonb; v_twijfel int; v_twijfel_rijen jsonb := '[]'::jsonb;
10964
+ BEGIN
10965
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_lijst');
10966
+ IF COALESCE(p_filter,'alles') NOT IN ('alles','onder_minimum','verpakking','niet_gekoppeld','verwijderd_floriday','prijs','twijfel','gearchiveerd') THEN
10967
+ RAISE EXCEPTION 'Onbekend filter "%"', p_filter USING ERRCODE = '22023';
10968
+ END IF;
10969
+ IF COALESCE(p_sort,'naam') NOT IN ('naam','artikelnummer','liggend','gereserveerd','vrij','inkomend','prijs') THEN
10970
+ RAISE EXCEPTION 'Onbekende sortering "%"', p_sort USING ERRCODE = '22023';
10971
+ END IF;
10972
+ v_gr := LEAST(GREATEST(COALESCE(p_grootte,25),1),500);
10973
+ v_off := (GREATEST(COALESCE(p_pagina,1),1) - 1) * v_gr;
10974
+ v_q := NULLIF(btrim(COALESCE(p_zoek,'')),'');
10975
+ BEGIN SELECT x.connection_id INTO v_conn FROM public.get_active_floriday_context() x LIMIT 1;
10976
+ EXCEPTION WHEN others THEN v_conn := NULL; END;
10977
+ v_kop := v_conn IS NOT NULL;
10978
+ BEGIN SELECT c.connection_id INTO v_ctx FROM public.get_stock_context() c LIMIT 1;
10979
+ EXCEPTION WHEN others THEN v_ctx := NULL; END;
10980
+
10981
+ WITH st AS (
10982
+ SELECT DISTINCT ON (x.trade_item_key) x.trade_item_key, x.physical_stock, x.reserved_stock, x.minimum_stock, x.incoming_stock
10983
+ FROM public.trade_item_stock x WHERE x.archived_at IS NULL
10984
+ ORDER BY x.trade_item_key, (x.connection_id IS NOT DISTINCT FROM v_ctx) DESC, x.updated_at DESC
10985
+ ), basis AS (
10986
+ SELECT p.id,
10987
+ COALESCE(NULLIF(btrim(p.product),''), NULLIF(p.custom_fields#>>'{floriday_payload,name}',''), '(naamloos)') AS naam,
10988
+ COALESCE(NULLIF(btrim(p.article_number),''), NULLIF(p.custom_fields#>>'{floriday_payload,articleCode}','')) AS artikelnummer,
10989
+ NULLIF(btrim(COALESCE(p.floriday_trade_item_id,'')),'') AS floriday_id,
10990
+ p.sale_price AS prijs, p.unit AS eenheid, p.gearchiveerd_at, p.barcode, p.floriday_verwijderd_at,
10991
+ -- Voorraadkolommen zijn NOT NULL: COALESCE kiest dus de eerste bestaande rij (s1, dan s2, dan s3).
10992
+ COALESCE(s1.physical_stock, s2.physical_stock, s3.physical_stock, 0) AS liggend,
10993
+ COALESCE(s1.reserved_stock, s2.reserved_stock, s3.reserved_stock, 0) AS gereserveerd,
10994
+ COALESCE(s1.minimum_stock, s2.minimum_stock, s3.minimum_stock, 0) AS minimum,
10995
+ COALESCE(s1.incoming_stock, s2.incoming_stock, s3.incoming_stock, 0) AS inkomend,
10996
+ COALESCE(s1.trade_item_key, s2.trade_item_key, s3.trade_item_key) AS voorraad_sleutel,
10997
+ CASE WHEN EXISTS (SELECT 1 FROM public.product_groups g WHERE g.head_product_id = p.id AND g.is_active) THEN 'GROUP'
10998
+ WHEN EXISTS (SELECT 1 FROM public.product_bom_items b WHERE b.parent_product_id = p.id) THEN 'SET'
10999
+ ELSE 'NORMAL' END AS soort,
11000
+ EXISTS (SELECT 1 FROM public.product_bom_items b WHERE b.component_product_id = p.id) AS is_onderdeel,
11001
+ EXISTS (SELECT 1 FROM public.product_packings k WHERE k.product_id = p.id AND k.is_primary) AS heeft_verpakking,
11002
+ COALESCE((p.custom_fields#>>'{voorraad_packing_link,ok}')::boolean, true) AS verpakking_ok
11003
+ FROM public.products p
11004
+ -- Zelfde volgorde als voorraad_canoniek_sleutel: trade_item_key, dan Floriday-id, dan product:<id>.
11005
+ LEFT JOIN st s1 ON s1.trade_item_key = NULLIF(btrim(COALESCE(p.trade_item_key,'')),'')
11006
+ LEFT JOIN st s2 ON s2.trade_item_key = NULLIF(btrim(COALESCE(p.floriday_trade_item_id,'')),'')
11007
+ LEFT JOIN st s3 ON s3.trade_item_key = 'product:' || p.id::text
11008
+ -- Spiegels (id = Floriday-id van een ander artikel) tellen niet mee.
11009
+ WHERE NOT EXISTS (SELECT 1 FROM public.products q
11010
+ WHERE q.id <> p.id AND NULLIF(btrim(COALESCE(q.floriday_trade_item_id,'')),'') IS NOT NULL
11011
+ AND lower(btrim(q.floriday_trade_item_id)) = lower(p.id::text))
11012
+ ), gemarkeerd AS (
11013
+ SELECT b.*,
11014
+ GREATEST(b.liggend - b.gereserveerd, 0) AS vrij,
11015
+ (b.minimum > 0 AND (b.liggend - b.gereserveerd) < b.minimum) AS f_onder_minimum,
11016
+ (b.soort <> 'GROUP' AND (NOT b.heeft_verpakking OR NOT b.verpakking_ok)) AS f_verpakking,
11017
+ (v_kop AND b.floriday_id IS NULL AND b.floriday_verwijderd_at IS NULL) AS f_niet_gekoppeld,
11018
+ (v_kop AND b.floriday_verwijderd_at IS NOT NULL) AS f_verwijderd_floriday,
11019
+ (lower(btrim(COALESCE(b.eenheid,''))) <> 'stuks' OR b.prijs IS NULL OR b.prijs <= 0) AS f_prijs
11020
+ FROM basis b
11021
+ WHERE v_q IS NULL
11022
+ OR b.naam ILIKE '%' || v_q || '%' OR COALESCE(b.artikelnummer,'') ILIKE '%' || v_q || '%'
11023
+ OR COALESCE(b.barcode,'') ILIKE '%' || v_q || '%' OR COALESCE(b.floriday_id,'') ILIKE v_q || '%'
11024
+ ), tel AS (
11025
+ SELECT jsonb_build_object(
11026
+ 'alles', count(*) FILTER (WHERE gearchiveerd_at IS NULL),
11027
+ 'onder_minimum', count(*) FILTER (WHERE gearchiveerd_at IS NULL AND f_onder_minimum),
11028
+ 'verpakking', count(*) FILTER (WHERE gearchiveerd_at IS NULL AND f_verpakking),
11029
+ 'niet_gekoppeld', count(*) FILTER (WHERE gearchiveerd_at IS NULL AND f_niet_gekoppeld),
11030
+ 'verwijderd_floriday', count(*) FILTER (WHERE gearchiveerd_at IS NULL AND f_verwijderd_floriday),
11031
+ 'prijs', count(*) FILTER (WHERE gearchiveerd_at IS NULL AND f_prijs),
11032
+ 'gearchiveerd', count(*) FILTER (WHERE gearchiveerd_at IS NOT NULL),
11033
+ 'onderdelen_zonder_prijs', count(*) FILTER (WHERE gearchiveerd_at IS NULL AND is_onderdeel AND f_prijs)) AS t
11034
+ FROM gemarkeerd
11035
+ ), gefilterd AS (
11036
+ SELECT * FROM gemarkeerd g
11037
+ WHERE CASE COALESCE(p_filter,'alles')
11038
+ WHEN 'gearchiveerd' THEN g.gearchiveerd_at IS NOT NULL
11039
+ WHEN 'twijfel' THEN false
11040
+ ELSE g.gearchiveerd_at IS NULL AND CASE p_filter
11041
+ WHEN 'onder_minimum' THEN g.f_onder_minimum
11042
+ WHEN 'verpakking' THEN g.f_verpakking
11043
+ WHEN 'niet_gekoppeld' THEN g.f_niet_gekoppeld
11044
+ WHEN 'verwijderd_floriday' THEN g.f_verwijderd_floriday
11045
+ WHEN 'prijs' THEN g.f_prijs
11046
+ ELSE true END
11047
+ END
11048
+ ), pagina AS (
11049
+ SELECT * FROM gefilterd
11050
+ ORDER BY
11051
+ CASE WHEN p_oplopend AND p_sort = 'naam' THEN lower(naam) END ASC,
11052
+ CASE WHEN NOT p_oplopend AND p_sort = 'naam' THEN lower(naam) END DESC,
11053
+ CASE WHEN p_oplopend AND p_sort = 'artikelnummer' THEN lower(artikelnummer) END ASC NULLS LAST,
11054
+ CASE WHEN NOT p_oplopend AND p_sort = 'artikelnummer' THEN lower(artikelnummer) END DESC NULLS LAST,
11055
+ CASE WHEN p_sort NOT IN ('naam','artikelnummer') THEN
11056
+ (CASE p_sort WHEN 'liggend' THEN liggend WHEN 'gereserveerd' THEN gereserveerd WHEN 'vrij' THEN vrij
11057
+ WHEN 'inkomend' THEN inkomend ELSE prijs END)::numeric * CASE WHEN p_oplopend THEN 1 ELSE -1 END
11058
+ END ASC NULLS LAST,
11059
+ lower(naam), id
11060
+ LIMIT v_gr OFFSET v_off
11061
+ )
11062
+ SELECT (SELECT t FROM tel), (SELECT count(*) FROM gefilterd),
11063
+ (SELECT COALESCE(jsonb_agg(jsonb_build_object(
11064
+ 'id', pg.id, 'naam', pg.naam, 'artikelnummer', pg.artikelnummer,
11065
+ 'foto', COALESCE(NULLIF(p.image_url,''), NULLIF(p.custom_fields#>>'{floriday_payload,images,0,url}',''), NULLIF(p.custom_fields#>>'{floriday_payload,images,0,previewUrl}','')),
11066
+ 'floriday_id', pg.floriday_id,
11067
+ 'prijs', pg.prijs, 'eenheid', pg.eenheid, 'soort', pg.soort, 'is_onderdeel', pg.is_onderdeel,
11068
+ 'liggend', pg.liggend, 'gereserveerd', pg.gereserveerd, 'minimum', pg.minimum, 'inkomend', pg.inkomend, 'vrij', pg.vrij,
11069
+ 'voorraad_sleutel', pg.voorraad_sleutel, 'gearchiveerd_at', pg.gearchiveerd_at,
11070
+ 'onder_minimum', pg.f_onder_minimum,
11071
+ 'status', CASE WHEN pg.gearchiveerd_at IS NOT NULL THEN 'gearchiveerd'
11072
+ WHEN pg.f_verwijderd_floriday THEN 'verwijderd_floriday'
11073
+ WHEN pg.f_verpakking THEN 'verpakking' WHEN pg.f_niet_gekoppeld THEN 'niet_gekoppeld'
11074
+ WHEN pg.f_prijs THEN 'prijs' ELSE 'ok' END)
11075
+ ORDER BY pg.rn), '[]'::jsonb)
11076
+ FROM (SELECT pagina.*, row_number() OVER () AS rn FROM pagina) pg JOIN public.products p ON p.id = pg.id)
11077
+ INTO v_tel, v_totaal, v_rijen;
11078
+
11079
+ -- Samenstellingen en productgroepen: vrij beschikbaar uit de gedeelde rekenfunctie (alleen voor deze pagina).
11080
+ IF v_ctx IS NOT NULL THEN
11081
+ SELECT COALESCE(jsonb_agg(CASE WHEN r->>'soort' IN ('SET','GROUP') THEN
11082
+ r || jsonb_build_object('vrij', COALESCE((public.calculate_structure_available(v_ctx, (r->>'id')::uuid)->>'available')::numeric, 0))
11083
+ ELSE r END ORDER BY o), '[]'::jsonb)
11084
+ INTO v_rijen FROM jsonb_array_elements(v_rijen) WITH ORDINALITY AS e(r, o);
11085
+ END IF;
11086
+
11087
+ SELECT count(*) INTO v_twijfel FROM public.voorraad_koppel_twijfel WHERE opgelost_at IS NULL;
11088
+ IF p_filter = 'twijfel' THEN
11089
+ v_totaal := v_twijfel;
11090
+ SELECT COALESCE(jsonb_agg(jsonb_build_object('floriday_id', t.floriday_id, 'naam', t.naam, 'reden', t.reden,
11091
+ 'kandidaten', COALESCE(jsonb_array_length(CASE WHEN jsonb_typeof(t.kandidaten)='array' THEN t.kandidaten END),0),
11092
+ 'laatst_gezien', t.laatst_gezien) ORDER BY t.laatst_gezien DESC), '[]'::jsonb)
11093
+ INTO v_twijfel_rijen
11094
+ FROM (SELECT * FROM public.voorraad_koppel_twijfel WHERE opgelost_at IS NULL
11095
+ ORDER BY laatst_gezien DESC LIMIT v_gr OFFSET v_off) t;
11096
+ END IF;
11097
+
11098
+ v_meld := jsonb_build_object(
11099
+ 'telmoment_ontbreekt', NOT EXISTS (SELECT 1 FROM public.voorraad_telmoment),
11100
+ 'versieronde_mislukt', public.voorraad_floriday_versieronde_melding(),
11101
+ 'onderdelen_zonder_prijs', COALESCE((v_tel->>'onderdelen_zonder_prijs')::int, 0),
11102
+ 'orders_gearchiveerd', public.voorraad_orders_gearchiveerd(NULL));
11103
+
11104
+ RETURN jsonb_build_object('tellers', v_tel || jsonb_build_object('twijfel', v_twijfel),
11105
+ 'totaal', v_totaal, 'rijen', v_rijen, 'twijfel_rijen', v_twijfel_rijen,
11106
+ 'meldingen', v_meld, 'heeft_koppeling', v_kop, 'pagina', GREATEST(COALESCE(p_pagina,1),1), 'grootte', v_gr,
11107
+ 'bijgewerkt', now());
11108
+ END $function$
11109
+ ;
11110
+ REVOKE ALL ON FUNCTION voorraad_lijst(text,text,text,boolean,integer,integer) FROM PUBLIC;
11111
+ REVOKE ALL ON FUNCTION voorraad_lijst(text,text,text,boolean,integer,integer) FROM anon;
11112
+ GRANT EXECUTE ON FUNCTION voorraad_lijst(text,text,text,boolean,integer,integer) TO authenticated;
11113
+ GRANT EXECUTE ON FUNCTION voorraad_lijst(text,text,text,boolean,integer,integer) TO service_role;
10123
11114
  CREATE OR REPLACE FUNCTION public.voorraad_load_carriers()
10124
11115
  RETURNS TABLE(code text, naam text, volgorde integer)
10125
11116
  LANGUAGE plpgsql
@@ -10274,6 +11265,7 @@ BEGIN
10274
11265
  IF v_k IS NULL THEN RETURN jsonb_build_object('status', 'geen_match'); END IF;
10275
11266
  IF array_length(v_k, 1) > 1 THEN RETURN jsonb_build_object('status', 'meerdere_kandidaten', 'kandidaten', to_jsonb(v_k)); END IF;
10276
11267
  SELECT array_agg(p.id) INTO v_p FROM public.products p WHERE lower(btrim(p.floriday_trade_item_id)) = v_k[1];
11268
+ IF v_p IS NULL THEN SELECT array_agg(a.product_id) INTO v_p FROM public.voorraad_floriday_alias a WHERE lower(a.floriday_id) = v_k[1]; END IF;
10277
11269
  IF v_p IS NULL THEN RETURN jsonb_build_object('status', 'geen_eigen_artikel', 'trade_item_id', v_k[1]); END IF;
10278
11270
  IF array_length(v_p, 1) > 1 THEN RETURN jsonb_build_object('status', 'meerdere_artikelen', 'trade_item_id', v_k[1], 'artikelen', to_jsonb(v_p)); END IF;
10279
11271
  RETURN jsonb_build_object('status', 'eenduidig', 'trade_item_id', v_k[1], 'product_id', v_p[1]);
@@ -10283,6 +11275,33 @@ REVOKE ALL ON FUNCTION voorraad_orderregel_trade_item_bron(uuid,text) FROM PUBLI
10283
11275
  REVOKE ALL ON FUNCTION voorraad_orderregel_trade_item_bron(uuid,text) FROM anon;
10284
11276
  GRANT EXECUTE ON FUNCTION voorraad_orderregel_trade_item_bron(uuid,text) TO authenticated;
10285
11277
  GRANT EXECUTE ON FUNCTION voorraad_orderregel_trade_item_bron(uuid,text) TO service_role;
11278
+ CREATE OR REPLACE FUNCTION public.voorraad_orders_gearchiveerd(p_product_id uuid DEFAULT NULL::uuid)
11279
+ RETURNS jsonb
11280
+ LANGUAGE plpgsql
11281
+ STABLE SECURITY DEFINER
11282
+ SET search_path TO 'public'
11283
+ AS $function$
11284
+ BEGIN
11285
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_orders_gearchiveerd');
11286
+ RETURN COALESCE((
11287
+ SELECT jsonb_agg(jsonb_build_object('id', x.id, 'naam', x.naam, 'regels', x.regels, 'laatste', x.laatste) ORDER BY x.laatste DESC)
11288
+ FROM (
11289
+ SELECT p.id, COALESCE(NULLIF(btrim(p.product),''), '(naamloos)') AS naam, count(*) AS regels, max(oi.created_at) AS laatste
11290
+ FROM public.products p
11291
+ JOIN public.order_items oi ON oi.product_id = p.id AND oi.created_at >= p.gearchiveerd_at AND oi.quarantined_at IS NULL
11292
+ JOIN public.orders o ON o.id = oi.order_id
11293
+ WHERE p.gearchiveerd_at IS NOT NULL
11294
+ AND (p_product_id IS NULL OR p.id = p_product_id)
11295
+ AND NOT public.voorraad_order_geannuleerd(o.status, o.cancelled_at, o.deleted_at)
11296
+ AND lower(COALESCE(o.status,'')) NOT IN ('verzonden','gereed voor facturatie')
11297
+ GROUP BY p.id
11298
+ LIMIT 20) x), '[]'::jsonb);
11299
+ END $function$
11300
+ ;
11301
+ REVOKE ALL ON FUNCTION voorraad_orders_gearchiveerd(uuid) FROM PUBLIC;
11302
+ REVOKE ALL ON FUNCTION voorraad_orders_gearchiveerd(uuid) FROM anon;
11303
+ GRANT EXECUTE ON FUNCTION voorraad_orders_gearchiveerd(uuid) TO authenticated;
11304
+ GRANT EXECUTE ON FUNCTION voorraad_orders_gearchiveerd(uuid) TO service_role;
10286
11305
  CREATE OR REPLACE FUNCTION public.voorraad_overzicht_orderregel_koppeling(p_voorbeelden integer DEFAULT 5)
10287
11306
  RETURNS jsonb
10288
11307
  LANGUAGE plpgsql
@@ -10764,6 +11783,165 @@ GRANT EXECUTE ON FUNCTION voorraad_touch_updated_at() TO service_role;
10764
11783
  REVOKE EXECUTE ON FUNCTION voorraad_touch_updated_at() FROM anon;
10765
11784
  GRANT EXECUTE ON FUNCTION voorraad_touch_updated_at() TO authenticated;
10766
11785
  GRANT EXECUTE ON FUNCTION voorraad_touch_updated_at() TO service_role;
11786
+ CREATE OR REPLACE FUNCTION public.voorraad_traysticker_opslaan(p_product_id uuid, p_oud_pad text, p_nieuw jsonb)
11787
+ RETURNS jsonb
11788
+ LANGUAGE plpgsql
11789
+ SECURITY DEFINER
11790
+ SET search_path TO 'public'
11791
+ AS $function$
11792
+ DECLARE v_cur text; v_bestaat boolean;
11793
+ BEGIN
11794
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_traysticker_opslaan');
11795
+ IF NOT public.voorraad_can_write() THEN
11796
+ RAISE EXCEPTION 'Je hebt geen schrijfrechten op de voorraad.' USING ERRCODE = '42501'; END IF;
11797
+ PERFORM 1 FROM public.products WHERE id = p_product_id FOR UPDATE;
11798
+ IF NOT FOUND THEN RAISE EXCEPTION 'Artikel niet gevonden.' USING ERRCODE = 'P0002'; END IF;
11799
+ SELECT true, bestandspad INTO v_bestaat, v_cur FROM public.product_tray_stickers WHERE product_id = p_product_id;
11800
+ IF v_cur IS DISTINCT FROM p_oud_pad THEN
11801
+ RETURN jsonb_build_object('ok', false, 'conflicten', jsonb_build_array(jsonb_build_object(
11802
+ 'veld', 'traysticker', 'bij_openen', p_oud_pad, 'hun', v_cur)));
11803
+ END IF;
11804
+ IF p_nieuw IS NULL THEN
11805
+ DELETE FROM public.product_tray_stickers WHERE product_id = p_product_id;
11806
+ ELSE
11807
+ IF NULLIF(btrim(COALESCE(p_nieuw->>'bestand_url','')),'') IS NULL THEN
11808
+ RAISE EXCEPTION 'Geen bestand opgegeven.' USING ERRCODE = '22023'; END IF;
11809
+ INSERT INTO public.product_tray_stickers(product_id, bestand_url, bestandspad, bestandsnaam, bestandsgrootte)
11810
+ VALUES (p_product_id, p_nieuw->>'bestand_url', p_nieuw->>'bestandspad', p_nieuw->>'bestandsnaam', (p_nieuw->>'bestandsgrootte')::int)
11811
+ ON CONFLICT (product_id) DO UPDATE SET bestand_url = EXCLUDED.bestand_url, bestandspad = EXCLUDED.bestandspad,
11812
+ bestandsnaam = EXCLUDED.bestandsnaam, bestandsgrootte = EXCLUDED.bestandsgrootte, updated_at = now();
11813
+ END IF;
11814
+ RETURN jsonb_build_object('ok', true, 'oud_pad', v_cur);
11815
+ END $function$
11816
+ ;
11817
+ REVOKE ALL ON FUNCTION voorraad_traysticker_opslaan(uuid,text,jsonb) FROM PUBLIC;
11818
+ REVOKE ALL ON FUNCTION voorraad_traysticker_opslaan(uuid,text,jsonb) FROM anon;
11819
+ GRANT EXECUTE ON FUNCTION voorraad_traysticker_opslaan(uuid,text,jsonb) TO authenticated;
11820
+ GRANT EXECUTE ON FUNCTION voorraad_traysticker_opslaan(uuid,text,jsonb) TO service_role;
11821
+ CREATE OR REPLACE FUNCTION public.voorraad_twijfel_kiezen(p_floriday_id text, p_product_id uuid)
11822
+ RETURNS jsonb
11823
+ LANGUAGE plpgsql
11824
+ SECURITY DEFINER
11825
+ SET search_path TO 'public'
11826
+ AS $function$
11827
+ DECLARE t public.voorraad_koppel_twijfel%ROWTYPE; p public.products%ROWTYPE; v_eigen text; v_fl_stock numeric; v_eigen_stock numeric;
11828
+ BEGIN
11829
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_twijfel_kiezen');
11830
+ IF NOT public.voorraad_can_write() THEN
11831
+ RAISE EXCEPTION 'Je hebt geen schrijfrechten op de voorraad.' USING ERRCODE = '42501'; END IF;
11832
+ SELECT * INTO t FROM public.voorraad_koppel_twijfel WHERE floriday_id = p_floriday_id FOR UPDATE;
11833
+ IF NOT FOUND OR t.opgelost_at IS NOT NULL THEN
11834
+ RAISE EXCEPTION 'Dit twijfelgeval is al opgelost of bestaat niet meer.' USING ERRCODE = 'P0002'; END IF;
11835
+ SELECT * INTO p FROM public.products WHERE id = p_product_id FOR UPDATE;
11836
+ IF NOT FOUND THEN RAISE EXCEPTION 'Artikel niet gevonden.' USING ERRCODE = 'P0002'; END IF;
11837
+ IF NULLIF(btrim(COALESCE(p.floriday_trade_item_id,'')),'') IS NOT NULL AND lower(btrim(p.floriday_trade_item_id)) <> lower(p_floriday_id) THEN
11838
+ RAISE EXCEPTION 'Dit artikel is al aan een ander Floriday-product gekoppeld.' USING ERRCODE = '23505'; END IF;
11839
+ IF EXISTS (SELECT 1 FROM public.products q WHERE q.id <> p.id AND lower(btrim(COALESCE(q.floriday_trade_item_id,''))) = lower(p_floriday_id)) THEN
11840
+ RAISE EXCEPTION 'Een ander artikel is al aan dit Floriday-product gekoppeld.' USING ERRCODE = '23505'; END IF;
11841
+ -- Twee eigen voorraadregels met voorraad: eerst samenvoegen (nooit stil optellen of weggooien).
11842
+ v_eigen := public.voorraad_canoniek_sleutel(p.id);
11843
+ SELECT COALESCE(sum(physical_stock),0) INTO v_fl_stock FROM public.trade_item_stock WHERE trade_item_key = p_floriday_id AND archived_at IS NULL;
11844
+ SELECT COALESCE(sum(physical_stock),0) INTO v_eigen_stock FROM public.trade_item_stock WHERE trade_item_key = v_eigen AND archived_at IS NULL;
11845
+ IF v_eigen IS DISTINCT FROM p_floriday_id AND v_fl_stock > 0 AND v_eigen_stock > 0 THEN
11846
+ RAISE EXCEPTION 'Het artikel en het Floriday-product hebben elk een eigen voorraad (% en %). Voeg die eerst samen.', v_eigen_stock, v_fl_stock USING ERRCODE = '23505'; END IF;
11847
+
11848
+ UPDATE public.products SET floriday_trade_item_id = p_floriday_id, updated_at = now() WHERE id = p.id;
11849
+ INSERT INTO public.voorraad_koppel_log(run_id, soort, floriday_id, product_id, oud, nieuw)
11850
+ VALUES (gen_random_uuid(), 'zelf_gekozen', p_floriday_id, p.id,
11851
+ jsonb_build_object('floriday_trade_item_id', p.floriday_trade_item_id),
11852
+ jsonb_build_object('floriday_trade_item_id', p_floriday_id, 'door', auth.uid(), 'reden', t.reden));
11853
+ UPDATE public.voorraad_koppel_twijfel SET opgelost_at = now() WHERE floriday_id = p_floriday_id;
11854
+ RETURN jsonb_build_object('ok', true, 'product_id', p.id);
11855
+ END $function$
11856
+ ;
11857
+ REVOKE ALL ON FUNCTION voorraad_twijfel_kiezen(text,uuid) FROM PUBLIC;
11858
+ REVOKE ALL ON FUNCTION voorraad_twijfel_kiezen(text,uuid) FROM anon;
11859
+ GRANT EXECUTE ON FUNCTION voorraad_twijfel_kiezen(text,uuid) TO authenticated;
11860
+ GRANT EXECUTE ON FUNCTION voorraad_twijfel_kiezen(text,uuid) TO service_role;
11861
+ CREATE OR REPLACE FUNCTION public.voorraad_twijfel_lijst(p_product_id uuid DEFAULT NULL::uuid, p_floriday_id text DEFAULT NULL::text)
11862
+ RETURNS jsonb
11863
+ LANGUAGE plpgsql
11864
+ STABLE SECURITY DEFINER
11865
+ SET search_path TO 'public'
11866
+ AS $function$
11867
+ BEGIN
11868
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_twijfel_lijst');
11869
+ RETURN COALESCE((
11870
+ SELECT jsonb_agg(jsonb_build_object(
11871
+ 'floriday_id', t.floriday_id, 'naam', t.naam, 'reden', t.reden, 'laatst_gezien', t.laatst_gezien,
11872
+ 'kandidaten', COALESCE((
11873
+ SELECT jsonb_agg(jsonb_build_object('id', p.id,
11874
+ 'naam', COALESCE(NULLIF(btrim(p.product),''), '(naamloos)'),
11875
+ 'artikelnummer', p.article_number, 'barcode', p.barcode,
11876
+ 'floriday_id', NULLIF(btrim(COALESCE(p.floriday_trade_item_id,'')),''),
11877
+ 'gearchiveerd', p.gearchiveerd_at IS NOT NULL) ORDER BY p.product)
11878
+ FROM public.products p
11879
+ WHERE jsonb_typeof(t.kandidaten) = 'array' AND t.kandidaten ? p.id::text), '[]'::jsonb))
11880
+ ORDER BY t.laatst_gezien DESC)
11881
+ FROM public.voorraad_koppel_twijfel t
11882
+ WHERE t.opgelost_at IS NULL
11883
+ AND (p_floriday_id IS NULL OR t.floriday_id = p_floriday_id)
11884
+ AND (p_product_id IS NULL OR (jsonb_typeof(t.kandidaten) = 'array' AND t.kandidaten ? p_product_id::text))
11885
+ LIMIT 50), '[]'::jsonb);
11886
+ END $function$
11887
+ ;
11888
+ REVOKE ALL ON FUNCTION voorraad_twijfel_lijst(uuid,text) FROM PUBLIC;
11889
+ REVOKE ALL ON FUNCTION voorraad_twijfel_lijst(uuid,text) FROM anon;
11890
+ GRANT EXECUTE ON FUNCTION voorraad_twijfel_lijst(uuid,text) TO authenticated;
11891
+ GRANT EXECUTE ON FUNCTION voorraad_twijfel_lijst(uuid,text) TO service_role;
11892
+ CREATE OR REPLACE FUNCTION public.voorraad_twijfel_nieuw_artikel(p_floriday_id text)
11893
+ RETURNS jsonb
11894
+ LANGUAGE plpgsql
11895
+ SECURITY DEFINER
11896
+ SET search_path TO 'public'
11897
+ AS $function$
11898
+ DECLARE t public.voorraad_koppel_twijfel%ROWTYPE; c record; v_name text; v_gtin text; v_code text; v_photo text;
11899
+ v_new uuid; v_sturing text;
11900
+ BEGIN
11901
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_twijfel_nieuw_artikel');
11902
+ IF NOT public.voorraad_can_write() THEN
11903
+ RAISE EXCEPTION 'Je hebt geen schrijfrechten op de voorraad.' USING ERRCODE = '42501'; END IF;
11904
+ SELECT * INTO t FROM public.voorraad_koppel_twijfel WHERE floriday_id = p_floriday_id FOR UPDATE;
11905
+ IF NOT FOUND OR t.opgelost_at IS NOT NULL THEN
11906
+ RAISE EXCEPTION 'Dit twijfelgeval is al opgelost of bestaat niet meer.' USING ERRCODE = 'P0002'; END IF;
11907
+ IF EXISTS (SELECT 1 FROM public.products q WHERE lower(btrim(COALESCE(q.floriday_trade_item_id,''))) = lower(p_floriday_id)) THEN
11908
+ RAISE EXCEPTION 'Een artikel is al aan dit Floriday-product gekoppeld.' USING ERRCODE = '23505'; END IF;
11909
+ SELECT x.floriday_id, x.data INTO c FROM public.floriday_trade_items_cache x
11910
+ WHERE x.floriday_id = p_floriday_id
11911
+ AND (t.connection_id IS NULL OR x.connection_id = t.connection_id) LIMIT 1;
11912
+ IF NOT FOUND THEN
11913
+ RAISE EXCEPTION 'Het Floriday-artikel staat niet (meer) in de Floriday-gegevens.' USING ERRCODE = 'P0002'; END IF;
11914
+ v_name := NULLIF(btrim(COALESCE(CASE WHEN jsonb_typeof(c.data->'tradeItemName') = 'string' THEN c.data->>'tradeItemName' END,
11915
+ c.data->'tradeItemName'->>'nl', c.data->'tradeItemName'->>'en', t.naam)), '');
11916
+ IF v_name IS NULL THEN RAISE EXCEPTION 'Het Floriday-artikel heeft geen naam; kies een bestaand artikel.' USING ERRCODE = '22023'; END IF;
11917
+ v_gtin := NULLIF(btrim(COALESCE(c.data->>'articleGtin', c.data->>'gtin')), '');
11918
+ v_code := NULLIF(btrim(COALESCE(c.data->>'supplierArticleCode', c.data->>'articleCode')), '');
11919
+ SELECT e->>'url' INTO v_photo FROM jsonb_array_elements(CASE WHEN jsonb_typeof(c.data->'photos') = 'array' THEN c.data->'photos' ELSE '[]'::jsonb END) e
11920
+ ORDER BY COALESCE((e->>'primary')::boolean, false) DESC LIMIT 1;
11921
+ SELECT COALESCE(standaard_sturing_nieuwe_floriday, 'lokaal') INTO v_sturing FROM public.voorraad_instellingen WHERE id;
11922
+ v_sturing := COALESCE(v_sturing, 'lokaal');
11923
+ PERFORM set_config('voorraad.floriday_sync', 'on', true);
11924
+ INSERT INTO public.products(product, batch, location, barcode, article_number, image_url, floriday_trade_item_id, trade_item_key, custom_fields)
11925
+ VALUES (v_name, '', '', v_gtin, v_code, v_photo, c.floriday_id,
11926
+ COALESCE((SELECT s.trade_item_key FROM public.trade_item_stock s WHERE lower(s.trade_item_key) = lower(c.floriday_id) AND s.archived_at IS NULL ORDER BY s.created_at LIMIT 1), c.floriday_id),
11927
+ jsonb_build_object('voorraad_auto_aangemaakt', true, 'voorraad_floriday_versie', c.data->>'tradeItemVersion',
11928
+ 'voorraad_auto_aangemaakt_at', now(), 'voorraad_uit_twijfel', true))
11929
+ RETURNING id INTO v_new;
11930
+ IF v_sturing = 'lokaal' THEN
11931
+ UPDATE public.floriday_inventory_settings SET erp_leading = NULL, stock_mode = 'DISABLED', updated_at = now()
11932
+ WHERE product_id = v_new;
11933
+ END IF;
11934
+ INSERT INTO public.voorraad_koppel_log(run_id, soort, floriday_id, product_id, oud, nieuw)
11935
+ VALUES (gen_random_uuid(), 'twijfel_nieuw_artikel', c.floriday_id, v_new, jsonb_build_object('reden', t.reden),
11936
+ jsonb_build_object('naam', v_name, 'gtin', v_gtin, 'artikelcode', v_code, 'sturing', v_sturing, 'door', auth.uid()));
11937
+ UPDATE public.voorraad_koppel_twijfel SET opgelost_at = now() WHERE floriday_id = p_floriday_id;
11938
+ RETURN jsonb_build_object('ok', true, 'product_id', v_new);
11939
+ END $function$
11940
+ ;
11941
+ REVOKE ALL ON FUNCTION voorraad_twijfel_nieuw_artikel(text) FROM PUBLIC;
11942
+ REVOKE ALL ON FUNCTION voorraad_twijfel_nieuw_artikel(text) FROM anon;
11943
+ GRANT EXECUTE ON FUNCTION voorraad_twijfel_nieuw_artikel(text) TO authenticated;
11944
+ GRANT EXECUTE ON FUNCTION voorraad_twijfel_nieuw_artikel(text) TO service_role;
10767
11945
  CREATE OR REPLACE FUNCTION public.voorraad_vereis(p_niveau text, p_functie text)
10768
11946
  RETURNS void
10769
11947
  LANGUAGE plpgsql
@@ -10790,6 +11968,113 @@ GRANT EXECUTE ON FUNCTION voorraad_vereis(text,text) TO service_role;
10790
11968
  GRANT EXECUTE ON FUNCTION voorraad_vereis(text,text) TO anon;
10791
11969
  GRANT EXECUTE ON FUNCTION voorraad_vereis(text,text) TO authenticated;
10792
11970
  GRANT EXECUTE ON FUNCTION voorraad_vereis(text,text) TO service_role;
11971
+ CREATE OR REPLACE FUNCTION public.voorraad_voorraad_corrigeren(p_product_id uuid, p_delta integer, p_reden text)
11972
+ RETURNS jsonb
11973
+ LANGUAGE plpgsql
11974
+ SECURITY DEFINER
11975
+ SET search_path TO 'public'
11976
+ AS $function$
11977
+ DECLARE v_ctx record; v_fl text; v_tk text; v_voor integer; v_na integer; r record;
11978
+ BEGIN
11979
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_voorraad_corrigeren');
11980
+ IF NOT public.voorraad_can_write() THEN
11981
+ RAISE EXCEPTION 'Je hebt geen schrijfrechten op de voorraad.' USING ERRCODE = '42501'; END IF;
11982
+ IF p_delta IS NULL OR p_delta = 0 THEN
11983
+ RAISE EXCEPTION 'Vul een aantal in dat niet 0 is.' USING ERRCODE = '22023'; END IF;
11984
+ IF COALESCE(btrim(p_reden),'') = '' THEN
11985
+ RAISE EXCEPTION 'Vul een reden in.' USING ERRCODE = '22023'; END IF;
11986
+ SELECT NULLIF(btrim(COALESCE(p.floriday_trade_item_id,'')),''), NULLIF(btrim(COALESCE(p.trade_item_key,'')),'')
11987
+ INTO v_fl, v_tk FROM public.products p WHERE p.id = p_product_id;
11988
+ IF NOT FOUND THEN RAISE EXCEPTION 'Artikel niet gevonden.' USING ERRCODE = 'P0002'; END IF;
11989
+ SELECT * INTO v_ctx FROM public.get_stock_context(true);
11990
+ IF v_ctx.connection_id IS NULL THEN RAISE EXCEPTION 'Geen voorraadcontext beschikbaar' USING ERRCODE = 'VR004'; END IF;
11991
+ -- Eén correctie tegelijk per artikel; bestaande rij vastzetten zodat een tussentijdse orderafboeking meetelt.
11992
+ PERFORM pg_advisory_xact_lock(hashtext('voorraad_corrigeren:' || p_product_id::text));
11993
+ SELECT s.physical_stock INTO v_voor FROM public.trade_item_stock s
11994
+ WHERE s.connection_id = v_ctx.connection_id
11995
+ AND s.trade_item_key IN (COALESCE(v_tk, v_fl, 'product:' || p_product_id::text), v_fl, v_tk, 'product:' || p_product_id::text, p_product_id::text)
11996
+ ORDER BY CASE WHEN s.trade_item_key = COALESCE(v_tk, v_fl, 'product:' || p_product_id::text) THEN 0 ELSE 1 END, s.updated_at DESC NULLS LAST
11997
+ LIMIT 1 FOR UPDATE;
11998
+ v_voor := COALESCE(v_voor, 0);
11999
+ v_na := v_voor + p_delta;
12000
+ IF v_na < 0 THEN
12001
+ RAISE EXCEPTION 'Je kunt niet meer weghalen (%) dan er ligt (%).', -p_delta, v_voor USING ERRCODE = '22023'; END IF;
12002
+ PERFORM set_config('stock.change_note', left('Aangepast ' || CASE WHEN p_delta > 0 THEN '+' ELSE '' END || p_delta || ': ' || btrim(p_reden), 500), true);
12003
+ PERFORM set_config('stock.keep_notes', '1', true);
12004
+ SELECT * INTO r FROM public.set_product_stock(p_product_id => p_product_id, p_physical_stock => v_na,
12005
+ p_minimum_stock => NULL, p_incoming_stock => NULL, p_notes => NULL, p_change_source => 'MANUAL_STOCK');
12006
+ RETURN jsonb_build_object('opgeslagen', true, 'physical_before', v_voor, 'physical_after', r.physical_stock,
12007
+ 'trade_item_key', r.trade_item_key, 'reserved_stock', r.reserved_stock);
12008
+ END $function$
12009
+ ;
12010
+ REVOKE ALL ON FUNCTION voorraad_voorraad_corrigeren(uuid,integer,text) FROM PUBLIC;
12011
+ REVOKE ALL ON FUNCTION voorraad_voorraad_corrigeren(uuid,integer,text) FROM anon;
12012
+ GRANT EXECUTE ON FUNCTION voorraad_voorraad_corrigeren(uuid,integer,text) TO authenticated;
12013
+ GRANT EXECUTE ON FUNCTION voorraad_voorraad_corrigeren(uuid,integer,text) TO service_role;
12014
+ CREATE OR REPLACE FUNCTION public.voorraad_voorraad_instellingen_opslaan(p_product_id uuid, p_oud jsonb, p_nieuw jsonb)
12015
+ RETURNS jsonb
12016
+ LANGUAGE plpgsql
12017
+ SECURITY DEFINER
12018
+ SET search_path TO 'public'
12019
+ AS $function$
12020
+ DECLARE
12021
+ v_key text; s public.trade_item_stock%ROWTYPE; v_cur jsonb; k text;
12022
+ v_conf jsonb := '[]'::jsonb; v_set jsonb := '{}'::jsonb; v_ctx uuid;
12023
+ BEGIN
12024
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_voorraad_instellingen_opslaan');
12025
+ IF NOT public.voorraad_can_write() THEN
12026
+ RAISE EXCEPTION 'Je hebt geen schrijfrechten op de voorraad.' USING ERRCODE = '42501'; END IF;
12027
+ IF jsonb_typeof(p_nieuw) IS DISTINCT FROM 'object' OR jsonb_typeof(p_oud) IS DISTINCT FROM 'object' THEN
12028
+ RAISE EXCEPTION 'Ongeldige invoer voor opslaan.' USING ERRCODE = '22023'; END IF;
12029
+ PERFORM 1 FROM public.products WHERE id = p_product_id FOR UPDATE;
12030
+ IF NOT FOUND THEN RAISE EXCEPTION 'Artikel niet gevonden.' USING ERRCODE = 'P0002'; END IF;
12031
+
12032
+ v_key := public.voorraad_artikel_detail(p_product_id)->>'voorraad_sleutel';
12033
+ IF v_key IS NULL THEN
12034
+ -- Nog geen voorraadregel: aanmaken met 0 liggend via de gewone opslagroute.
12035
+ PERFORM public.set_product_stock(p_product_id => p_product_id, p_physical_stock => 0, p_change_source => 'MANUAL_STOCK');
12036
+ v_key := public.voorraad_artikel_detail(p_product_id)->>'voorraad_sleutel';
12037
+ END IF;
12038
+ BEGIN SELECT c.connection_id INTO v_ctx FROM public.get_stock_context() c LIMIT 1;
12039
+ EXCEPTION WHEN others THEN v_ctx := NULL; END;
12040
+ SELECT * INTO s FROM public.trade_item_stock x WHERE x.trade_item_key = v_key AND x.archived_at IS NULL
12041
+ ORDER BY (x.connection_id IS NOT DISTINCT FROM v_ctx) DESC, x.updated_at DESC LIMIT 1 FOR UPDATE;
12042
+ IF NOT FOUND THEN RAISE EXCEPTION 'Geen voorraadregel gevonden voor dit artikel.' USING ERRCODE = 'P0002'; END IF;
12043
+
12044
+ v_cur := jsonb_build_object('minimum_stock', s.minimum_stock, 'incoming_stock', s.incoming_stock, 'notes', s.notes);
12045
+ FOR k IN SELECT jsonb_object_keys(p_nieuw) LOOP
12046
+ IF k NOT IN ('minimum_stock','incoming_stock','notes') THEN
12047
+ RAISE EXCEPTION 'Veld "%" kan hier niet worden opgeslagen.', k USING ERRCODE = '22023'; END IF;
12048
+ IF NOT (p_oud ? k) THEN
12049
+ RAISE EXCEPTION 'Stand bij openen ontbreekt voor veld "%".', k USING ERRCODE = '22023'; END IF;
12050
+ CONTINUE WHEN (p_nieuw->k) = (p_oud->k);
12051
+ IF (v_cur->k) IS DISTINCT FROM (p_oud->k) AND (v_cur->k) IS DISTINCT FROM (p_nieuw->k) THEN
12052
+ v_conf := v_conf || jsonb_build_object('veld', k, 'bij_openen', p_oud->k, 'jouw', p_nieuw->k, 'hun', v_cur->k);
12053
+ ELSE
12054
+ v_set := v_set || jsonb_build_object(k, p_nieuw->k);
12055
+ END IF;
12056
+ END LOOP;
12057
+ IF jsonb_array_length(v_conf) > 0 THEN RETURN jsonb_build_object('ok', false, 'conflicten', v_conf); END IF;
12058
+ IF (v_set ? 'minimum_stock' AND COALESCE((v_set->>'minimum_stock')::numeric, 0) < 0)
12059
+ OR (v_set ? 'incoming_stock' AND COALESCE((v_set->>'incoming_stock')::numeric, 0) < 0) THEN
12060
+ RAISE EXCEPTION 'Voorraadaantallen mogen niet negatief zijn' USING ERRCODE = '22023'; END IF;
12061
+
12062
+ IF v_set <> '{}'::jsonb THEN
12063
+ PERFORM set_config('stock.change_source', 'MANUAL_STOCK', true);
12064
+ UPDATE public.trade_item_stock SET
12065
+ minimum_stock = CASE WHEN v_set ? 'minimum_stock' THEN COALESCE((v_set->>'minimum_stock')::numeric, 0) ELSE minimum_stock END,
12066
+ incoming_stock = CASE WHEN v_set ? 'incoming_stock' THEN COALESCE((v_set->>'incoming_stock')::numeric, 0) ELSE incoming_stock END,
12067
+ notes = CASE WHEN v_set ? 'notes' THEN NULLIF(btrim(COALESCE(v_set->>'notes','')), '') ELSE notes END,
12068
+ change_source = 'MANUAL_STOCK', updated_at = now()
12069
+ WHERE id = s.id;
12070
+ END IF;
12071
+ RETURN jsonb_build_object('ok', true, 'opgeslagen', v_set);
12072
+ END $function$
12073
+ ;
12074
+ REVOKE ALL ON FUNCTION voorraad_voorraad_instellingen_opslaan(uuid,jsonb,jsonb) FROM PUBLIC;
12075
+ REVOKE ALL ON FUNCTION voorraad_voorraad_instellingen_opslaan(uuid,jsonb,jsonb) FROM anon;
12076
+ GRANT EXECUTE ON FUNCTION voorraad_voorraad_instellingen_opslaan(uuid,jsonb,jsonb) TO authenticated;
12077
+ GRANT EXECUTE ON FUNCTION voorraad_voorraad_instellingen_opslaan(uuid,jsonb,jsonb) TO service_role;
10793
12078
  CREATE OR REPLACE FUNCTION public.voorraad_voorraad_opslaan(p_product_id uuid, p_connection_id uuid, p_trade_item_key text, p_physical_stock numeric, p_minimum_stock numeric, p_incoming_stock numeric, p_wijziging_notitie text DEFAULT NULL::text, p_artikel_notitie text DEFAULT NULL::text, p_artikel_notitie_wijzigen boolean DEFAULT false)
10794
12079
  RETURNS jsonb
10795
12080
  LANGUAGE plpgsql
@@ -10947,7 +12232,13 @@ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraa
10947
12232
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_correcties_pkey' AND conrelid = 'public.voorraad_correcties'::regclass) THEN ALTER TABLE public.voorraad_correcties ADD CONSTRAINT voorraad_correcties_pkey PRIMARY KEY (id); END IF; END $c$;
10948
12233
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_custom_fields_pkey' AND conrelid = 'public.voorraad_custom_fields'::regclass) THEN ALTER TABLE public.voorraad_custom_fields ADD CONSTRAINT voorraad_custom_fields_pkey PRIMARY KEY (id); END IF; END $c$;
10949
12234
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_defaults_state_pkey' AND conrelid = 'public.voorraad_defaults_state'::regclass) THEN ALTER TABLE public.voorraad_defaults_state ADD CONSTRAINT voorraad_defaults_state_pkey PRIMARY KEY (id); END IF; END $c$;
12235
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_floriday_alias_pkey' AND conrelid = 'public.voorraad_floriday_alias'::regclass) THEN ALTER TABLE public.voorraad_floriday_alias ADD CONSTRAINT voorraad_floriday_alias_pkey PRIMARY KEY (floriday_id); END IF; END $c$;
10950
12236
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_floriday_artikel_wachtrij_pkey' AND conrelid = 'public.voorraad_floriday_artikel_wachtrij'::regclass) THEN ALTER TABLE public.voorraad_floriday_artikel_wachtrij ADD CONSTRAINT voorraad_floriday_artikel_wachtrij_pkey PRIMARY KEY (floriday_id); END IF; END $c$;
12237
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_floriday_versie_fouten_pkey' AND conrelid = 'public.voorraad_floriday_versie_fouten'::regclass) THEN ALTER TABLE public.voorraad_floriday_versie_fouten ADD CONSTRAINT voorraad_floriday_versie_fouten_pkey PRIMARY KEY (connection_id, floriday_id); END IF; END $c$;
12238
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_floriday_versie_rondes_pkey' AND conrelid = 'public.voorraad_floriday_versie_rondes'::regclass) THEN ALTER TABLE public.voorraad_floriday_versie_rondes ADD CONSTRAINT voorraad_floriday_versie_rondes_pkey PRIMARY KEY (id); END IF; END $c$;
12239
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_floriday_versieronde_stand_pkey' AND conrelid = 'public.voorraad_floriday_versieronde_stand'::regclass) THEN ALTER TABLE public.voorraad_floriday_versieronde_stand ADD CONSTRAINT voorraad_floriday_versieronde_stand_pkey PRIMARY KEY (connection_id); END IF; END $c$;
12240
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_floriday_versieronde_uitkomst_pkey' AND conrelid = 'public.voorraad_floriday_versieronde_uitkomst'::regclass) THEN ALTER TABLE public.voorraad_floriday_versieronde_uitkomst ADD CONSTRAINT voorraad_floriday_versieronde_uitkomst_pkey PRIMARY KEY (ronde_id, floriday_id); END IF; END $c$;
12241
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_floriday_vervallen_pkey' AND conrelid = 'public.voorraad_floriday_vervallen'::regclass) THEN ALTER TABLE public.voorraad_floriday_vervallen ADD CONSTRAINT voorraad_floriday_vervallen_pkey PRIMARY KEY (floriday_id); END IF; END $c$;
10951
12242
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_instellingen_pkey' AND conrelid = 'public.voorraad_instellingen'::regclass) THEN ALTER TABLE public.voorraad_instellingen ADD CONSTRAINT voorraad_instellingen_pkey PRIMARY KEY (id); END IF; END $c$;
10952
12243
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_koppel_log_pkey' AND conrelid = 'public.voorraad_koppel_log'::regclass) THEN ALTER TABLE public.voorraad_koppel_log ADD CONSTRAINT voorraad_koppel_log_pkey PRIMARY KEY (id); END IF; END $c$;
10953
12244
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_koppel_twijfel_pkey' AND conrelid = 'public.voorraad_koppel_twijfel'::regclass) THEN ALTER TABLE public.voorraad_koppel_twijfel ADD CONSTRAINT voorraad_koppel_twijfel_pkey PRIMARY KEY (floriday_id); END IF; END $c$;
@@ -11070,6 +12361,7 @@ CREATE INDEX IF NOT EXISTS idx_voorraad_buffer_state_closed_at ON public.voorraa
11070
12361
  CREATE INDEX IF NOT EXISTS idx_voorraad_event_log_processed_at ON public.voorraad_logistics_event_log USING btree (processed_at DESC);
11071
12362
  CREATE INDEX IF NOT EXISTS idx_voorraad_publish_attempts_user_created ON public.voorraad_publish_attempts USING btree (user_id, created_at DESC);
11072
12363
  CREATE INDEX IF NOT EXISTS inventory_stock_snapshots_time_idx ON public.inventory_stock_snapshots USING btree (connection_id, snapshot_at DESC);
12364
+ CREATE INDEX IF NOT EXISTS product_packings_primary_idx ON public.product_packings USING btree (product_id) WHERE is_primary;
11073
12365
  CREATE INDEX IF NOT EXISTS product_packings_product_idx ON public.product_packings USING btree (product_id);
11074
12366
  CREATE INDEX IF NOT EXISTS stock_import_rows_chunk_idx ON public.stock_import_rows USING btree (run_id, chunk_no);
11075
12367
  CREATE INDEX IF NOT EXISTS stock_import_runs_hash_idx ON public.stock_import_runs USING btree (file_hash) WHERE (file_hash IS NOT NULL);
@@ -11077,6 +12369,7 @@ CREATE INDEX IF NOT EXISTS stock_import_runs_user_created_idx ON public.stock_im
11077
12369
  CREATE INDEX IF NOT EXISTS stock_integrity_alerts_open_idx ON public.stock_integrity_alerts USING btree (created_at DESC) WHERE (acknowledged_at IS NULL);
11078
12370
  CREATE INDEX IF NOT EXISTS trade_item_prices_lookup ON public.trade_item_prices USING btree (trade_item_key, price_group_id);
11079
12371
  CREATE INDEX IF NOT EXISTS trade_item_stock_trade_item_key_idx ON public.trade_item_stock USING btree (trade_item_key);
12372
+ CREATE INDEX IF NOT EXISTS voorraad_floriday_alias_lower_idx ON public.voorraad_floriday_alias USING btree (lower(floriday_id));
11080
12373
  CREATE INDEX IF NOT EXISTS voorraad_koppel_log_item_idx ON public.voorraad_koppel_log USING btree (order_item_id);
11081
12374
  CREATE INDEX IF NOT EXISTS voorraad_koppel_log_run_idx ON public.voorraad_koppel_log USING btree (run_id);
11082
12375
  CREATE UNIQUE INDEX IF NOT EXISTS fcsq_one_open_per_week ON public.floriday_continuous_stock_queue USING btree (connection_id, trade_item_id, COALESCE(target_year, '-1'::integer), COALESCE(target_week, '-1'::integer)) WHERE (status = ANY (ARRAY['pending'::text, 'needs_base_supply'::text, 'skipped_no_price'::text, 'failed'::text]));
@@ -11233,10 +12526,34 @@ ALTER TABLE public.voorraad_defaults_state ENABLE ROW LEVEL SECURITY;
11233
12526
  REVOKE ALL ON public.voorraad_defaults_state FROM anon;
11234
12527
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_defaults_state TO authenticated;
11235
12528
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_defaults_state TO service_role;
12529
+ ALTER TABLE public.voorraad_floriday_alias ENABLE ROW LEVEL SECURITY;
12530
+ REVOKE ALL ON public.voorraad_floriday_alias FROM anon;
12531
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_alias TO authenticated;
12532
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_alias TO service_role;
11236
12533
  ALTER TABLE public.voorraad_floriday_artikel_wachtrij ENABLE ROW LEVEL SECURITY;
11237
12534
  REVOKE ALL ON public.voorraad_floriday_artikel_wachtrij FROM anon;
11238
12535
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_artikel_wachtrij TO authenticated;
11239
12536
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_artikel_wachtrij TO service_role;
12537
+ ALTER TABLE public.voorraad_floriday_versie_fouten ENABLE ROW LEVEL SECURITY;
12538
+ REVOKE ALL ON public.voorraad_floriday_versie_fouten FROM anon;
12539
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_versie_fouten TO authenticated;
12540
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_versie_fouten TO service_role;
12541
+ ALTER TABLE public.voorraad_floriday_versie_rondes ENABLE ROW LEVEL SECURITY;
12542
+ REVOKE ALL ON public.voorraad_floriday_versie_rondes FROM anon;
12543
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_versie_rondes TO authenticated;
12544
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_versie_rondes TO service_role;
12545
+ ALTER TABLE public.voorraad_floriday_versieronde_stand ENABLE ROW LEVEL SECURITY;
12546
+ REVOKE ALL ON public.voorraad_floriday_versieronde_stand FROM anon;
12547
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_versieronde_stand TO authenticated;
12548
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_versieronde_stand TO service_role;
12549
+ ALTER TABLE public.voorraad_floriday_versieronde_uitkomst ENABLE ROW LEVEL SECURITY;
12550
+ REVOKE ALL ON public.voorraad_floriday_versieronde_uitkomst FROM anon;
12551
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_versieronde_uitkomst TO authenticated;
12552
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_versieronde_uitkomst TO service_role;
12553
+ ALTER TABLE public.voorraad_floriday_vervallen ENABLE ROW LEVEL SECURITY;
12554
+ REVOKE ALL ON public.voorraad_floriday_vervallen FROM anon;
12555
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_vervallen TO authenticated;
12556
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_vervallen TO service_role;
11240
12557
  ALTER TABLE public.voorraad_instellingen ENABLE ROW LEVEL SECURITY;
11241
12558
  REVOKE ALL ON public.voorraad_instellingen FROM anon;
11242
12559
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_instellingen TO authenticated;
@@ -11414,8 +12731,18 @@ DROP POLICY IF EXISTS "Authenticated can update voorraad custom fields" ON publi
11414
12731
  CREATE POLICY "Authenticated can update voorraad custom fields" ON public.voorraad_custom_fields AS PERMISSIVE FOR UPDATE TO authenticated USING (true) WITH CHECK (true);
11415
12732
  DROP POLICY IF EXISTS "Ingelogd mag standaardstatus lezen" ON public.voorraad_defaults_state;
11416
12733
  CREATE POLICY "Ingelogd mag standaardstatus lezen" ON public.voorraad_defaults_state AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12734
+ DROP POLICY IF EXISTS "Ingelogd lezen alias" ON public.voorraad_floriday_alias;
12735
+ CREATE POLICY "Ingelogd lezen alias" ON public.voorraad_floriday_alias AS PERMISSIVE FOR SELECT TO authenticated USING (true);
11417
12736
  DROP POLICY IF EXISTS voorraad_floriday_artikel_wachtrij_lezen ON public.voorraad_floriday_artikel_wachtrij;
11418
12737
  CREATE POLICY voorraad_floriday_artikel_wachtrij_lezen ON public.voorraad_floriday_artikel_wachtrij AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12738
+ DROP POLICY IF EXISTS "Ingelogd lezen versie fouten" ON public.voorraad_floriday_versie_fouten;
12739
+ CREATE POLICY "Ingelogd lezen versie fouten" ON public.voorraad_floriday_versie_fouten AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12740
+ DROP POLICY IF EXISTS "Ingelogd lezen versierondes" ON public.voorraad_floriday_versie_rondes;
12741
+ CREATE POLICY "Ingelogd lezen versierondes" ON public.voorraad_floriday_versie_rondes AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12742
+ DROP POLICY IF EXISTS "Ingelogd lezen versieronde stand" ON public.voorraad_floriday_versieronde_stand;
12743
+ CREATE POLICY "Ingelogd lezen versieronde stand" ON public.voorraad_floriday_versieronde_stand AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12744
+ DROP POLICY IF EXISTS "Ingelogd lezen vervallen" ON public.voorraad_floriday_vervallen;
12745
+ CREATE POLICY "Ingelogd lezen vervallen" ON public.voorraad_floriday_vervallen AS PERMISSIVE FOR SELECT TO authenticated USING (true);
11419
12746
  DROP POLICY IF EXISTS voorraad_instellingen_lezen ON public.voorraad_instellingen;
11420
12747
  CREATE POLICY voorraad_instellingen_lezen ON public.voorraad_instellingen AS PERMISSIVE FOR SELECT TO authenticated USING (true);
11421
12748
  DROP POLICY IF EXISTS voorraad_koppel_log_lezen ON public.voorraad_koppel_log;
@@ -11580,6 +12907,8 @@ DROP TRIGGER IF EXISTS trg_voorraad_buffer_state_updated_at ON public.voorraad_b
11580
12907
  CREATE TRIGGER trg_voorraad_buffer_state_updated_at BEFORE UPDATE ON public.voorraad_buffer_state FOR EACH ROW EXECUTE FUNCTION voorraad_touch_updated_at();
11581
12908
  DROP TRIGGER IF EXISTS update_voorraad_custom_fields_updated_at ON public.voorraad_custom_fields;
11582
12909
  CREATE TRIGGER update_voorraad_custom_fields_updated_at BEFORE UPDATE ON public.voorraad_custom_fields FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
12910
+ DROP TRIGGER IF EXISTS voorraad_floriday_vervallen_touch ON public.voorraad_floriday_vervallen;
12911
+ CREATE TRIGGER voorraad_floriday_vervallen_touch BEFORE UPDATE ON public.voorraad_floriday_vervallen FOR EACH ROW EXECUTE FUNCTION voorraad_touch_updated_at();
11583
12912
  DROP TRIGGER IF EXISTS trg_voorraad_event_log_updated_at ON public.voorraad_logistics_event_log;
11584
12913
  CREATE TRIGGER trg_voorraad_event_log_updated_at BEFORE UPDATE ON public.voorraad_logistics_event_log FOR EACH ROW EXECUTE FUNCTION voorraad_touch_updated_at();
11585
12914
  DROP TRIGGER IF EXISTS update_voorraad_publish_jobs_updated_at ON public.voorraad_publish_jobs;
@@ -11603,3 +12932,7 @@ BEGIN
11603
12932
  RAISE NOTICE 'Voorraad: pg_cron ontbreekt, vaste taken overgeslagen';
11604
12933
  END IF;
11605
12934
  END $cron$;
12935
+
12936
+ -- realtime-publicatie voor live bijwerken van de artikelpagina
12937
+ DO $rt$ BEGIN IF to_regclass('public.products') IS NOT NULL AND EXISTS (SELECT 1 FROM pg_publication WHERE pubname='supabase_realtime') AND NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname='supabase_realtime' AND schemaname='public' AND tablename='products') THEN ALTER PUBLICATION supabase_realtime ADD TABLE public.products; END IF; END $rt$;
12938
+ DO $rt$ BEGIN IF to_regclass('public.trade_item_stock') IS NOT NULL AND EXISTS (SELECT 1 FROM pg_publication WHERE pubname='supabase_realtime') AND NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname='supabase_realtime' AND schemaname='public' AND tablename='trade_item_stock') THEN ALTER PUBLICATION supabase_realtime ADD TABLE public.trade_item_stock; END IF; END $rt$;