@flowselections/floriday-voorraad 1.0.164 → 1.0.166

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 (31) hide show
  1. package/dist-lib/components/fs-ui/DataTable.d.ts.map +1 -1
  2. package/dist-lib/components/fs-ui/DataTable.js +2 -2
  3. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  4. package/dist-lib/components/voorraad/VoorraadPage.js +86 -6
  5. package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts.map +1 -1
  6. package/dist-lib/components/voorraad/artikel/ArtikelPagina.js +10 -2
  7. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts +3 -1
  8. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts.map +1 -1
  9. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.js +5 -2
  10. package/dist-lib/hooks/useVoorraadLijst.d.ts +4 -5
  11. package/dist-lib/hooks/useVoorraadLijst.d.ts.map +1 -1
  12. package/dist-lib/hooks/useVoorraadLijst.js +23 -5
  13. package/dist-lib/hooks-entry.d.ts +1 -0
  14. package/dist-lib/hooks-entry.d.ts.map +1 -1
  15. package/dist-lib/hooks-entry.js +1 -0
  16. package/dist-lib/integrations/supabase/types.d.ts +537 -0
  17. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  18. package/dist-lib/lib/lijst-stand.d.ts +31 -0
  19. package/dist-lib/lib/lijst-stand.d.ts.map +1 -0
  20. package/dist-lib/lib/lijst-stand.js +77 -0
  21. package/dist-lib/lib/voorraad-push-melding.d.ts +10 -0
  22. package/dist-lib/lib/voorraad-push-melding.d.ts.map +1 -0
  23. package/dist-lib/lib/voorraad-push-melding.js +11 -0
  24. package/dist-lib/server-hooks/floriday-mutaties.d.ts +3 -0
  25. package/dist-lib/server-hooks/floriday-mutaties.d.ts.map +1 -0
  26. package/dist-lib/server-hooks/floriday-mutaties.js +70 -0
  27. package/dist-lib/server-hooks/guard.d.ts.map +1 -1
  28. package/dist-lib/server-hooks/guard.js +1 -1
  29. package/package.json +22 -1
  30. package/sql/AGENTS.md +2 -0
  31. package/sql/floriday-voorraad-schema.sql +1454 -902
@@ -1,5 +1,5 @@
1
- -- @flowselections/floriday-voorraad 1.0.162 – volledig schema (idempotent, geen gegevens).
2
- -- Laatste migratie: 20261106160000_voorraad_crons_naar_dashboard.sql
1
+ -- @flowselections/floriday-voorraad 1.0.164 – volledig schema (idempotent, geen gegevens).
2
+ -- Laatste migratie: 20261107133040_e0b29cec-5ee3-487e-99bd-5f3b66b8da4f.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.
@@ -61,6 +61,7 @@ ALTER TABLE public.floriday_continuous_stock_queue ADD COLUMN IF NOT EXISTS base
61
61
  ALTER TABLE public.floriday_continuous_stock_queue ADD COLUMN IF NOT EXISTS price_status text;
62
62
  ALTER TABLE public.floriday_continuous_stock_queue ADD COLUMN IF NOT EXISTS price_note text;
63
63
  ALTER TABLE public.floriday_continuous_stock_queue ADD COLUMN IF NOT EXISTS price_previous numeric;
64
+ ALTER TABLE public.floriday_continuous_stock_queue ADD COLUMN IF NOT EXISTS voorraad_mutatie boolean DEFAULT false NOT NULL;
64
65
 
65
66
  -- floriday_inventory_settings
66
67
  CREATE TABLE IF NOT EXISTS public.floriday_inventory_settings ();
@@ -348,6 +349,39 @@ ALTER TABLE public.product_tray_stickers ADD COLUMN IF NOT EXISTS bestandsgroott
348
349
  ALTER TABLE public.product_tray_stickers ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
349
350
  ALTER TABLE public.product_tray_stickers ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT now() NOT NULL;
350
351
 
352
+ -- productie_order_onderdelen
353
+ CREATE TABLE IF NOT EXISTS public.productie_order_onderdelen ();
354
+ ALTER TABLE public.productie_order_onderdelen ADD COLUMN IF NOT EXISTS id uuid DEFAULT gen_random_uuid() NOT NULL;
355
+ ALTER TABLE public.productie_order_onderdelen ADD COLUMN IF NOT EXISTS productie_order_id uuid NOT NULL;
356
+ ALTER TABLE public.productie_order_onderdelen ADD COLUMN IF NOT EXISTS product_id uuid;
357
+ ALTER TABLE public.productie_order_onderdelen ADD COLUMN IF NOT EXISTS trade_item_key text NOT NULL;
358
+ ALTER TABLE public.productie_order_onderdelen ADD COLUMN IF NOT EXISTS naam text;
359
+ ALTER TABLE public.productie_order_onderdelen ADD COLUMN IF NOT EXISTS aantal numeric NOT NULL;
360
+ ALTER TABLE public.productie_order_onderdelen ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
361
+
362
+ -- productie_orders
363
+ CREATE TABLE IF NOT EXISTS public.productie_orders ();
364
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS id uuid DEFAULT gen_random_uuid() NOT NULL;
365
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS nummer text NOT NULL;
366
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS product_id uuid NOT NULL;
367
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS trade_item_key text NOT NULL;
368
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS aantal numeric NOT NULL;
369
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS status text DEFAULT 'open'::text NOT NULL;
370
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS opmerking text;
371
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS aangemaakt_door uuid;
372
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS aangemaakt_op timestamp with time zone DEFAULT now() NOT NULL;
373
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS verwerkt_door uuid;
374
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS verwerkt_op timestamp with time zone;
375
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS geannuleerd_door uuid;
376
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS geannuleerd_op timestamp with time zone;
377
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS teruggedraaid_door uuid;
378
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS teruggedraaid_op timestamp with time zone;
379
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS reden text;
380
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS geprint_op timestamp with time zone;
381
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS waarschuwingen jsonb DEFAULT '[]'::jsonb NOT NULL;
382
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
383
+ ALTER TABLE public.productie_orders ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT now() NOT NULL;
384
+
351
385
  -- stock_import_chunks
352
386
  CREATE TABLE IF NOT EXISTS public.stock_import_chunks ();
353
387
  ALTER TABLE public.stock_import_chunks ADD COLUMN IF NOT EXISTS run_id uuid NOT NULL;
@@ -592,6 +626,37 @@ ALTER TABLE public.voorraad_floriday_artikel_wachtrij ADD COLUMN IF NOT EXISTS f
592
626
  ALTER TABLE public.voorraad_floriday_artikel_wachtrij ADD COLUMN IF NOT EXISTS connection_id uuid;
593
627
  ALTER TABLE public.voorraad_floriday_artikel_wachtrij ADD COLUMN IF NOT EXISTS aangemeld_at timestamp with time zone DEFAULT now() NOT NULL;
594
628
 
629
+ -- voorraad_floriday_mutaties
630
+ CREATE TABLE IF NOT EXISTS public.voorraad_floriday_mutaties ();
631
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS id uuid DEFAULT gen_random_uuid() NOT NULL;
632
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS connection_id uuid;
633
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS product_id uuid NOT NULL;
634
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS trade_item_id uuid;
635
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS trade_item_key text;
636
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS order_id uuid;
637
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS order_item_id uuid;
638
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS referentie text;
639
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS delta integer NOT NULL;
640
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS bron text NOT NULL;
641
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS status text DEFAULT 'open'::text NOT NULL;
642
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS pogingen integer DEFAULT 0 NOT NULL;
643
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS volgende_poging_at timestamp with time zone DEFAULT now() NOT NULL;
644
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS laatste_fout text;
645
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS queue_id uuid;
646
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS verzonden_at timestamp with time zone;
647
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS bevestigd_at timestamp with time zone;
648
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
649
+ ALTER TABLE public.voorraad_floriday_mutaties ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT now() NOT NULL;
650
+
651
+ -- voorraad_floriday_stand
652
+ CREATE TABLE IF NOT EXISTS public.voorraad_floriday_stand ();
653
+ ALTER TABLE public.voorraad_floriday_stand ADD COLUMN IF NOT EXISTS connection_id uuid NOT NULL;
654
+ ALTER TABLE public.voorraad_floriday_stand ADD COLUMN IF NOT EXISTS trade_item_id uuid NOT NULL;
655
+ ALTER TABLE public.voorraad_floriday_stand ADD COLUMN IF NOT EXISTS number_of_pieces integer DEFAULT 0 NOT NULL;
656
+ ALTER TABLE public.voorraad_floriday_stand ADD COLUMN IF NOT EXISTS jaar integer;
657
+ ALTER TABLE public.voorraad_floriday_stand ADD COLUMN IF NOT EXISTS week integer;
658
+ ALTER TABLE public.voorraad_floriday_stand ADD COLUMN IF NOT EXISTS opgehaald_at timestamp with time zone DEFAULT now() NOT NULL;
659
+
595
660
  -- voorraad_floriday_versie_fouten
596
661
  CREATE TABLE IF NOT EXISTS public.voorraad_floriday_versie_fouten ();
597
662
  ALTER TABLE public.voorraad_floriday_versie_fouten ADD COLUMN IF NOT EXISTS connection_id uuid NOT NULL;
@@ -661,6 +726,13 @@ ALTER TABLE public.voorraad_instellingen ADD COLUMN IF NOT EXISTS standaard_stur
661
726
  ALTER TABLE public.voorraad_instellingen ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT now() NOT NULL;
662
727
  ALTER TABLE public.voorraad_instellingen ADD COLUMN IF NOT EXISTS updated_by uuid;
663
728
 
729
+ -- voorraad_integriteit_runs
730
+ CREATE TABLE IF NOT EXISTS public.voorraad_integriteit_runs ();
731
+ ALTER TABLE public.voorraad_integriteit_runs ADD COLUMN IF NOT EXISTS id uuid DEFAULT gen_random_uuid() NOT NULL;
732
+ ALTER TABLE public.voorraad_integriteit_runs ADD COLUMN IF NOT EXISTS uitgevoerd_op timestamp with time zone DEFAULT now() NOT NULL;
733
+ ALTER TABLE public.voorraad_integriteit_runs ADD COLUMN IF NOT EXISTS samenvatting jsonb DEFAULT '{}'::jsonb NOT NULL;
734
+ ALTER TABLE public.voorraad_integriteit_runs ADD COLUMN IF NOT EXISTS uitkomst jsonb DEFAULT '{}'::jsonb NOT NULL;
735
+
664
736
  -- voorraad_koppel_log
665
737
  CREATE TABLE IF NOT EXISTS public.voorraad_koppel_log ();
666
738
  ALTER TABLE public.voorraad_koppel_log ADD COLUMN IF NOT EXISTS id uuid DEFAULT gen_random_uuid() NOT NULL;
@@ -734,6 +806,36 @@ ALTER TABLE public.voorraad_media_upload_failures ADD COLUMN IF NOT EXISTS respo
734
806
  ALTER TABLE public.voorraad_media_upload_failures ADD COLUMN IF NOT EXISTS error_message text;
735
807
  ALTER TABLE public.voorraad_media_upload_failures ADD COLUMN IF NOT EXISTS client_user_id uuid;
736
808
 
809
+ -- voorraad_order_terugdraaiing
810
+ CREATE TABLE IF NOT EXISTS public.voorraad_order_terugdraaiing ();
811
+ ALTER TABLE public.voorraad_order_terugdraaiing ADD COLUMN IF NOT EXISTS order_id uuid NOT NULL;
812
+ ALTER TABLE public.voorraad_order_terugdraaiing ADD COLUMN IF NOT EXISTS order_number text;
813
+ ALTER TABLE public.voorraad_order_terugdraaiing ADD COLUMN IF NOT EXISTS reden text;
814
+ ALTER TABLE public.voorraad_order_terugdraaiing ADD COLUMN IF NOT EXISTS sleutels text[] DEFAULT '{}'::text[] NOT NULL;
815
+ ALTER TABLE public.voorraad_order_terugdraaiing ADD COLUMN IF NOT EXISTS door uuid;
816
+ ALTER TABLE public.voorraad_order_terugdraaiing ADD COLUMN IF NOT EXISTS op timestamp with time zone DEFAULT now() NOT NULL;
817
+
818
+ -- voorraad_orderregel_afboeking
819
+ CREATE TABLE IF NOT EXISTS public.voorraad_orderregel_afboeking ();
820
+ ALTER TABLE public.voorraad_orderregel_afboeking ADD COLUMN IF NOT EXISTS order_item_id uuid NOT NULL;
821
+ ALTER TABLE public.voorraad_orderregel_afboeking ADD COLUMN IF NOT EXISTS trade_item_key text NOT NULL;
822
+ ALTER TABLE public.voorraad_orderregel_afboeking ADD COLUMN IF NOT EXISTS order_id uuid;
823
+ ALTER TABLE public.voorraad_orderregel_afboeking ADD COLUMN IF NOT EXISTS besteld numeric DEFAULT 0 NOT NULL;
824
+ ALTER TABLE public.voorraad_orderregel_afboeking ADD COLUMN IF NOT EXISTS afgeboekt numeric DEFAULT 0 NOT NULL;
825
+ ALTER TABLE public.voorraad_orderregel_afboeking ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
826
+ ALTER TABLE public.voorraad_orderregel_afboeking ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT now() NOT NULL;
827
+
828
+ -- voorraad_orderregel_verzending
829
+ CREATE TABLE IF NOT EXISTS public.voorraad_orderregel_verzending ();
830
+ ALTER TABLE public.voorraad_orderregel_verzending ADD COLUMN IF NOT EXISTS order_item_id uuid NOT NULL;
831
+ ALTER TABLE public.voorraad_orderregel_verzending ADD COLUMN IF NOT EXISTS order_id uuid;
832
+ ALTER TABLE public.voorraad_orderregel_verzending ADD COLUMN IF NOT EXISTS besteld numeric DEFAULT 0 NOT NULL;
833
+ ALTER TABLE public.voorraad_orderregel_verzending ADD COLUMN IF NOT EXISTS verzonden numeric DEFAULT 0 NOT NULL;
834
+ ALTER TABLE public.voorraad_orderregel_verzending ADD COLUMN IF NOT EXISTS meer_dan_besteld boolean DEFAULT false NOT NULL;
835
+ ALTER TABLE public.voorraad_orderregel_verzending ADD COLUMN IF NOT EXISTS afboekregel text;
836
+ ALTER TABLE public.voorraad_orderregel_verzending ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
837
+ ALTER TABLE public.voorraad_orderregel_verzending ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT now() NOT NULL;
838
+
737
839
  -- voorraad_placeholder_codes
738
840
  CREATE TABLE IF NOT EXISTS public.voorraad_placeholder_codes ();
739
841
  ALTER TABLE public.voorraad_placeholder_codes ADD COLUMN IF NOT EXISTS code text NOT NULL;
@@ -942,55 +1044,15 @@ CREATE OR REPLACE FUNCTION public.apply_stock_consumption(p_trade_item_key text,
942
1044
  SECURITY DEFINER
943
1045
  SET search_path TO 'public'
944
1046
  AS $function$
945
- DECLARE
946
- v_norm text := public.normalize_order_source(p_source);
947
- v_src text := CASE WHEN v_norm = 'FO' THEN 'FLORIDAY_ORDER' ELSE 'ORDER_DELIVERY' END;
948
- v_ctx record; v_key text; r public.trade_item_stock%ROWTYPE; v_after numeric; v_res_after numeric;
949
1047
  BEGIN
950
1048
  PERFORM public.voorraad_vereis('ingelogd', 'apply_stock_consumption');
951
- IF p_trade_item_key IS NULL OR COALESCE(p_qty,0) <= 0 THEN RETURN; END IF;
952
- SELECT * INTO v_ctx FROM public.get_stock_context(true);
953
- v_key := public.ensure_trade_item_stock_row_key(p_trade_item_key);
954
- IF v_ctx.connection_id IS NULL OR v_key IS NULL THEN
955
- INSERT INTO public.stock_mutation_log(trade_item_key, order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
956
- VALUES (p_trade_item_key, p_order_id, p_order_item_id, v_norm, 'consumption_skipped', p_qty,
957
- CASE WHEN v_ctx.connection_id IS NULL THEN 'geen actieve koppeling' ELSE 'geen voorraadrij' END || ' — ' || COALESCE(p_reason,''), auth.uid());
958
- RETURN;
959
- END IF;
960
- SELECT * INTO r FROM public.trade_item_stock WHERE connection_id = v_ctx.connection_id AND trade_item_key = v_key FOR UPDATE;
961
- IF r.id IS NULL THEN
962
- INSERT INTO public.stock_mutation_log(trade_item_key, order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
963
- VALUES (v_key, p_order_id, p_order_item_id, v_norm, 'consumption_skipped', p_qty, 'geen voorraadrij op de actieve koppeling — ' || COALESCE(p_reason,''), auth.uid());
1049
+ IF p_order_item_id IS NOT NULL THEN
1050
+ -- Orderregel: altijd de ene afboekroute (aantal erbij, nooit boven besteld). De meegegeven sleutel telt niet:
1051
+ -- de regel bepaalt zelf samenstelling of onderdelen, gelijk aan de karroute.
1052
+ PERFORM public.consume_order_item_v2(p_order_id, p_order_item_id, p_qty, p_source, p_reason, 'erbij');
964
1053
  RETURN;
965
1054
  END IF;
966
- v_after := COALESCE(r.physical_stock,0) - p_qty;
967
- v_res_after := GREATEST(COALESCE(r.reserved_stock,0) - p_qty, 0);
968
-
969
- PERFORM set_config('stock.change_source', v_src, true);
970
- PERFORM set_config('stock.log_handled',
971
- COALESCE(NULLIF(current_setting('stock.log_handled', true),''), ';') || r.connection_id || '|' || v_key || '|' || v_after || '|' || v_res_after || ';', true);
972
-
973
- UPDATE public.trade_item_stock
974
- SET physical_stock = v_after, reserved_stock = v_res_after, change_source = v_src, updated_at = now()
975
- WHERE id = r.id;
976
-
977
- INSERT INTO public.stock_mutation_log(trade_item_key, connection_id, order_id, order_item_id, order_source,
978
- mutation_type, qty_delta, physical_before, physical_after, reserved_before, reserved_after, reason, created_by)
979
- VALUES (v_key, r.connection_id, p_order_id, p_order_item_id, v_norm, 'consumption', -p_qty,
980
- COALESCE(r.physical_stock,0), v_after, COALESCE(r.reserved_stock,0), v_res_after,
981
- COALESCE(p_reason,'afgeboekt') || ' (' || v_src || ')', auth.uid());
982
-
983
- IF v_after < 0 THEN
984
- BEGIN
985
- INSERT INTO public.stock_integrity_alerts(tx_id, alert_kind, item_count, items)
986
- VALUES (-txid_current(), 'NEGATIVE_STOCK', 1, jsonb_build_array(jsonb_build_object(
987
- 'trade_item_key', v_key, 'connection_id', r.connection_id, 'physical_before', r.physical_stock,
988
- 'physical_after', v_after, 'order_id', p_order_id, 'order_item_id', p_order_item_id, 'change_source', v_src)))
989
- ON CONFLICT (tx_id) DO UPDATE SET item_count = public.stock_integrity_alerts.item_count + 1,
990
- items = public.stock_integrity_alerts.items || EXCLUDED.items, updated_at = now();
991
- EXCEPTION WHEN others THEN RAISE WARNING '[negative-stock-alert] %', SQLERRM;
992
- END;
993
- END IF;
1055
+ PERFORM public.voorraad_afboeken_kern(p_trade_item_key, p_qty, p_order_id, p_order_item_id, p_source, p_reason);
994
1056
  END $function$
995
1057
  ;
996
1058
  REVOKE ALL ON FUNCTION apply_stock_consumption(text,numeric,uuid,uuid,text,text) FROM PUBLIC;
@@ -1514,31 +1576,33 @@ CREATE OR REPLACE FUNCTION public.claim_floriday_stock_queue(p_limit integer DEF
1514
1576
  SECURITY DEFINER
1515
1577
  SET search_path TO 'public'
1516
1578
  AS $function$
1579
+ DECLARE v_vers boolean;
1517
1580
  BEGIN
1518
1581
  PERFORM public.voorraad_vereis('server', 'claim_floriday_stock_queue');
1582
+ v_vers := public.voorraad_floriday_orders_vers();
1583
+ UPDATE public.floriday_continuous_stock_queue q
1584
+ SET result_label = 'Wacht op een verse ophaalronde van Floriday (minder dan 2 minuten oud)', updated_at = now()
1585
+ WHERE q.status = 'pending' AND q.next_attempt_at <= now()
1586
+ AND q.result_label IS DISTINCT FROM 'Wacht op een verse ophaalronde van Floriday (minder dan 2 minuten oud)'
1587
+ AND (NOT v_vers OR (q.voorraad_mutatie AND public.voorraad_floriday_mutatie_doel(q.product_id) IS NULL));
1519
1588
  RETURN QUERY
1520
1589
  WITH candidates AS (
1521
- SELECT q.id
1522
- FROM public.floriday_continuous_stock_queue q
1590
+ SELECT q.id FROM public.floriday_continuous_stock_queue q
1523
1591
  WHERE q.next_attempt_at <= now()
1524
- AND (
1525
- q.status = 'pending'
1526
- OR (q.status = 'processing' AND q.locked_at < now() - interval '5 minutes')
1527
- )
1592
+ AND (q.status = 'pending' OR (q.status = 'processing' AND q.locked_at < now() - interval '5 minutes'))
1593
+ AND v_vers
1594
+ AND (NOT q.voorraad_mutatie OR public.voorraad_floriday_mutatie_doel(q.product_id) IS NOT NULL)
1528
1595
  ORDER BY q.next_attempt_at ASC, q.created_at ASC
1529
1596
  LIMIT GREATEST(1, LEAST(p_limit, 200))
1530
- FOR UPDATE SKIP LOCKED
1531
- )
1597
+ FOR UPDATE SKIP LOCKED)
1532
1598
  UPDATE public.floriday_continuous_stock_queue q
1533
- SET status = 'processing',
1534
- locked_at = now(),
1535
- locked_by = p_worker,
1536
- updated_at = now()
1537
- FROM candidates c
1538
- WHERE q.id = c.id
1599
+ SET status = 'processing', locked_at = now(), locked_by = p_worker, updated_at = now(),
1600
+ number_of_pieces = CASE WHEN q.voorraad_mutatie THEN public.voorraad_floriday_mutatie_doel(q.product_id) ELSE q.number_of_pieces END,
1601
+ desired_available = CASE WHEN q.voorraad_mutatie THEN public.voorraad_floriday_mutatie_doel(q.product_id) > 0 ELSE q.desired_available END,
1602
+ result_label = CASE WHEN q.result_label LIKE 'Wacht op een verse ophaalronde%' THEN NULL ELSE q.result_label END
1603
+ FROM candidates c WHERE q.id = c.id
1539
1604
  RETURNING q.*;
1540
- END;
1541
- $function$
1605
+ END $function$
1542
1606
  ;
1543
1607
  REVOKE ALL ON FUNCTION claim_floriday_stock_queue(integer,text) FROM PUBLIC;
1544
1608
  REVOKE ALL ON FUNCTION claim_floriday_stock_queue(integer,text) FROM anon;
@@ -1895,252 +1959,11 @@ CREATE OR REPLACE FUNCTION public.consume_bom_for_production(p_parent_kind text,
1895
1959
  LANGUAGE plpgsql
1896
1960
  SECURITY DEFINER
1897
1961
  SET search_path TO 'public'
1898
- AS $function$
1899
- DECLARE
1900
- v_parent_product_uuid uuid;
1901
- v_bom_row record;
1902
- v_allocation_row record;
1903
- v_needed numeric;
1904
- v_original_needed numeric;
1905
- v_remaining numeric;
1906
- v_take numeric;
1907
- v_current numeric;
1908
- v_already_planned numeric;
1909
- v_available numeric;
1910
- v_group_available numeric;
1911
- v_before numeric;
1912
- v_after numeric;
1913
- v_shortages jsonb := '[]'::jsonb;
1914
- v_consumed jsonb := '[]'::jsonb;
1915
- v_component_key text;
1916
- v_component_name text;
1917
- v_component_product_id uuid;
1918
- v_stock_row record;
1919
- v_connection_id uuid := p_connection_id;
1920
- BEGIN
1921
- PERFORM public.voorraad_vereis('ingelogd', 'consume_bom_for_production');
1922
- IF p_quantity IS NULL OR p_quantity <= 0 THEN
1923
- RETURN jsonb_build_object('ok', true, 'consumed', '[]'::jsonb);
1924
- END IF;
1925
-
1926
- IF p_parent_kind = 'product' THEN
1927
- v_parent_product_uuid := p_parent_id::uuid;
1928
- END IF;
1929
-
1930
- CREATE TEMP TABLE IF NOT EXISTS pg_temp.production_components (
1931
- component_key text PRIMARY KEY,
1932
- component_product_id uuid,
1933
- component_name text,
1934
- needed numeric NOT NULL DEFAULT 0
1935
- ) ON COMMIT DROP;
1936
- TRUNCATE pg_temp.production_components;
1937
-
1938
- FOR v_bom_row IN
1939
- SELECT b.*,
1940
- p.product AS product_name,
1941
- p.trade_item_key AS product_key,
1942
- p.article_number AS product_article,
1943
- g.id AS component_group_id,
1944
- g.name AS component_group_name
1945
- FROM product_bom_items b
1946
- LEFT JOIN products p ON p.id = b.component_product_id
1947
- LEFT JOIN product_groups g ON (
1948
- g.head_product_id = b.component_product_id
1949
- OR g.head_trade_item_floriday_id = b.component_trade_item_floriday_id
1950
- )
1951
- WHERE (p_parent_kind = 'product' AND b.parent_product_id = v_parent_product_uuid)
1952
- OR (p_parent_kind = 'floriday' AND b.parent_trade_item_floriday_id = p_parent_id)
1953
- LOOP
1954
- v_needed := COALESCE(v_bom_row.quantity, 1) * p_quantity;
1955
- v_original_needed := v_needed;
1956
-
1957
- IF v_bom_row.component_group_id IS NOT NULL THEN
1958
- v_remaining := v_needed;
1959
- v_group_available := 0;
1960
-
1961
- FOR v_allocation_row IN
1962
- SELECT gi.trade_item_id,
1963
- gi.sort_order,
1964
- p2.id AS product_id,
1965
- p2.product AS product_name,
1966
- p2.trade_item_key AS product_key,
1967
- f.floriday_id,
1968
- f.data AS floriday_data,
1969
- td.id AS draft_id,
1970
- td.data AS draft_data
1971
- FROM product_group_items gi
1972
- LEFT JOIN products p2 ON p2.id::text = gi.trade_item_id
1973
- LEFT JOIN floriday_trade_items_cache f ON f.floriday_id = gi.trade_item_id
1974
- LEFT JOIN trade_item_drafts td ON td.id::text = gi.trade_item_id
1975
- WHERE gi.product_group_id = v_bom_row.component_group_id
1976
- ORDER BY gi.sort_order ASC NULLS LAST, gi.created_at ASC NULLS LAST
1977
- LOOP
1978
- v_component_key := COALESCE(
1979
- NULLIF(trim(v_allocation_row.product_key), ''),
1980
- v_allocation_row.floriday_id,
1981
- CASE WHEN v_allocation_row.draft_id IS NOT NULL THEN 'draft:' || v_allocation_row.draft_id::text END,
1982
- v_allocation_row.trade_item_id
1983
- );
1984
- v_component_product_id := v_allocation_row.product_id;
1985
- v_component_name := COALESCE(
1986
- v_allocation_row.product_name,
1987
- v_allocation_row.floriday_data->'tradeItemName'->>'nl',
1988
- v_allocation_row.floriday_data->'tradeItemName'->>'en',
1989
- v_allocation_row.draft_data->>'name',
1990
- v_allocation_row.draft_data->>'botanicalName',
1991
- v_allocation_row.draft_data->>'articleCode',
1992
- v_allocation_row.trade_item_id
1993
- );
1994
-
1995
- SELECT COALESCE(ts.physical_stock, 0) INTO v_current
1996
- FROM trade_item_stock ts
1997
- WHERE ts.trade_item_key = v_component_key;
1998
- v_current := COALESCE(v_current, 0);
1999
-
2000
- SELECT COALESCE(pc.needed, 0) INTO v_already_planned
2001
- FROM pg_temp.production_components pc
2002
- WHERE pc.component_key = v_component_key;
2003
- v_already_planned := COALESCE(v_already_planned, 0);
2004
-
2005
- v_available := GREATEST(v_current - v_already_planned, 0);
2006
- v_group_available := v_group_available + v_available;
2007
- v_take := LEAST(v_remaining, v_available);
2008
-
2009
- IF v_take > 0 THEN
2010
- INSERT INTO pg_temp.production_components(component_key, component_product_id, component_name, needed)
2011
- VALUES (v_component_key, v_component_product_id, v_component_name, v_take)
2012
- ON CONFLICT (component_key) DO UPDATE
2013
- SET needed = production_components.needed + EXCLUDED.needed,
2014
- component_name = EXCLUDED.component_name,
2015
- component_product_id = COALESCE(production_components.component_product_id, EXCLUDED.component_product_id);
2016
- v_remaining := v_remaining - v_take;
2017
- END IF;
2018
-
2019
- IF v_remaining <= 0 THEN
2020
- EXIT;
2021
- END IF;
2022
- END LOOP;
2023
-
2024
- IF v_remaining > 0 THEN
2025
- v_shortages := v_shortages || jsonb_build_object(
2026
- 'name', COALESCE(v_bom_row.component_group_name, v_bom_row.product_name, v_bom_row.component_product_id::text, v_bom_row.component_trade_item_floriday_id),
2027
- 'needed', v_original_needed,
2028
- 'available', v_group_available
2029
- );
2030
- END IF;
2031
- ELSE
2032
- IF v_bom_row.component_product_id IS NOT NULL THEN
2033
- v_component_key := COALESCE(NULLIF(trim(v_bom_row.product_key), ''), 'product:' || v_bom_row.component_product_id::text);
2034
- v_component_product_id := v_bom_row.component_product_id;
2035
- v_component_name := COALESCE(v_bom_row.product_name, v_bom_row.component_product_id::text);
2036
- ELSE
2037
- v_component_key := v_bom_row.component_trade_item_floriday_id;
2038
- v_component_product_id := NULL;
2039
- SELECT COALESCE(f.data->'tradeItemName'->>'nl', f.data->'tradeItemName'->>'en', v_component_key)
2040
- INTO v_component_name
2041
- FROM floriday_trade_items_cache f
2042
- WHERE f.floriday_id = v_component_key;
2043
- v_component_name := COALESCE(v_component_name, v_component_key);
2044
- END IF;
2045
-
2046
- INSERT INTO pg_temp.production_components(component_key, component_product_id, component_name, needed)
2047
- VALUES (v_component_key, v_component_product_id, v_component_name, v_needed)
2048
- ON CONFLICT (component_key) DO UPDATE
2049
- SET needed = production_components.needed + EXCLUDED.needed,
2050
- component_name = EXCLUDED.component_name,
2051
- component_product_id = COALESCE(production_components.component_product_id, EXCLUDED.component_product_id);
2052
- END IF;
2053
- END LOOP;
2054
-
2055
- -- No BOM? nothing to consume
2056
- IF NOT EXISTS (SELECT 1 FROM pg_temp.production_components) THEN
2057
- RETURN jsonb_build_object('ok', true, 'consumed', '[]'::jsonb, 'no_bom', true);
2058
- END IF;
2059
-
2060
- IF jsonb_array_length(v_shortages) = 0 THEN
2061
- FOR v_stock_row IN SELECT * FROM pg_temp.production_components LOOP
2062
- SELECT COALESCE(ts.physical_stock, 0) INTO v_current
2063
- FROM trade_item_stock ts
2064
- WHERE ts.trade_item_key = v_stock_row.component_key;
2065
- v_current := COALESCE(v_current, 0);
2066
-
2067
- IF v_current < v_stock_row.needed THEN
2068
- v_shortages := v_shortages || jsonb_build_object(
2069
- 'name', v_stock_row.component_name,
2070
- 'needed', v_stock_row.needed,
2071
- 'available', v_current
2072
- );
2073
- END IF;
2074
- END LOOP;
2075
- END IF;
2076
-
2077
- IF jsonb_array_length(v_shortages) > 0 THEN
2078
- RAISE EXCEPTION 'Onvoldoende voorraad: %', v_shortages::text;
2079
- END IF;
2080
-
2081
- IF v_connection_id IS NULL THEN
2082
- SELECT ts.connection_id INTO v_connection_id
2083
- FROM pg_temp.production_components pc
2084
- JOIN trade_item_stock ts ON ts.trade_item_key = pc.component_key
2085
- WHERE ts.connection_id IS NOT NULL
2086
- LIMIT 1;
2087
- END IF;
2088
-
2089
- IF v_connection_id IS NULL THEN
2090
- SELECT fc.id INTO v_connection_id
2091
- FROM floriday_connections fc
2092
- WHERE fc.is_active = true
2093
- ORDER BY fc.created_at DESC NULLS LAST
2094
- LIMIT 1;
2095
- END IF;
2096
-
2097
- FOR v_stock_row IN SELECT * FROM pg_temp.production_components LOOP
2098
- SELECT COALESCE(ts.physical_stock, 0) INTO v_before
2099
- FROM trade_item_stock ts
2100
- WHERE ts.trade_item_key = v_stock_row.component_key;
2101
- v_before := COALESCE(v_before, 0);
2102
- v_after := v_before - v_stock_row.needed;
2103
-
2104
- -- Set flag so the trigger on trade_item_stock does not re-trigger consumption for components
2105
- PERFORM set_config('app.bom_consume_in_progress', 'on', true);
2106
-
2107
- INSERT INTO trade_item_stock (connection_id, trade_item_key, physical_stock, user_id)
2108
- VALUES (v_connection_id, v_stock_row.component_key, v_after::int, p_user_id)
2109
- ON CONFLICT (connection_id, trade_item_key) DO UPDATE
2110
- SET physical_stock = EXCLUDED.physical_stock,
2111
- connection_id = COALESCE(trade_item_stock.connection_id, EXCLUDED.connection_id),
2112
- user_id = COALESCE(trade_item_stock.user_id, EXCLUDED.user_id),
2113
- updated_at = now();
2114
-
2115
- PERFORM set_config('app.bom_consume_in_progress', 'off', true);
2116
-
2117
- IF v_stock_row.component_product_id IS NOT NULL THEN
2118
- UPDATE products
2119
- SET quantity = v_after::int,
2120
- updated_at = now()
2121
- WHERE id = v_stock_row.component_product_id;
2122
- END IF;
2123
-
2124
- INSERT INTO stock_mutation_log (
2125
- connection_id, trade_item_key, mutation_type, qty_delta,
2126
- physical_before, physical_after, reason, created_by
2127
- ) VALUES (
2128
- v_connection_id, v_stock_row.component_key, 'production_consume', -v_stock_row.needed,
2129
- v_before, v_after,
2130
- 'Verbruik voor productie van ' || p_parent_key || ' (x' || p_quantity || ')',
2131
- p_user_id
2132
- );
2133
-
2134
- v_consumed := v_consumed || jsonb_build_object(
2135
- 'name', v_stock_row.component_name,
2136
- 'consumed', v_stock_row.needed,
2137
- 'remaining', v_after
2138
- );
2139
- END LOOP;
2140
-
2141
- RETURN jsonb_build_object('ok', true, 'consumed', v_consumed);
2142
- END;
2143
- $function$
1962
+ AS $function$
1963
+ BEGIN
1964
+ PERFORM public.voorraad_vereis('ingelogd', 'consume_bom_for_production');
1965
+ RAISE EXCEPTION 'Gebruik Productieorder maken.' USING ERRCODE = 'P0001', HINT = 'Produceren loopt nu via productieorders.';
1966
+ END $function$
2144
1967
  ;
2145
1968
  REVOKE ALL ON FUNCTION consume_bom_for_production(text,text,text,numeric,uuid,uuid) FROM PUBLIC;
2146
1969
  REVOKE ALL ON FUNCTION consume_bom_for_production(text,text,text,numeric,uuid,uuid) FROM anon;
@@ -2150,208 +1973,11 @@ CREATE OR REPLACE FUNCTION public.consume_bom_for_production(p_parent_kind text,
2150
1973
  LANGUAGE plpgsql
2151
1974
  SECURITY DEFINER
2152
1975
  SET search_path TO 'public'
2153
- AS $function$
2154
- DECLARE
2155
- v_parent_product_uuid uuid;
2156
- v_bom_row record;
2157
- v_allocation_row record;
2158
- v_needed numeric;
2159
- v_original_needed numeric;
2160
- v_remaining numeric;
2161
- v_take numeric;
2162
- v_current numeric;
2163
- v_already_planned numeric;
2164
- v_available numeric;
2165
- v_group_available numeric;
2166
- v_before numeric;
2167
- v_after numeric;
2168
- v_shortages jsonb := '[]'::jsonb;
2169
- v_consumed jsonb := '[]'::jsonb;
2170
- v_component_key text;
2171
- v_component_name text;
2172
- v_component_product_id uuid;
2173
- v_stock_row record;
2174
- v_connection_id uuid := p_connection_id;
2175
- v_batch_id uuid := COALESCE(p_batch_id, gen_random_uuid());
2176
- BEGIN
2177
- PERFORM public.voorraad_vereis('ingelogd', 'consume_bom_for_production');
2178
- IF p_quantity IS NULL OR p_quantity <= 0 THEN
2179
- RETURN jsonb_build_object('ok', true, 'consumed', '[]'::jsonb, 'batch_id', v_batch_id);
2180
- END IF;
2181
-
2182
- IF p_parent_kind = 'product' THEN
2183
- v_parent_product_uuid := p_parent_id::uuid;
2184
- END IF;
2185
-
2186
- CREATE TEMP TABLE IF NOT EXISTS pg_temp.production_components (
2187
- component_key text PRIMARY KEY,
2188
- component_product_id uuid,
2189
- component_name text,
2190
- needed numeric NOT NULL DEFAULT 0
2191
- ) ON COMMIT DROP;
2192
- TRUNCATE pg_temp.production_components;
2193
-
2194
- FOR v_bom_row IN
2195
- SELECT b.*, p.product AS product_name, p.trade_item_key AS product_key,
2196
- g.id AS component_group_id, g.name AS component_group_name
2197
- FROM product_bom_items b
2198
- LEFT JOIN products p ON p.id = b.component_product_id
2199
- LEFT JOIN product_groups g ON (
2200
- g.head_product_id = b.component_product_id
2201
- OR g.head_trade_item_floriday_id = b.component_trade_item_floriday_id
2202
- )
2203
- WHERE (p_parent_kind = 'product' AND b.parent_product_id = v_parent_product_uuid)
2204
- OR (p_parent_kind = 'floriday' AND b.parent_trade_item_floriday_id = p_parent_id)
2205
- LOOP
2206
- v_needed := COALESCE(v_bom_row.quantity, 1) * p_quantity;
2207
- v_original_needed := v_needed;
2208
-
2209
- IF v_bom_row.component_group_id IS NOT NULL THEN
2210
- v_remaining := v_needed;
2211
- v_group_available := 0;
2212
- FOR v_allocation_row IN
2213
- SELECT gi.trade_item_id, gi.sort_order,
2214
- p2.id AS product_id, p2.product AS product_name, p2.trade_item_key AS product_key,
2215
- f.floriday_id, f.data AS floriday_data,
2216
- td.id AS draft_id, td.data AS draft_data
2217
- FROM product_group_items gi
2218
- LEFT JOIN products p2 ON p2.id::text = gi.trade_item_id
2219
- LEFT JOIN floriday_trade_items_cache f ON f.floriday_id = gi.trade_item_id
2220
- LEFT JOIN trade_item_drafts td ON td.id::text = gi.trade_item_id
2221
- WHERE gi.product_group_id = v_bom_row.component_group_id
2222
- ORDER BY gi.sort_order ASC NULLS LAST, gi.created_at ASC NULLS LAST
2223
- LOOP
2224
- v_component_key := COALESCE(
2225
- NULLIF(trim(v_allocation_row.product_key), ''),
2226
- v_allocation_row.floriday_id,
2227
- CASE WHEN v_allocation_row.draft_id IS NOT NULL THEN 'draft:' || v_allocation_row.draft_id::text END,
2228
- v_allocation_row.trade_item_id
2229
- );
2230
- v_component_product_id := v_allocation_row.product_id;
2231
- v_component_name := COALESCE(
2232
- v_allocation_row.product_name,
2233
- v_allocation_row.floriday_data->'tradeItemName'->>'nl',
2234
- v_allocation_row.floriday_data->'tradeItemName'->>'en',
2235
- v_allocation_row.draft_data->>'name',
2236
- v_allocation_row.draft_data->>'articleCode',
2237
- v_allocation_row.trade_item_id
2238
- );
2239
- SELECT COALESCE(ts.physical_stock, 0) INTO v_current
2240
- FROM trade_item_stock ts WHERE ts.trade_item_key = v_component_key;
2241
- v_current := COALESCE(v_current, 0);
2242
- SELECT COALESCE(pc.needed, 0) INTO v_already_planned
2243
- FROM pg_temp.production_components pc WHERE pc.component_key = v_component_key;
2244
- v_already_planned := COALESCE(v_already_planned, 0);
2245
- v_available := GREATEST(v_current - v_already_planned, 0);
2246
- v_group_available := v_group_available + v_available;
2247
- v_take := LEAST(v_remaining, v_available);
2248
- IF v_take > 0 THEN
2249
- INSERT INTO pg_temp.production_components(component_key, component_product_id, component_name, needed)
2250
- VALUES (v_component_key, v_component_product_id, v_component_name, v_take)
2251
- ON CONFLICT (component_key) DO UPDATE
2252
- SET needed = production_components.needed + EXCLUDED.needed,
2253
- component_name = EXCLUDED.component_name,
2254
- component_product_id = COALESCE(production_components.component_product_id, EXCLUDED.component_product_id);
2255
- v_remaining := v_remaining - v_take;
2256
- END IF;
2257
- IF v_remaining <= 0 THEN EXIT; END IF;
2258
- END LOOP;
2259
- IF v_remaining > 0 THEN
2260
- v_shortages := v_shortages || jsonb_build_object(
2261
- 'name', COALESCE(v_bom_row.component_group_name, v_bom_row.product_name, v_bom_row.component_trade_item_floriday_id),
2262
- 'needed', v_original_needed, 'available', v_group_available);
2263
- END IF;
2264
- ELSE
2265
- IF v_bom_row.component_product_id IS NOT NULL THEN
2266
- v_component_key := COALESCE(NULLIF(trim(v_bom_row.product_key), ''), 'product:' || v_bom_row.component_product_id::text);
2267
- v_component_product_id := v_bom_row.component_product_id;
2268
- v_component_name := COALESCE(v_bom_row.product_name, v_bom_row.component_product_id::text);
2269
- ELSE
2270
- v_component_key := v_bom_row.component_trade_item_floriday_id;
2271
- v_component_product_id := NULL;
2272
- SELECT COALESCE(f.data->'tradeItemName'->>'nl', f.data->'tradeItemName'->>'en', v_component_key)
2273
- INTO v_component_name FROM floriday_trade_items_cache f WHERE f.floriday_id = v_component_key;
2274
- v_component_name := COALESCE(v_component_name, v_component_key);
2275
- END IF;
2276
- INSERT INTO pg_temp.production_components(component_key, component_product_id, component_name, needed)
2277
- VALUES (v_component_key, v_component_product_id, v_component_name, v_needed)
2278
- ON CONFLICT (component_key) DO UPDATE
2279
- SET needed = production_components.needed + EXCLUDED.needed,
2280
- component_name = EXCLUDED.component_name,
2281
- component_product_id = COALESCE(production_components.component_product_id, EXCLUDED.component_product_id);
2282
- END IF;
2283
- END LOOP;
2284
-
2285
- IF NOT EXISTS (SELECT 1 FROM pg_temp.production_components) THEN
2286
- RETURN jsonb_build_object('ok', true, 'consumed', '[]'::jsonb, 'no_bom', true, 'batch_id', v_batch_id);
2287
- END IF;
2288
-
2289
- IF jsonb_array_length(v_shortages) = 0 THEN
2290
- FOR v_stock_row IN SELECT * FROM pg_temp.production_components LOOP
2291
- SELECT COALESCE(ts.physical_stock, 0) INTO v_current
2292
- FROM trade_item_stock ts WHERE ts.trade_item_key = v_stock_row.component_key;
2293
- v_current := COALESCE(v_current, 0);
2294
- IF v_current < v_stock_row.needed THEN
2295
- v_shortages := v_shortages || jsonb_build_object(
2296
- 'name', v_stock_row.component_name, 'needed', v_stock_row.needed, 'available', v_current);
2297
- END IF;
2298
- END LOOP;
2299
- END IF;
2300
-
2301
- IF jsonb_array_length(v_shortages) > 0 THEN
2302
- RAISE EXCEPTION 'Onvoldoende voorraad: %', v_shortages::text;
2303
- END IF;
2304
-
2305
- IF v_connection_id IS NULL THEN
2306
- SELECT ts.connection_id INTO v_connection_id
2307
- FROM pg_temp.production_components pc
2308
- JOIN trade_item_stock ts ON ts.trade_item_key = pc.component_key
2309
- WHERE ts.connection_id IS NOT NULL LIMIT 1;
2310
- END IF;
2311
- IF v_connection_id IS NULL THEN
2312
- SELECT fc.id INTO v_connection_id FROM floriday_connections fc
2313
- WHERE fc.is_active = true ORDER BY fc.created_at DESC NULLS LAST LIMIT 1;
2314
- END IF;
2315
-
2316
- FOR v_stock_row IN SELECT * FROM pg_temp.production_components LOOP
2317
- SELECT COALESCE(ts.physical_stock, 0) INTO v_before
2318
- FROM trade_item_stock ts WHERE ts.trade_item_key = v_stock_row.component_key;
2319
- v_before := COALESCE(v_before, 0);
2320
- v_after := v_before - v_stock_row.needed;
2321
-
2322
- PERFORM set_config('app.bom_consume_in_progress', 'on', true);
2323
- INSERT INTO trade_item_stock (connection_id, trade_item_key, physical_stock, user_id)
2324
- VALUES (v_connection_id, v_stock_row.component_key, v_after::int, p_user_id)
2325
- ON CONFLICT (connection_id, trade_item_key) DO UPDATE
2326
- SET physical_stock = EXCLUDED.physical_stock,
2327
- connection_id = COALESCE(trade_item_stock.connection_id, EXCLUDED.connection_id),
2328
- user_id = COALESCE(trade_item_stock.user_id, EXCLUDED.user_id),
2329
- updated_at = now();
2330
- PERFORM set_config('app.bom_consume_in_progress', 'off', true);
2331
-
2332
- IF v_stock_row.component_product_id IS NOT NULL THEN
2333
- UPDATE products SET quantity = v_after::int, updated_at = now()
2334
- WHERE id = v_stock_row.component_product_id;
2335
- END IF;
2336
-
2337
- INSERT INTO stock_mutation_log (
2338
- connection_id, trade_item_key, mutation_type, qty_delta,
2339
- physical_before, physical_after, reason, created_by,
2340
- production_batch_id, parent_trade_item_key
2341
- ) VALUES (
2342
- v_connection_id, v_stock_row.component_key, 'production_consume', -v_stock_row.needed,
2343
- v_before, v_after,
2344
- 'Verbruik voor productie van ' || p_parent_key || ' (x' || p_quantity || ')',
2345
- p_user_id, v_batch_id, p_parent_key
2346
- );
2347
-
2348
- v_consumed := v_consumed || jsonb_build_object(
2349
- 'name', v_stock_row.component_name, 'consumed', v_stock_row.needed, 'remaining', v_after);
2350
- END LOOP;
2351
-
2352
- RETURN jsonb_build_object('ok', true, 'consumed', v_consumed, 'batch_id', v_batch_id);
2353
- END;
2354
- $function$
1976
+ AS $function$
1977
+ BEGIN
1978
+ PERFORM public.voorraad_vereis('ingelogd', 'consume_bom_for_production');
1979
+ RAISE EXCEPTION 'Gebruik Productieorder maken.' USING ERRCODE = 'P0001', HINT = 'Produceren loopt nu via productieorders.';
1980
+ END $function$
2355
1981
  ;
2356
1982
  REVOKE ALL ON FUNCTION consume_bom_for_production(text,text,text,numeric,uuid,uuid,uuid) FROM PUBLIC;
2357
1983
  REVOKE ALL ON FUNCTION consume_bom_for_production(text,text,text,numeric,uuid,uuid,uuid) FROM anon;
@@ -2362,45 +1988,131 @@ CREATE OR REPLACE FUNCTION public.consume_order_item(p_order_id uuid, p_order_it
2362
1988
  SECURITY DEFINER
2363
1989
  SET search_path TO 'public'
2364
1990
  AS $function$
2365
- DECLARE a record; n integer := 0; v_ins integer; v_norm text := public.normalize_order_source(p_source);
1991
+ DECLARE v jsonb;
2366
1992
  BEGIN
2367
1993
  PERFORM public.voorraad_vereis('ingelogd', 'consume_order_item');
2368
- IF p_order_item_id IS NULL THEN RETURN 0; END IF;
2369
- IF EXISTS (SELECT 1 FROM public.stock_mutation_log WHERE order_item_id = p_order_item_id AND mutation_type = 'consumption')
2370
- AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log WHERE order_item_id = p_order_item_id AND mutation_type = 'consumption_done') THEN
2371
- RETURN 0;
2372
- END IF;
2373
- INSERT INTO public.stock_mutation_log(order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
2374
- VALUES (p_order_id, p_order_item_id, v_norm, 'consumption_done', 0, COALESCE(p_reason,'afgeboekt'), auth.uid())
2375
- ON CONFLICT (order_item_id) WHERE mutation_type = 'consumption_done' AND order_item_id IS NOT NULL DO NOTHING;
2376
- GET DIAGNOSTICS v_ins = ROW_COUNT;
2377
- IF v_ins = 0 THEN RETURN 0; END IF;
2378
-
2379
- FOR a IN SELECT * FROM public.logistics_order_line_allocation(p_order_item_id) LOOP
2380
- IF a.source = 'error' OR a.trade_item_key IS NULL OR a.trade_item_key LIKE 'group:%' THEN
2381
- INSERT INTO public.stock_mutation_log(trade_item_key, order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
2382
- VALUES (a.trade_item_key, p_order_id, p_order_item_id, v_norm, 'consumption_blocked', COALESCE(a.qty,0),
2383
- 'toewijzing Engine: ' || COALESCE(a.note, 'fout'), auth.uid());
2384
- ELSIF COALESCE(a.qty,0) > 0 AND v_norm = 'FO' AND public.voorraad_floriday_leads_key(a.trade_item_key) THEN
2385
- -- Floriday leidt: Floriday heeft deze verkoop al van het eigen aanbod afgetrokken.
2386
- INSERT INTO public.stock_mutation_log(trade_item_key, order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
2387
- VALUES (a.trade_item_key, p_order_id, p_order_item_id, v_norm, 'consumption_skipped', a.qty,
2388
- 'Niet afgeboekt: Floriday leidt dit artikel en heeft de verkoop al verwerkt — ' || COALESCE(p_reason,''), auth.uid());
2389
- ELSIF COALESCE(a.qty,0) > 0 THEN
2390
- PERFORM public.apply_stock_consumption(a.trade_item_key, a.qty, p_order_id, p_order_item_id, p_source,
2391
- COALESCE(p_reason,'afgeboekt') || ' [' || a.source || ']');
2392
- IF v_norm <> 'FO' AND public.voorraad_floriday_leads_key(a.trade_item_key) THEN
2393
- PERFORM public.voorraad_alert_floriday_not_updated(a.trade_item_key, a.qty, p_order_id, p_order_item_id);
1994
+ v := public.consume_order_item_v2(p_order_id, p_order_item_id, NULL, p_source, p_reason, 'totaal');
1995
+ RETURN COALESCE((v->>'afgeboekt_regels')::integer, 0);
1996
+ END $function$
1997
+ ;
1998
+ REVOKE ALL ON FUNCTION consume_order_item(uuid,uuid,text,text) FROM PUBLIC;
1999
+ REVOKE ALL ON FUNCTION consume_order_item(uuid,uuid,text,text) FROM anon;
2000
+ GRANT EXECUTE ON FUNCTION consume_order_item(uuid,uuid,text,text) TO service_role;
2001
+ CREATE OR REPLACE FUNCTION public.consume_order_item_v2(p_order_id uuid, p_order_item_id uuid, p_verzonden numeric, p_source text, p_reason text, p_modus text DEFAULT 'totaal'::text)
2002
+ RETURNS jsonb
2003
+ LANGUAGE plpgsql
2004
+ SECURITY DEFINER
2005
+ SET search_path TO 'public'
2006
+ AS $function$
2007
+ DECLARE
2008
+ v_norm text := public.normalize_order_source(p_source);
2009
+ v_besteld numeric; v_order uuid; v_pid uuid; v_prev numeric; v_doel numeric; v_keys text[]; v_regel text;
2010
+ v_pkey text; a record; r record; v_doelkey numeric; v_delta numeric; n integer := 0;
2011
+ v_reason text := COALESCE(NULLIF(p_reason, ''), 'afgeboekt');
2012
+ BEGIN
2013
+ PERFORM public.voorraad_vereis('ingelogd', 'consume_order_item_v2');
2014
+ IF p_order_item_id IS NULL THEN RETURN jsonb_build_object('status', 'geen_regel'); END IF;
2015
+ SELECT oi.quantity, oi.order_id, oi.product_id INTO v_besteld, v_order, v_pid FROM public.order_items oi WHERE oi.id = p_order_item_id;
2016
+ IF NOT FOUND THEN RETURN jsonb_build_object('status', 'regel_niet_gevonden'); END IF;
2017
+ v_order := COALESCE(p_order_id, v_order);
2018
+ v_besteld := COALESCE(v_besteld, 0);
2019
+
2020
+ INSERT INTO public.voorraad_orderregel_verzending(order_item_id, order_id, besteld)
2021
+ VALUES (p_order_item_id, v_order, v_besteld) ON CONFLICT (order_item_id) DO NOTHING;
2022
+ SELECT verzonden, afboekregel INTO v_prev, v_regel FROM public.voorraad_orderregel_verzending WHERE order_item_id = p_order_item_id FOR UPDATE;
2023
+
2024
+ IF NOT EXISTS (SELECT 1 FROM public.voorraad_orderregel_afboeking WHERE order_item_id = p_order_item_id) THEN
2025
+ IF v_pid IS NOT NULL AND EXISTS (SELECT 1 FROM public.product_bom_items b WHERE b.parent_product_id = v_pid) THEN
2026
+ v_pkey := public.ensure_trade_item_stock_row_key(v_pid::text);
2027
+ IF v_pkey IS NOT NULL AND (
2028
+ EXISTS (SELECT 1 FROM public.productie_orders po WHERE po.product_id = v_pid AND po.status IN ('open','verwerkt'))
2029
+ OR COALESCE((SELECT MAX(s.physical_stock) FROM public.trade_item_stock s WHERE s.trade_item_key = v_pkey), 0) > 0) THEN
2030
+ v_regel := 'samenstelling';
2031
+ INSERT INTO public.voorraad_orderregel_afboeking(order_item_id, trade_item_key, order_id, besteld)
2032
+ VALUES (p_order_item_id, v_pkey, v_order, v_besteld) ON CONFLICT DO NOTHING;
2394
2033
  END IF;
2034
+ END IF;
2035
+ IF v_regel IS DISTINCT FROM 'samenstelling' THEN
2036
+ v_regel := 'onderdelen';
2037
+ FOR a IN SELECT * FROM public.logistics_order_line_allocation(p_order_item_id) LOOP
2038
+ IF a.source = 'error' OR a.trade_item_key IS NULL OR a.trade_item_key LIKE 'group:%' OR COALESCE(a.qty, 0) <= 0 THEN CONTINUE; END IF;
2039
+ INSERT INTO public.voorraad_orderregel_afboeking(order_item_id, trade_item_key, order_id, besteld)
2040
+ VALUES (p_order_item_id, a.trade_item_key, v_order, a.qty)
2041
+ ON CONFLICT (order_item_id, trade_item_key) DO UPDATE SET besteld = public.voorraad_orderregel_afboeking.besteld + EXCLUDED.besteld;
2042
+ END LOOP;
2043
+ END IF;
2044
+ -- Eerder (oude route) afgeboekt: overnemen, zodat niets twee keer gaat.
2045
+ INSERT INTO public.voorraad_orderregel_afboeking(order_item_id, trade_item_key, order_id, besteld, afgeboekt)
2046
+ SELECT p_order_item_id, l.trade_item_key, v_order, 0, -SUM(l.qty_delta)
2047
+ FROM public.stock_mutation_log l
2048
+ WHERE l.order_item_id = p_order_item_id AND l.mutation_type = 'consumption' AND l.trade_item_key IS NOT NULL
2049
+ GROUP BY l.trade_item_key
2050
+ ON CONFLICT (order_item_id, trade_item_key) DO UPDATE SET afgeboekt = EXCLUDED.afgeboekt;
2051
+ IF EXISTS (SELECT 1 FROM public.voorraad_orderregel_afboeking WHERE order_item_id = p_order_item_id) THEN
2052
+ UPDATE public.voorraad_orderregel_verzending SET afboekregel = v_regel, updated_at = now() WHERE order_item_id = p_order_item_id;
2053
+ END IF;
2054
+ IF EXISTS (SELECT 1 FROM public.stock_mutation_log WHERE order_item_id = p_order_item_id AND mutation_type = 'consumption_done') THEN
2055
+ UPDATE public.voorraad_orderregel_verzending SET verzonden = GREATEST(verzonden, besteld), updated_at = now()
2056
+ WHERE order_item_id = p_order_item_id RETURNING verzonden INTO v_prev;
2057
+ END IF;
2058
+ END IF;
2059
+
2060
+ v_doel := CASE
2061
+ WHEN p_verzonden IS NULL THEN GREATEST(v_besteld, v_prev)
2062
+ WHEN p_modus = 'erbij' THEN LEAST(v_prev + GREATEST(p_verzonden, 0), GREATEST(v_besteld, v_prev))
2063
+ ELSE GREATEST(p_verzonden, v_prev) END;
2064
+
2065
+ IF NOT EXISTS (SELECT 1 FROM public.voorraad_orderregel_afboeking WHERE order_item_id = p_order_item_id) THEN
2066
+ IF v_doel > v_prev THEN
2067
+ INSERT INTO public.stock_mutation_log(order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
2068
+ VALUES (v_order, p_order_item_id, v_norm, 'consumption_blocked', v_doel - v_prev,
2069
+ 'Geen toewijzing voor deze regel; niets afgeboekt — ' || v_reason, auth.uid());
2070
+ END IF;
2071
+ RETURN jsonb_build_object('status', 'geblokkeerd', 'verzonden', v_prev, 'besteld', v_besteld);
2072
+ END IF;
2073
+
2074
+ SELECT array_agg(trade_item_key ORDER BY trade_item_key) INTO v_keys
2075
+ FROM public.voorraad_orderregel_afboeking WHERE order_item_id = p_order_item_id;
2076
+ PERFORM public.voorraad_lock_sleutels(v_keys);
2077
+
2078
+ FOR r IN SELECT * FROM public.voorraad_orderregel_afboeking WHERE order_item_id = p_order_item_id
2079
+ ORDER BY trade_item_key FOR UPDATE LOOP
2080
+ v_doelkey := CASE WHEN v_besteld <= 0 THEN r.besteld ELSE round(r.besteld * v_doel / v_besteld) END;
2081
+ v_delta := v_doelkey - r.afgeboekt;
2082
+ IF v_delta <= 0 THEN CONTINUE; END IF;
2083
+ IF v_norm = 'FO' AND public.voorraad_floriday_leads_key(r.trade_item_key) THEN
2084
+ INSERT INTO public.stock_mutation_log(trade_item_key, order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
2085
+ VALUES (r.trade_item_key, v_order, p_order_item_id, v_norm, 'consumption_skipped', v_delta,
2086
+ 'Niet afgeboekt: Floriday leidt dit artikel en heeft de verkoop al verwerkt — ' || v_reason, auth.uid());
2087
+ ELSE
2088
+ PERFORM public.voorraad_afboeken_kern(r.trade_item_key, v_delta, v_order, p_order_item_id, p_source,
2089
+ v_reason || ' [verzonden ' || v_doel || ' van ' || v_besteld || ']');
2395
2090
  n := n + 1;
2091
+ IF v_norm <> 'FO' AND public.voorraad_floriday_leads_key(r.trade_item_key)
2092
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_floriday_mutaties m WHERE m.order_item_id = p_order_item_id) THEN
2093
+ PERFORM public.voorraad_alert_floriday_not_updated(r.trade_item_key, v_delta, v_order, p_order_item_id);
2094
+ END IF;
2396
2095
  END IF;
2096
+ UPDATE public.voorraad_orderregel_afboeking SET afgeboekt = afgeboekt + v_delta, updated_at = now()
2097
+ WHERE order_item_id = r.order_item_id AND trade_item_key = r.trade_item_key;
2397
2098
  END LOOP;
2398
- RETURN n;
2099
+
2100
+ UPDATE public.voorraad_orderregel_verzending
2101
+ SET verzonden = v_doel, besteld = v_besteld, meer_dan_besteld = v_doel > v_besteld, updated_at = now()
2102
+ WHERE order_item_id = p_order_item_id;
2103
+ IF v_doel >= v_besteld THEN
2104
+ INSERT INTO public.stock_mutation_log(order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
2105
+ VALUES (v_order, p_order_item_id, v_norm, 'consumption_done', 0, v_reason, auth.uid())
2106
+ ON CONFLICT (order_item_id) WHERE mutation_type = 'consumption_done' AND order_item_id IS NOT NULL DO NOTHING;
2107
+ END IF;
2108
+ RETURN jsonb_build_object('status', 'ok', 'afgeboekt_regels', n, 'verzonden', v_doel, 'besteld', v_besteld,
2109
+ 'meer_dan_besteld', v_doel > v_besteld, 'afboekregel', v_regel);
2399
2110
  END $function$
2400
2111
  ;
2401
- REVOKE ALL ON FUNCTION consume_order_item(uuid,uuid,text,text) FROM PUBLIC;
2402
- REVOKE ALL ON FUNCTION consume_order_item(uuid,uuid,text,text) FROM anon;
2403
- GRANT EXECUTE ON FUNCTION consume_order_item(uuid,uuid,text,text) TO service_role;
2112
+ REVOKE ALL ON FUNCTION consume_order_item_v2(uuid,uuid,numeric,text,text,text) FROM PUBLIC;
2113
+ REVOKE ALL ON FUNCTION consume_order_item_v2(uuid,uuid,numeric,text,text,text) FROM anon;
2114
+ GRANT EXECUTE ON FUNCTION consume_order_item_v2(uuid,uuid,numeric,text,text,text) TO authenticated;
2115
+ GRANT EXECUTE ON FUNCTION consume_order_item_v2(uuid,uuid,numeric,text,text,text) TO service_role;
2404
2116
  CREATE OR REPLACE FUNCTION public.drain_inventory_recompute_queue()
2405
2117
  RETURNS integer
2406
2118
  LANGUAGE plpgsql
@@ -3245,6 +2957,7 @@ BEGIN
3245
2957
  WHERE t.trade_item_key IN (v_tid, 'product:'||v_pid::text, v_pid::text)
3246
2958
  ORDER BY (t.trade_item_key = v_tid) DESC, t.updated_at DESC NULLS LAST LIMIT 1;
3247
2959
  v_stock := NULLIF(v_it->>'stock','')::numeric;
2960
+ IF v_stock IS NOT NULL THEN v_stock := public.voorraad_floriday_volg_waarde(v_pid, p_connection_id, v_stock); END IF;
3248
2961
  IF v_stock IS NULL THEN v_res := 'Geen Floriday-voorraad gevonden (geen doorlopende voorraad en geen weekaanbod voor deze week)'; END IF;
3249
2962
  IF v_stock IS NOT NULL AND v_stock >= 0 AND v_stock IS DISTINCT FROM v_cur_stock THEN
3250
2963
  IF v_uid IS NULL THEN RAISE EXCEPTION 'Geen eigenaar bij de voorraadsturing'; END IF;
@@ -3283,200 +2996,10 @@ CREATE OR REPLACE FUNCTION public.floriday_ingest_sales_orders(p_limit integer D
3283
2996
  SECURITY DEFINER
3284
2997
  SET search_path TO 'public'
3285
2998
  AS $function$
3286
- DECLARE
3287
- r record;
3288
- v_pieces integer;
3289
- v_delta integer;
3290
- v_key text;
3291
- v_user uuid;
3292
- v_count integer := 0;
3293
- v_packing jsonb;
3294
- v_vbn text;
3295
- v_custom text;
3296
- v_fust_key text;
3297
- v_per_package integer;
3298
- v_packages integer;
3299
- v_fust_id uuid;
3300
- v_prev_packages integer;
3301
- v_fust_delta integer;
3302
- v_owner uuid;
3303
- BEGIN
3304
- PERFORM public.voorraad_vereis('ingelogd', 'floriday_ingest_sales_orders');
3305
- PERFORM public.voorraad_fust_startmoment_auto();
3306
- FOR r IN
3307
- SELECT c.connection_id,
3308
- c.floriday_id,
3309
- COALESCE((c.data->>'salesOrderVersion')::bigint, 0) AS version,
3310
- COALESCE(c.status, c.data->>'status') AS status,
3311
- c.is_deleted,
3312
- c.data->>'tradeItemId' AS trade_item_id,
3313
- COALESCE((c.data->>'numberOfPieces')::int, 0) AS pieces,
3314
- c.data->'packingConfiguration' AS order_packing,
3315
- c.data->'delivery'->>'latestDeliveryDateTime' AS lever_at
3316
- FROM public.floriday_salesorders_cache c
3317
- WHERE NOT EXISTS (
3318
- SELECT 1 FROM public.floriday_sales_order_ingest i
3319
- WHERE i.connection_id = c.connection_id
3320
- AND i.sales_order_id = c.floriday_id
3321
- AND i.order_version = COALESCE((c.data->>'salesOrderVersion')::bigint, 0)
3322
- )
3323
- ORDER BY c.updated_at ASC
3324
- LIMIT GREATEST(1, LEAST(p_limit, 1000))
3325
- LOOP
3326
- v_pieces := GREATEST(0, COALESCE(r.pieces, 0));
3327
- v_key := r.trade_item_id;
3328
-
3329
- -- Reservering: openstaande orders reserveren, geannuleerde geven vrij.
3330
- IF r.is_deleted OR COALESCE(r.status, '') IN ('CANCELLED', 'REJECTED', 'EXPIRED') THEN
3331
- v_delta := 0;
3332
- ELSE
3333
- v_delta := v_pieces;
3334
- END IF;
3335
-
3336
- IF v_key IS NOT NULL AND v_delta > 0 THEN
3337
- SELECT user_id INTO v_user FROM public.trade_item_stock
3338
- WHERE connection_id = r.connection_id AND trade_item_key = v_key LIMIT 1;
3339
-
3340
- IF v_user IS NOT NULL THEN
3341
- UPDATE public.trade_item_stock
3342
- SET reserved_stock = GREATEST(0, COALESCE(reserved_stock, 0) + v_delta),
3343
- change_source = 'FLORIDAY_ORDER',
3344
- updated_at = now()
3345
- WHERE connection_id = r.connection_id
3346
- AND trade_item_key = v_key;
3347
- END IF;
3348
- END IF;
3349
-
3350
- ---------------------------------------------------------------
3351
- -- Fustenafschrijving
3352
- ---------------------------------------------------------------
3353
- v_fust_key := NULL;
3354
- v_packages := 0;
3355
- v_fust_id := NULL;
3356
-
3357
- -- Belading: eerst die van de order zelf, anders de primaire belading
3358
- -- van het artikel uit de cache.
3359
- v_packing := r.order_packing;
3360
- IF v_packing IS NULL OR v_packing = 'null'::jsonb THEN
3361
- SELECT p INTO v_packing
3362
- FROM public.floriday_trade_items_cache tic
3363
- CROSS JOIN LATERAL jsonb_array_elements(
3364
- COALESCE(tic.data->'packingConfigurations', '[]'::jsonb)
3365
- ) AS p
3366
- WHERE tic.connection_id = r.connection_id
3367
- AND tic.floriday_id = v_key
3368
- ORDER BY COALESCE((p->>'primary')::boolean, (p->>'isPrimary')::boolean, false) DESC
3369
- LIMIT 1;
3370
- END IF;
3371
-
3372
- IF v_packing IS NOT NULL AND v_packing <> 'null'::jsonb THEN
3373
- v_vbn := COALESCE(
3374
- NULLIF(v_packing->'package'->>'vbnPackageCode', ''),
3375
- NULLIF(v_packing->>'vbnPackageCode', ''),
3376
- NULLIF(v_packing->>'packagingType', '')
3377
- );
3378
- v_custom := COALESCE(
3379
- NULLIF(v_packing->'package'->>'customPackageId', ''),
3380
- NULLIF(v_packing->>'packagingMaterialId', '')
3381
- );
3382
- IF v_vbn IS NOT NULL THEN
3383
- v_fust_key := 'vbn:' || v_vbn;
3384
- ELSIF v_custom IS NOT NULL THEN
3385
- v_fust_key := 'custom:' || v_custom;
3386
- END IF;
3387
-
3388
- v_per_package := GREATEST(1, COALESCE(
3389
- NULLIF(v_packing->>'piecesPerPackage', '')::int,
3390
- NULLIF(v_packing->>'unitsPerPackage', '')::int,
3391
- 1
3392
- ));
3393
- v_packages := CEIL(v_pieces::numeric / v_per_package)::int;
3394
- END IF;
3395
-
3396
- IF v_fust_key IS NOT NULL AND v_packages > 0 AND NOT EXISTS (SELECT 1 FROM public.order_fust_bookings b JOIN public.order_items oi ON oi.id = b.order_item_id WHERE oi.source_line_key = r.floriday_id) THEN
3397
- v_fust_id := public.voorraad_active_fust_id(v_fust_key, NULL);
3398
-
3399
- IF v_fust_id IS NULL THEN
3400
- SELECT user_id INTO v_owner FROM public.fust_types LIMIT 1;
3401
- IF v_owner IS NULL THEN
3402
- SELECT user_id INTO v_owner FROM public.trade_item_stock
3403
- WHERE connection_id = r.connection_id LIMIT 1;
3404
- END IF;
3405
- IF v_owner IS NOT NULL THEN
3406
- INSERT INTO public.fust_types (
3407
- user_id, connection_id, fust_key, name, source, vbn_code,
3408
- custom_package_id, in_use, used_by_count
3409
- ) VALUES (
3410
- v_owner, NULL, v_fust_key,
3411
- CASE WHEN v_vbn IS NOT NULL THEN 'VBN-fust ' || v_vbn
3412
- ELSE 'Eigen fust ' || left(COALESCE(v_custom, ''), 8) END,
3413
- CASE WHEN v_vbn IS NOT NULL THEN 'VBN' ELSE 'SUPPLIER' END,
3414
- CASE WHEN v_vbn ~ '^[0-9]+$' THEN v_vbn::int ELSE NULL END,
3415
- CASE WHEN v_vbn IS NULL THEN v_custom ELSE NULL END,
3416
- true, 1
3417
- )
3418
- ON CONFLICT (user_id, connection_id, fust_key) DO NOTHING
3419
- RETURNING id INTO v_fust_id;
3420
-
3421
- IF v_fust_id IS NULL THEN
3422
- v_fust_id := public.voorraad_active_fust_id(v_fust_key, NULL);
3423
- END IF;
3424
- END IF;
3425
- END IF;
3426
- END IF;
3427
-
3428
- IF v_fust_id IS NOT NULL AND public.voorraad_fust_voor_startmoment(v_fust_id, public.voorraad_lever_datum(r.lever_at)) THEN
3429
- v_packages := 0; -- leverdatum vóór het fust-startmoment: geen fustboeking
3430
- ELSIF v_fust_id IS NOT NULL THEN
3431
- -- Eerder geboekt aantal voor deze order (vorige versie) terugdraaien.
3432
- SELECT COALESCE(MAX((i.impact->>'fustPackages')::int), 0)
3433
- INTO v_prev_packages
3434
- FROM public.floriday_sales_order_ingest i
3435
- WHERE i.connection_id = r.connection_id
3436
- AND i.sales_order_id = r.floriday_id
3437
- AND (i.impact->>'fustTypeId') = v_fust_id::text;
3438
-
3439
- IF r.is_deleted OR COALESCE(r.status, '') IN ('CANCELLED', 'REJECTED', 'EXPIRED') THEN
3440
- v_fust_delta := v_prev_packages; -- alles teruggeven
3441
- v_packages := 0;
3442
- ELSE
3443
- v_fust_delta := -(v_packages - COALESCE(v_prev_packages, 0));
3444
- END IF;
3445
-
3446
- IF v_fust_delta <> 0 THEN
3447
- SELECT user_id INTO v_owner FROM public.fust_types WHERE id = v_fust_id;
3448
- INSERT INTO public.fust_stock_mutations (
3449
- user_id, fust_type_id, delta, reason, note, trade_item_key
3450
- ) VALUES (
3451
- v_owner, v_fust_id, v_fust_delta,
3452
- CASE WHEN v_fust_delta < 0 THEN 'sale' ELSE 'sale_cancelled' END,
3453
- 'Floriday-order ' || r.floriday_id || ' (' || v_pieces || ' stuks)',
3454
- v_key
3455
- );
3456
- END IF;
3457
- END IF;
3458
-
3459
- INSERT INTO public.floriday_sales_order_ingest (
3460
- connection_id, sales_order_id, order_version, order_status, impact
3461
- ) VALUES (
3462
- r.connection_id, r.floriday_id, r.version, r.status,
3463
- jsonb_build_object(
3464
- 'tradeItemId', v_key,
3465
- 'reservedDelta', v_delta,
3466
- 'fustTypeId', v_fust_id,
3467
- 'fustKey', v_fust_key,
3468
- 'fustPackages', v_packages,
3469
- 'voorStartmoment', public.voorraad_fust_voor_startmoment(v_fust_id, public.voorraad_lever_datum(r.lever_at))
3470
- )
3471
- )
3472
- ON CONFLICT (connection_id, sales_order_id, order_version) DO NOTHING;
3473
-
3474
- v_count := v_count + 1;
3475
- END LOOP;
3476
-
3477
- RETURN v_count;
3478
- END;
3479
- $function$
2999
+ BEGIN
3000
+ PERFORM public.voorraad_vereis('server', 'floriday_ingest_sales_orders');
3001
+ RAISE EXCEPTION 'Deze functie is uitgeschakeld: Floriday-orders worden verwerkt door de minuutronde.' USING ERRCODE = 'P0001';
3002
+ END $function$
3480
3003
  ;
3481
3004
  REVOKE ALL ON FUNCTION floriday_ingest_sales_orders(integer) FROM PUBLIC;
3482
3005
  REVOKE ALL ON FUNCTION floriday_ingest_sales_orders(integer) FROM anon;
@@ -3616,7 +3139,7 @@ BEGIN
3616
3139
 
3617
3140
  IF TG_OP = 'UPDATE' THEN
3618
3141
  IF NEW.status = 'pending' AND OLD.status IS DISTINCT FROM 'pending'
3619
- AND (OLD.status IN ('blocked_by_gate','skipped_week_closed') OR NOT COALESCE(v_found,false) OR v_lead IS FALSE) THEN
3142
+ AND (OLD.status IN ('blocked_by_gate','skipped_week_closed') OR NOT COALESCE(v_found,false) OR (v_lead IS FALSE AND NOT COALESCE(NEW.voorraad_mutatie, false))) THEN
3620
3143
  NEW.status := OLD.status;
3621
3144
  END IF;
3622
3145
  RETURN NEW;
@@ -3627,7 +3150,7 @@ BEGIN
3627
3150
  IF NEW.target_week IS NOT NULL AND (COALESCE(NEW.target_year, v_cy), NEW.target_week) < (v_cy, v_cw) THEN
3628
3151
  v_reason := 'Week ligt in het verleden'; v_status := 'skipped_week_closed';
3629
3152
  ELSIF NOT COALESCE(v_found,false) THEN v_reason := 'Geen voorraadsturing voor dit artikel';
3630
- ELSIF v_lead IS FALSE THEN v_reason := 'Floriday is leidend';
3153
+ ELSIF v_lead IS FALSE AND NOT COALESCE(NEW.voorraad_mutatie, false) THEN v_reason := 'Floriday is leidend';
3631
3154
  ELSIF v_lead IS TRUE AND v_forms IS NOT NULL AND (
3632
3155
  (NEW.target_week IS NULL AND COALESCE((v_forms->>'continuous')::boolean,false) = false)
3633
3156
  OR (NEW.target_week IS NOT NULL AND COALESCE((v_forms->>'weekly')::boolean,false) = false)) THEN
@@ -5017,24 +4540,294 @@ $function$
5017
4540
  REVOKE ALL ON FUNCTION product_type(uuid) FROM PUBLIC;
5018
4541
  REVOKE ALL ON FUNCTION product_type(uuid) FROM anon;
5019
4542
  GRANT EXECUTE ON FUNCTION product_type(uuid) TO service_role;
5020
- CREATE OR REPLACE FUNCTION public.promote_trade_item_key(p_old text, p_new text)
5021
- RETURNS void
4543
+ CREATE OR REPLACE FUNCTION public.productie_order_aanmaken(p_product_id uuid, p_aantal numeric, p_opmerking text DEFAULT NULL::text)
4544
+ RETURNS jsonb
5022
4545
  LANGUAGE plpgsql
5023
4546
  SECURITY DEFINER
5024
4547
  SET search_path TO 'public'
5025
4548
  AS $function$
5026
- DECLARE r RECORD; v_existing_id uuid;
5027
- BEGIN
5028
- PERFORM public.voorraad_vereis('ingelogd', 'promote_trade_item_key');
5029
- IF p_old IS NULL OR p_new IS NULL OR p_old = p_new THEN RETURN; END IF;
5030
- FOR r IN SELECT * FROM public.trade_item_stock WHERE trade_item_key = p_old FOR UPDATE LOOP
5031
- SELECT id INTO v_existing_id FROM public.trade_item_stock
5032
- WHERE connection_id = r.connection_id AND trade_item_key = p_new;
5033
- IF v_existing_id IS NULL THEN
5034
- UPDATE public.trade_item_stock SET trade_item_key = p_new, updated_at = now() WHERE id = r.id;
5035
- ELSE
5036
- -- Physical/min/incoming represent the same inventory; take the greater value.
5037
- -- Reservations from different sources are additive.
4549
+ DECLARE
4550
+ v_id uuid := gen_random_uuid(); v_nr text; v_pkey text; b record; m record; v_need numeric; v_rem numeric;
4551
+ v_key text; v_last_key text; v_last_pid uuid; v_last_name text; v_avail numeric; v_take numeric; v_planned numeric;
4552
+ v_keys text[]; v_tekort jsonb := '[]'::jsonb; d record; v_vrij numeric; v_naam text;
4553
+ BEGIN
4554
+ PERFORM public.voorraad_vereis('ingelogd', 'productie_order_aanmaken');
4555
+ IF NOT public.voorraad_can_write() THEN RAISE EXCEPTION 'Je hebt geen rechten om voorraad te wijzigen.' USING ERRCODE = '42501'; END IF;
4556
+ IF p_product_id IS NULL OR COALESCE(p_aantal, 0) <= 0 THEN RAISE EXCEPTION 'Kies een samenstelling en een aantal groter dan 0.' USING ERRCODE = 'P0001'; END IF;
4557
+ IF NOT EXISTS (SELECT 1 FROM public.product_bom_items x WHERE x.parent_product_id = p_product_id) THEN
4558
+ RAISE EXCEPTION 'Dit artikel heeft geen stuklijst; kies een samenstelling.' USING ERRCODE = 'P0001';
4559
+ END IF;
4560
+ v_pkey := public.ensure_trade_item_stock_row_key(p_product_id::text);
4561
+ IF v_pkey IS NULL THEN RAISE EXCEPTION 'Voor deze samenstelling is geen voorraadregel te maken.' USING ERRCODE = 'P0001'; END IF;
4562
+ v_nr := 'PO-' || lpad(nextval('public.productie_order_nummer_seq')::text, 6, '0');
4563
+ INSERT INTO public.productie_orders(id, nummer, product_id, trade_item_key, aantal, opmerking, aangemaakt_door)
4564
+ VALUES (v_id, v_nr, p_product_id, v_pkey, p_aantal, NULLIF(btrim(COALESCE(p_opmerking, '')), ''), auth.uid());
4565
+
4566
+ -- Stuklijst vastleggen; groepen vertaald zoals de productie dat deed (volgorde groep, beschikbaar), rest op het laatste lid.
4567
+ FOR b IN SELECT bi.*, p.product AS pnaam, g.id AS gid, g.name AS gnaam
4568
+ FROM public.product_bom_items bi
4569
+ LEFT JOIN public.products p ON p.id = bi.component_product_id
4570
+ LEFT JOIN public.product_groups g ON g.is_active = true AND (g.head_product_id = bi.component_product_id
4571
+ OR g.head_trade_item_floriday_id = bi.component_trade_item_floriday_id)
4572
+ WHERE bi.parent_product_id = p_product_id ORDER BY bi.sort_order NULLS LAST, bi.created_at LOOP
4573
+ v_need := COALESCE(b.quantity, 1) * p_aantal;
4574
+ IF b.gid IS NOT NULL THEN
4575
+ v_rem := v_need; v_last_key := NULL;
4576
+ FOR m IN SELECT gi.trade_item_id, p2.id AS pid, p2.product AS naam
4577
+ FROM public.product_group_items gi LEFT JOIN public.products p2 ON p2.id::text = gi.trade_item_id
4578
+ WHERE gi.product_group_id = b.gid ORDER BY gi.sort_order ASC NULLS LAST, gi.created_at ASC NULLS LAST LOOP
4579
+ v_key := public.ensure_trade_item_stock_row_key(COALESCE(m.pid::text, m.trade_item_id));
4580
+ IF v_key IS NULL THEN CONTINUE; END IF;
4581
+ v_last_key := v_key; v_last_pid := m.pid; v_last_name := COALESCE(m.naam, m.trade_item_id);
4582
+ IF v_rem <= 0 THEN CONTINUE; END IF;
4583
+ SELECT COALESCE(SUM(aantal), 0) INTO v_planned FROM public.productie_order_onderdelen WHERE productie_order_id = v_id AND trade_item_key = v_key;
4584
+ SELECT GREATEST(COALESCE(MAX(s.physical_stock), 0) - v_planned, 0) INTO v_avail FROM public.trade_item_stock s WHERE s.trade_item_key = v_key;
4585
+ v_take := LEAST(v_rem, COALESCE(v_avail, 0));
4586
+ IF v_take > 0 THEN
4587
+ INSERT INTO public.productie_order_onderdelen(productie_order_id, product_id, trade_item_key, naam, aantal)
4588
+ VALUES (v_id, m.pid, v_key, COALESCE(m.naam, m.trade_item_id), v_take);
4589
+ v_rem := v_rem - v_take;
4590
+ END IF;
4591
+ END LOOP;
4592
+ IF v_rem > 0 THEN
4593
+ IF v_last_key IS NULL THEN
4594
+ RAISE EXCEPTION 'De groep "%" in de stuklijst heeft geen leden met voorraad; pas de stuklijst aan.', COALESCE(b.gnaam, '?') USING ERRCODE = 'P0001';
4595
+ END IF;
4596
+ INSERT INTO public.productie_order_onderdelen(productie_order_id, product_id, trade_item_key, naam, aantal)
4597
+ VALUES (v_id, v_last_pid, v_last_key, v_last_name, v_rem);
4598
+ END IF;
4599
+ ELSE
4600
+ v_key := public.ensure_trade_item_stock_row_key(COALESCE(b.component_product_id::text, b.component_trade_item_floriday_id));
4601
+ IF v_key IS NULL THEN
4602
+ RAISE EXCEPTION 'Een onderdeel in de stuklijst heeft geen voorraadregel (%); pas de stuklijst aan.', COALESCE(b.pnaam, b.component_trade_item_floriday_id, '?') USING ERRCODE = 'P0001';
4603
+ END IF;
4604
+ INSERT INTO public.productie_order_onderdelen(productie_order_id, product_id, trade_item_key, naam, aantal)
4605
+ VALUES (v_id, b.component_product_id, v_key, COALESCE(b.pnaam, b.component_trade_item_floriday_id), v_need);
4606
+ END IF;
4607
+ END LOOP;
4608
+
4609
+ SELECT array_agg(DISTINCT x ORDER BY x) INTO v_keys FROM (SELECT trade_item_key x FROM public.productie_order_onderdelen WHERE productie_order_id = v_id UNION SELECT v_pkey) s;
4610
+ PERFORM public.voorraad_lock_sleutels(v_keys);
4611
+
4612
+ -- Tekort bepalen vóór de eigen reservering: vrij = fysiek − gereserveerd.
4613
+ FOR d IN SELECT trade_item_key, MAX(naam) AS naam, SUM(aantal) AS aantal FROM public.productie_order_onderdelen
4614
+ WHERE productie_order_id = v_id GROUP BY trade_item_key ORDER BY trade_item_key LOOP
4615
+ SELECT COALESCE(MAX(s.physical_stock), 0) - COALESCE(MAX(s.reserved_stock), 0) INTO v_vrij FROM public.trade_item_stock s WHERE s.trade_item_key = d.trade_item_key;
4616
+ IF d.aantal > GREATEST(COALESCE(v_vrij, 0), 0) THEN
4617
+ v_tekort := v_tekort || jsonb_build_object('trade_item_key', d.trade_item_key, 'naam', d.naam,
4618
+ 'tekort', d.aantal - GREATEST(COALESCE(v_vrij, 0), 0),
4619
+ 'tekst', 'Tekort: ' || trim(to_char(d.aantal - GREATEST(COALESCE(v_vrij, 0), 0), 'FM999999990.##')) || ' × ' || COALESCE(d.naam, d.trade_item_key));
4620
+ END IF;
4621
+ PERFORM public.voorraad_reservering_verschil(d.trade_item_key, d.aantal);
4622
+ END LOOP;
4623
+
4624
+ PERFORM set_config('stock.change_source', 'PRODUCTION', true);
4625
+ UPDATE public.trade_item_stock SET incoming_stock = COALESCE(incoming_stock, 0) + CEIL(p_aantal)::integer, change_source = 'PRODUCTION', updated_at = now()
4626
+ WHERE trade_item_key = v_pkey;
4627
+ INSERT INTO public.stock_mutation_log(trade_item_key, mutation_type, qty_delta, reason, created_by, production_batch_id)
4628
+ VALUES (v_pkey, 'production', 0, v_nr || ': productieorder aangemaakt, inkomend +' || p_aantal, auth.uid(), v_id);
4629
+
4630
+ RETURN jsonb_build_object('id', v_id, 'nummer', v_nr, 'status', 'open', 'tekorten', v_tekort);
4631
+ END $function$
4632
+ ;
4633
+ REVOKE ALL ON FUNCTION productie_order_aanmaken(uuid,numeric,text) FROM PUBLIC;
4634
+ REVOKE ALL ON FUNCTION productie_order_aanmaken(uuid,numeric,text) FROM anon;
4635
+ GRANT EXECUTE ON FUNCTION productie_order_aanmaken(uuid,numeric,text) TO authenticated;
4636
+ GRANT EXECUTE ON FUNCTION productie_order_aanmaken(uuid,numeric,text) TO service_role;
4637
+ CREATE OR REPLACE FUNCTION public.productie_order_annuleren(p_id uuid, p_reden text DEFAULT NULL::text)
4638
+ RETURNS jsonb
4639
+ LANGUAGE plpgsql
4640
+ SECURITY DEFINER
4641
+ SET search_path TO 'public'
4642
+ AS $function$
4643
+ DECLARE po record; d record; v_keys text[];
4644
+ BEGIN
4645
+ PERFORM public.voorraad_vereis('ingelogd', 'productie_order_annuleren');
4646
+ IF NOT public.voorraad_can_write() THEN RAISE EXCEPTION 'Je hebt geen rechten om voorraad te wijzigen.' USING ERRCODE = '42501'; END IF;
4647
+ SELECT * INTO po FROM public.productie_orders WHERE id = p_id FOR UPDATE;
4648
+ IF NOT FOUND THEN RAISE EXCEPTION 'Deze productieorder bestaat niet.' USING ERRCODE = 'P0001'; END IF;
4649
+ IF po.status = 'geannuleerd' THEN RETURN jsonb_build_object('status', 'al_geannuleerd', 'nummer', po.nummer); END IF;
4650
+ IF po.status <> 'open' THEN RAISE EXCEPTION 'Alleen een open productieorder kan worden geannuleerd.' USING ERRCODE = 'P0001'; END IF;
4651
+ SELECT array_agg(DISTINCT x ORDER BY x) INTO v_keys FROM (SELECT trade_item_key x FROM public.productie_order_onderdelen WHERE productie_order_id = p_id UNION SELECT po.trade_item_key) s;
4652
+ PERFORM public.voorraad_lock_sleutels(v_keys);
4653
+ UPDATE public.productie_orders SET status = 'geannuleerd', geannuleerd_door = auth.uid(), geannuleerd_op = now(),
4654
+ reden = NULLIF(btrim(COALESCE(p_reden, '')), '') WHERE id = p_id;
4655
+ FOR d IN SELECT trade_item_key, SUM(aantal) AS aantal FROM public.productie_order_onderdelen WHERE productie_order_id = p_id GROUP BY 1 ORDER BY 1 LOOP
4656
+ PERFORM public.voorraad_reservering_verschil(d.trade_item_key, -d.aantal);
4657
+ END LOOP;
4658
+ PERFORM set_config('stock.change_source', 'PRODUCTION', true);
4659
+ UPDATE public.trade_item_stock SET incoming_stock = GREATEST(COALESCE(incoming_stock, 0) - CEIL(po.aantal)::integer, 0), change_source = 'PRODUCTION', updated_at = now()
4660
+ WHERE trade_item_key = po.trade_item_key;
4661
+ INSERT INTO public.stock_mutation_log(trade_item_key, mutation_type, qty_delta, reason, created_by, production_batch_id)
4662
+ VALUES (po.trade_item_key, 'production', 0, po.nummer || ': geannuleerd, inkomend −' || po.aantal, auth.uid(), p_id);
4663
+ RETURN jsonb_build_object('status', 'geannuleerd', 'nummer', po.nummer);
4664
+ END $function$
4665
+ ;
4666
+ REVOKE ALL ON FUNCTION productie_order_annuleren(uuid,text) FROM PUBLIC;
4667
+ REVOKE ALL ON FUNCTION productie_order_annuleren(uuid,text) FROM anon;
4668
+ GRANT EXECUTE ON FUNCTION productie_order_annuleren(uuid,text) TO authenticated;
4669
+ GRANT EXECUTE ON FUNCTION productie_order_annuleren(uuid,text) TO service_role;
4670
+ CREATE OR REPLACE FUNCTION public.productie_order_geprint(p_id uuid)
4671
+ RETURNS jsonb
4672
+ LANGUAGE plpgsql
4673
+ SECURITY DEFINER
4674
+ SET search_path TO 'public'
4675
+ AS $function$
4676
+ DECLARE v_op timestamptz;
4677
+ BEGIN
4678
+ PERFORM public.voorraad_vereis('ingelogd', 'productie_order_geprint');
4679
+ UPDATE public.productie_orders SET geprint_op = now() WHERE id = p_id RETURNING geprint_op INTO v_op;
4680
+ IF v_op IS NULL THEN RAISE EXCEPTION 'Deze productieorder bestaat niet.' USING ERRCODE = 'P0001'; END IF;
4681
+ RETURN jsonb_build_object('geprint_op', v_op);
4682
+ END $function$
4683
+ ;
4684
+ REVOKE ALL ON FUNCTION productie_order_geprint(uuid) FROM PUBLIC;
4685
+ REVOKE ALL ON FUNCTION productie_order_geprint(uuid) FROM anon;
4686
+ GRANT EXECUTE ON FUNCTION productie_order_geprint(uuid) TO authenticated;
4687
+ GRANT EXECUTE ON FUNCTION productie_order_geprint(uuid) TO service_role;
4688
+ CREATE OR REPLACE FUNCTION public.productie_order_terugdraaien(p_id uuid, p_reden text)
4689
+ RETURNS jsonb
4690
+ LANGUAGE plpgsql
4691
+ SECURITY DEFINER
4692
+ SET search_path TO 'public'
4693
+ AS $function$
4694
+ DECLARE po record; d record; v_keys text[]; v_before numeric; v_after numeric; v_conn uuid;
4695
+ BEGIN
4696
+ PERFORM public.voorraad_vereis('ingelogd', 'productie_order_terugdraaien');
4697
+ IF NOT public.voorraad_is_beheerder() THEN
4698
+ RAISE EXCEPTION 'Alleen een beheerder kan een verwerkte productieorder terugdraaien.' USING ERRCODE = '42501';
4699
+ END IF;
4700
+ IF NULLIF(btrim(COALESCE(p_reden, '')), '') IS NULL THEN RAISE EXCEPTION 'Geef een reden op.' USING ERRCODE = 'P0001'; END IF;
4701
+ SELECT * INTO po FROM public.productie_orders WHERE id = p_id FOR UPDATE;
4702
+ IF NOT FOUND THEN RAISE EXCEPTION 'Deze productieorder bestaat niet.' USING ERRCODE = 'P0001'; END IF;
4703
+ IF po.status = 'teruggedraaid' THEN RETURN jsonb_build_object('status', 'al_teruggedraaid', 'nummer', po.nummer); END IF;
4704
+ IF po.status <> 'verwerkt' THEN RAISE EXCEPTION 'Alleen een verwerkte productieorder kan worden teruggedraaid.' USING ERRCODE = 'P0001'; END IF;
4705
+ SELECT array_agg(DISTINCT x ORDER BY x) INTO v_keys FROM (SELECT trade_item_key x FROM public.productie_order_onderdelen WHERE productie_order_id = p_id UNION SELECT po.trade_item_key) s;
4706
+ PERFORM public.voorraad_lock_sleutels(v_keys);
4707
+ PERFORM set_config('stock.change_source', 'PRODUCTION', true);
4708
+ FOR d IN SELECT trade_item_key, SUM(aantal) AS aantal FROM public.productie_order_onderdelen WHERE productie_order_id = p_id GROUP BY 1 ORDER BY 1 LOOP
4709
+ SELECT physical_stock, connection_id INTO v_before, v_conn FROM public.trade_item_stock WHERE trade_item_key = d.trade_item_key ORDER BY updated_at DESC NULLS LAST LIMIT 1 FOR UPDATE;
4710
+ v_before := COALESCE(v_before, 0); v_after := v_before + d.aantal;
4711
+ UPDATE public.trade_item_stock SET physical_stock = v_after::integer, change_source = 'PRODUCTION', updated_at = now() WHERE trade_item_key = d.trade_item_key;
4712
+ INSERT INTO public.stock_mutation_log(trade_item_key, connection_id, mutation_type, qty_delta, physical_before, physical_after, reason, created_by, production_batch_id)
4713
+ VALUES (d.trade_item_key, v_conn, 'production_reverse', d.aantal, v_before, v_after, po.nummer || ': terugdraaien onderdeel, reden ' || p_reden, auth.uid(), p_id);
4714
+ END LOOP;
4715
+ SELECT physical_stock, connection_id INTO v_before, v_conn FROM public.trade_item_stock WHERE trade_item_key = po.trade_item_key ORDER BY updated_at DESC NULLS LAST LIMIT 1 FOR UPDATE;
4716
+ v_before := COALESCE(v_before, 0); v_after := v_before - po.aantal;
4717
+ UPDATE public.trade_item_stock SET physical_stock = v_after::integer, change_source = 'PRODUCTION', updated_at = now() WHERE trade_item_key = po.trade_item_key;
4718
+ INSERT INTO public.stock_mutation_log(trade_item_key, connection_id, mutation_type, qty_delta, physical_before, physical_after, reason, created_by, production_batch_id)
4719
+ VALUES (po.trade_item_key, v_conn, 'production_reverse', -po.aantal, v_before, v_after, po.nummer || ': terugdraaien samenstelling, reden ' || p_reden, auth.uid(), p_id);
4720
+ UPDATE public.productie_orders SET status = 'teruggedraaid', teruggedraaid_door = auth.uid(), teruggedraaid_op = now(), reden = p_reden WHERE id = p_id;
4721
+ RETURN jsonb_build_object('status', 'teruggedraaid', 'nummer', po.nummer);
4722
+ END $function$
4723
+ ;
4724
+ REVOKE ALL ON FUNCTION productie_order_terugdraaien(uuid,text) FROM PUBLIC;
4725
+ REVOKE ALL ON FUNCTION productie_order_terugdraaien(uuid,text) FROM anon;
4726
+ GRANT EXECUTE ON FUNCTION productie_order_terugdraaien(uuid,text) TO authenticated;
4727
+ GRANT EXECUTE ON FUNCTION productie_order_terugdraaien(uuid,text) TO service_role;
4728
+ CREATE OR REPLACE FUNCTION public.productie_order_verwerken(p_id uuid)
4729
+ RETURNS jsonb
4730
+ LANGUAGE plpgsql
4731
+ SECURITY DEFINER
4732
+ SET search_path TO 'public'
4733
+ AS $function$
4734
+ DECLARE po record; d record; v_keys text[]; v_before numeric; v_after numeric; v_conn uuid; v_warn jsonb := '[]'::jsonb;
4735
+ BEGIN
4736
+ PERFORM public.voorraad_vereis('ingelogd', 'productie_order_verwerken');
4737
+ IF NOT public.voorraad_can_write() THEN RAISE EXCEPTION 'Je hebt geen rechten om voorraad te wijzigen.' USING ERRCODE = '42501'; END IF;
4738
+ SELECT * INTO po FROM public.productie_orders WHERE id = p_id FOR UPDATE;
4739
+ IF NOT FOUND THEN RAISE EXCEPTION 'Deze productieorder bestaat niet.' USING ERRCODE = 'P0001'; END IF;
4740
+ IF po.status = 'verwerkt' THEN RETURN jsonb_build_object('status', 'al_verwerkt', 'nummer', po.nummer, 'waarschuwingen', po.waarschuwingen); END IF;
4741
+ IF po.status <> 'open' THEN RAISE EXCEPTION 'Deze productieorder is % en kan niet meer worden verwerkt.', po.status USING ERRCODE = 'P0001'; END IF;
4742
+ SELECT array_agg(DISTINCT x ORDER BY x) INTO v_keys FROM (SELECT trade_item_key x FROM public.productie_order_onderdelen WHERE productie_order_id = p_id UNION SELECT po.trade_item_key) s;
4743
+ PERFORM public.voorraad_lock_sleutels(v_keys);
4744
+ PERFORM set_config('stock.change_source', 'PRODUCTION', true);
4745
+
4746
+ FOR d IN SELECT trade_item_key, MAX(naam) AS naam, SUM(aantal) AS aantal FROM public.productie_order_onderdelen
4747
+ WHERE productie_order_id = p_id GROUP BY trade_item_key ORDER BY trade_item_key LOOP
4748
+ SELECT physical_stock, connection_id INTO v_before, v_conn FROM public.trade_item_stock WHERE trade_item_key = d.trade_item_key ORDER BY updated_at DESC NULLS LAST LIMIT 1 FOR UPDATE;
4749
+ v_before := COALESCE(v_before, 0); v_after := v_before - d.aantal;
4750
+ UPDATE public.trade_item_stock
4751
+ SET physical_stock = v_after::integer, reserved_stock = GREATEST(COALESCE(reserved_stock, 0) - CEIL(d.aantal)::integer, 0),
4752
+ change_source = 'PRODUCTION', updated_at = now()
4753
+ WHERE trade_item_key = d.trade_item_key;
4754
+ INSERT INTO public.stock_mutation_log(trade_item_key, connection_id, mutation_type, qty_delta, physical_before, physical_after, reason, created_by, production_batch_id)
4755
+ VALUES (d.trade_item_key, v_conn, 'production_consume', -d.aantal, v_before, v_after, po.nummer || ': verbruik voor productie', auth.uid(), p_id);
4756
+ IF v_after < 0 THEN
4757
+ v_warn := v_warn || jsonb_build_object('trade_item_key', d.trade_item_key, 'naam', d.naam, 'stand', v_after,
4758
+ 'tekst', COALESCE(d.naam, d.trade_item_key) || ' staat nu op ' || trim(to_char(v_after, 'FM999999990.##')) || '; controleer de voorraad');
4759
+ END IF;
4760
+ END LOOP;
4761
+
4762
+ SELECT physical_stock, connection_id INTO v_before, v_conn FROM public.trade_item_stock WHERE trade_item_key = po.trade_item_key ORDER BY updated_at DESC NULLS LAST LIMIT 1 FOR UPDATE;
4763
+ v_before := COALESCE(v_before, 0); v_after := v_before + po.aantal;
4764
+ UPDATE public.trade_item_stock
4765
+ SET physical_stock = v_after::integer, incoming_stock = GREATEST(COALESCE(incoming_stock, 0) - CEIL(po.aantal)::integer, 0),
4766
+ change_source = 'PRODUCTION', updated_at = now()
4767
+ WHERE trade_item_key = po.trade_item_key;
4768
+ INSERT INTO public.stock_mutation_log(trade_item_key, connection_id, mutation_type, qty_delta, physical_before, physical_after, reason, created_by, production_batch_id)
4769
+ VALUES (po.trade_item_key, v_conn, 'production_output', po.aantal, v_before, v_after, po.nummer || ': geproduceerd', auth.uid(), p_id);
4770
+
4771
+ UPDATE public.productie_orders SET status = 'verwerkt', verwerkt_door = auth.uid(), verwerkt_op = now(), waarschuwingen = v_warn WHERE id = p_id;
4772
+ RETURN jsonb_build_object('status', 'verwerkt', 'nummer', po.nummer, 'waarschuwingen', v_warn);
4773
+ END $function$
4774
+ ;
4775
+ REVOKE ALL ON FUNCTION productie_order_verwerken(uuid) FROM PUBLIC;
4776
+ REVOKE ALL ON FUNCTION productie_order_verwerken(uuid) FROM anon;
4777
+ GRANT EXECUTE ON FUNCTION productie_order_verwerken(uuid) TO authenticated;
4778
+ GRANT EXECUTE ON FUNCTION productie_order_verwerken(uuid) TO service_role;
4779
+ CREATE OR REPLACE FUNCTION public.productie_orders_lijst(p_status text DEFAULT NULL::text, p_van timestamp with time zone DEFAULT NULL::timestamp with time zone, p_tot timestamp with time zone DEFAULT NULL::timestamp with time zone, p_zoek text DEFAULT NULL::text, p_limit integer DEFAULT 25, p_offset integer DEFAULT 0)
4780
+ RETURNS jsonb
4781
+ LANGUAGE plpgsql
4782
+ STABLE SECURITY DEFINER
4783
+ SET search_path TO 'public'
4784
+ AS $function$
4785
+ DECLARE v_rows jsonb; v_total bigint; v_z text := NULLIF(lower(btrim(COALESCE(p_zoek, ''))), '');
4786
+ v_st text[] := CASE WHEN p_status IS NULL OR p_status = '' THEN NULL
4787
+ WHEN p_status = 'geannuleerd' THEN ARRAY['geannuleerd','teruggedraaid'] ELSE ARRAY[p_status] END;
4788
+ BEGIN
4789
+ PERFORM public.voorraad_vereis('ingelogd', 'productie_orders_lijst');
4790
+ SELECT count(*) INTO v_total FROM public.productie_orders po LEFT JOIN public.products p ON p.id = po.product_id
4791
+ WHERE (v_st IS NULL OR po.status = ANY(v_st)) AND (p_van IS NULL OR po.aangemaakt_op >= p_van) AND (p_tot IS NULL OR po.aangemaakt_op < p_tot)
4792
+ AND (v_z IS NULL OR lower(po.nummer) LIKE '%' || v_z || '%' OR lower(COALESCE(p.product, '')) LIKE '%' || v_z || '%' OR lower(COALESCE(p.article_number, '')) LIKE '%' || v_z || '%');
4793
+ SELECT COALESCE(jsonb_agg(r ORDER BY (r->>'aangemaakt_op') DESC), '[]'::jsonb) INTO v_rows FROM (
4794
+ SELECT jsonb_build_object('id', po.id, 'nummer', po.nummer, 'product_id', po.product_id, 'naam', p.product, 'artikelcode', p.article_number,
4795
+ 'aantal', po.aantal, 'status', po.status, 'opmerking', po.opmerking, 'aangemaakt_op', po.aangemaakt_op, 'aangemaakt_door', po.aangemaakt_door,
4796
+ 'verwerkt_op', po.verwerkt_op, 'verwerkt_door', po.verwerkt_door, 'geannuleerd_op', po.geannuleerd_op,
4797
+ 'teruggedraaid_op', po.teruggedraaid_op, 'teruggedraaid_door', po.teruggedraaid_door, 'reden', po.reden,
4798
+ 'geprint_op', po.geprint_op, 'waarschuwingen', po.waarschuwingen,
4799
+ 'onderdelen', (SELECT COALESCE(jsonb_agg(jsonb_build_object('product_id', d.product_id, 'naam', d.naam, 'aantal', d.aantal) ORDER BY d.naam), '[]'::jsonb)
4800
+ FROM public.productie_order_onderdelen d WHERE d.productie_order_id = po.id)) AS r
4801
+ FROM public.productie_orders po LEFT JOIN public.products p ON p.id = po.product_id
4802
+ WHERE (v_st IS NULL OR po.status = ANY(v_st)) AND (p_van IS NULL OR po.aangemaakt_op >= p_van) AND (p_tot IS NULL OR po.aangemaakt_op < p_tot)
4803
+ AND (v_z IS NULL OR lower(po.nummer) LIKE '%' || v_z || '%' OR lower(COALESCE(p.product, '')) LIKE '%' || v_z || '%' OR lower(COALESCE(p.article_number, '')) LIKE '%' || v_z || '%')
4804
+ ORDER BY po.aangemaakt_op DESC
4805
+ LIMIT LEAST(GREATEST(COALESCE(p_limit, 25), 1), 500) OFFSET GREATEST(COALESCE(p_offset, 0), 0)) s;
4806
+ RETURN jsonb_build_object('rijen', v_rows, 'totaal', v_total);
4807
+ END $function$
4808
+ ;
4809
+ REVOKE ALL ON FUNCTION productie_orders_lijst(text,timestamp with time zone,timestamp with time zone,text,integer,integer) FROM PUBLIC;
4810
+ REVOKE ALL ON FUNCTION productie_orders_lijst(text,timestamp with time zone,timestamp with time zone,text,integer,integer) FROM anon;
4811
+ GRANT EXECUTE ON FUNCTION productie_orders_lijst(text,timestamp with time zone,timestamp with time zone,text,integer,integer) TO authenticated;
4812
+ GRANT EXECUTE ON FUNCTION productie_orders_lijst(text,timestamp with time zone,timestamp with time zone,text,integer,integer) TO service_role;
4813
+ CREATE OR REPLACE FUNCTION public.promote_trade_item_key(p_old text, p_new text)
4814
+ RETURNS void
4815
+ LANGUAGE plpgsql
4816
+ SECURITY DEFINER
4817
+ SET search_path TO 'public'
4818
+ AS $function$
4819
+ DECLARE r RECORD; v_existing_id uuid;
4820
+ BEGIN
4821
+ PERFORM public.voorraad_vereis('ingelogd', 'promote_trade_item_key');
4822
+ IF p_old IS NULL OR p_new IS NULL OR p_old = p_new THEN RETURN; END IF;
4823
+ FOR r IN SELECT * FROM public.trade_item_stock WHERE trade_item_key = p_old FOR UPDATE LOOP
4824
+ SELECT id INTO v_existing_id FROM public.trade_item_stock
4825
+ WHERE connection_id = r.connection_id AND trade_item_key = p_new;
4826
+ IF v_existing_id IS NULL THEN
4827
+ UPDATE public.trade_item_stock SET trade_item_key = p_new, updated_at = now() WHERE id = r.id;
4828
+ ELSE
4829
+ -- Physical/min/incoming represent the same inventory; take the greater value.
4830
+ -- Reservations from different sources are additive.
5038
4831
  UPDATE public.trade_item_stock
5039
4832
  SET physical_stock = GREATEST(physical_stock, r.physical_stock),
5040
4833
  minimum_stock = GREATEST(minimum_stock, r.minimum_stock),
@@ -5069,6 +4862,7 @@ BEGIN
5069
4862
  BEGIN PERFORM public.voorraad_ensure_defaults(); EXCEPTION WHEN others THEN NULL; END;
5070
4863
  IF current_setting('app.recomputing_reserved', true) = 'on' THEN RETURN; END IF;
5071
4864
  PERFORM set_config('app.recomputing_reserved', 'on', true);
4865
+ PERFORM pg_advisory_xact_lock(hashtext('voorraad_res:*')); -- één volledige herberekening tegelijk
5072
4866
 
5073
4867
  DROP TABLE IF EXISTS _reserved_keys;
5074
4868
  DROP TABLE IF EXISTS _reserved_demand;
@@ -5227,7 +5021,7 @@ BEGIN
5227
5021
  FROM public.trade_item_stock s2
5228
5022
  LEFT JOIN _reserved_demand d ON d.trade_item_key = s2.trade_item_key
5229
5023
  WHERE s2.id = s.id
5230
- AND s.reserved_stock IS DISTINCT FROM CEIL(COALESCE(d.qty, 0))::integer;
5024
+ AND s.reserved_stock IS DISTINCT FROM CEIL(COALESCE(d.qty, 0))::integer + public.voorraad_reservering_extra(s.trade_item_key);
5231
5025
 
5232
5026
  IF jsonb_array_length(v_skipped) > 0 THEN
5233
5027
  PERFORM public.logistics_trace(jsonb_build_object(
@@ -5322,16 +5116,17 @@ CREATE OR REPLACE FUNCTION public.recompute_reserved_for_order(p_order_id uuid)
5322
5116
  SECURITY DEFINER
5323
5117
  SET search_path TO 'public'
5324
5118
  AS $function$
5325
- DECLARE k text;
5119
+ DECLARE k text; v_keys text[];
5326
5120
  BEGIN
5327
5121
  PERFORM public.voorraad_vereis('ingelogd', 'recompute_reserved_for_order');
5328
- FOR k IN
5329
- SELECT DISTINCT public.voorraad_orderregel_sleutel(oi.product_id::text)
5330
- FROM logistics_active.order_items oi
5331
- WHERE oi.order_id = p_order_id AND oi.product_id IS NOT NULL
5332
- LOOP
5333
- IF k IS NOT NULL THEN PERFORM public.recompute_reserved_stock(k); END IF;
5334
- END LOOP;
5122
+ SELECT array_agg(DISTINCT x ORDER BY x) INTO v_keys FROM (
5123
+ SELECT public.voorraad_orderregel_sleutel(oi.product_id::text) AS x
5124
+ FROM logistics_active.order_items oi WHERE oi.order_id = p_order_id AND oi.product_id IS NOT NULL
5125
+ UNION SELECT a.trade_item_key FROM public.voorraad_orderregel_afboeking a WHERE a.order_id = p_order_id
5126
+ ) s WHERE x IS NOT NULL;
5127
+ IF v_keys IS NULL THEN RETURN; END IF;
5128
+ PERFORM public.voorraad_lock_sleutels(v_keys);
5129
+ FOREACH k IN ARRAY v_keys LOOP PERFORM public.recompute_reserved_stock(k); END LOOP;
5335
5130
  END $function$
5336
5131
  ;
5337
5132
  REVOKE ALL ON FUNCTION recompute_reserved_for_order(uuid) FROM PUBLIC;
@@ -5357,7 +5152,8 @@ BEGIN
5357
5152
  IF current_setting('app.recomputing_reserved', true) = 'on' THEN RETURN; END IF;
5358
5153
 
5359
5154
  v_key := public.logistics_reserved_key(p_trade_item_key);
5360
- IF public.logistics_reserved_fast_skip(p_trade_item_key, v_key) THEN RETURN; END IF;
5155
+ PERFORM public.voorraad_lock_sleutels(ARRAY[v_key]); -- SUM en UPDATE onder een lock per sleutel
5156
+ IF public.logistics_reserved_fast_skip(p_trade_item_key, v_key) AND public.voorraad_reservering_extra(v_key) = 0 THEN RETURN; END IF;
5361
5157
 
5362
5158
  SELECT array_agg(p.id) INTO v_products
5363
5159
  FROM public.products p
@@ -5435,7 +5231,7 @@ BEGIN
5435
5231
  change_source = 'RESERVATION_RECOMPUTE',
5436
5232
  updated_at = now()
5437
5233
  WHERE trade_item_key = v_key
5438
- AND reserved_stock IS DISTINCT FROM v_sum;
5234
+ AND reserved_stock IS DISTINCT FROM v_sum + public.voorraad_reservering_extra(v_key);
5439
5235
  END
5440
5236
  $function$
5441
5237
  ;
@@ -5654,128 +5450,11 @@ CREATE OR REPLACE FUNCTION public.reverse_production(p_parent_key text, p_quanti
5654
5450
  LANGUAGE plpgsql
5655
5451
  SECURITY DEFINER
5656
5452
  SET search_path TO 'public'
5657
- AS $function$
5658
- DECLARE
5659
- v_remaining numeric := COALESCE(p_quantity, 0);
5660
- v_batch record;
5661
- v_consume record;
5662
- v_scale numeric;
5663
- v_output_remaining numeric;
5664
- v_reverse_amount numeric;
5665
- v_before numeric;
5666
- v_after numeric;
5667
- v_connection_id uuid := p_connection_id;
5668
- v_uid uuid := COALESCE(p_user_id, auth.uid());
5669
- v_reversed jsonb := '[]'::jsonb;
5670
- v_batches_processed int := 0;
5671
- BEGIN
5672
- PERFORM public.voorraad_vereis('ingelogd', 'reverse_production');
5673
- IF v_remaining <= 0 OR p_parent_key IS NULL THEN
5674
- RETURN jsonb_build_object('ok', true, 'reversed', v_reversed);
5675
- END IF;
5676
-
5677
- -- Walk production batches most-recent first
5678
- FOR v_batch IN
5679
- SELECT
5680
- out_row.production_batch_id AS batch_id,
5681
- out_row.qty_delta AS produced_qty,
5682
- out_row.connection_id AS conn_id,
5683
- out_row.created_at AS produced_at,
5684
- COALESCE((
5685
- SELECT SUM(-rev.qty_delta)
5686
- FROM stock_mutation_log rev
5687
- WHERE rev.production_batch_id = out_row.production_batch_id
5688
- AND rev.parent_trade_item_key = p_parent_key
5689
- AND rev.mutation_type = 'production_reverse'
5690
- ), 0) AS already_reversed
5691
- FROM stock_mutation_log out_row
5692
- WHERE out_row.mutation_type = 'production_output'
5693
- AND out_row.parent_trade_item_key = p_parent_key
5694
- AND out_row.production_batch_id IS NOT NULL
5695
- ORDER BY out_row.created_at DESC
5696
- LOOP
5697
- v_output_remaining := v_batch.produced_qty - v_batch.already_reversed;
5698
- IF v_output_remaining <= 0 THEN CONTINUE; END IF;
5699
-
5700
- v_reverse_amount := LEAST(v_remaining, v_output_remaining);
5701
- v_scale := v_reverse_amount / v_batch.produced_qty;
5702
- v_connection_id := COALESCE(v_connection_id, v_batch.conn_id);
5703
-
5704
- -- Restore each consumed component pro-rata
5705
- FOR v_consume IN
5706
- SELECT trade_item_key, connection_id, -qty_delta AS consumed_qty
5707
- FROM stock_mutation_log
5708
- WHERE production_batch_id = v_batch.batch_id
5709
- AND mutation_type = 'production_consume'
5710
- AND parent_trade_item_key = p_parent_key
5711
- LOOP
5712
- DECLARE
5713
- v_restore numeric := v_consume.consumed_qty * v_scale;
5714
- v_prod_id uuid;
5715
- BEGIN
5716
- SELECT COALESCE(ts.physical_stock, 0) INTO v_before
5717
- FROM trade_item_stock ts WHERE ts.trade_item_key = v_consume.trade_item_key;
5718
- v_before := COALESCE(v_before, 0);
5719
- v_after := v_before + v_restore;
5720
-
5721
- PERFORM set_config('app.bom_consume_in_progress', 'on', true);
5722
- INSERT INTO trade_item_stock (connection_id, trade_item_key, physical_stock, user_id)
5723
- VALUES (COALESCE(v_consume.connection_id, v_connection_id), v_consume.trade_item_key, v_after::int, v_uid)
5724
- ON CONFLICT (connection_id, trade_item_key) DO UPDATE
5725
- SET physical_stock = EXCLUDED.physical_stock,
5726
- connection_id = COALESCE(trade_item_stock.connection_id, EXCLUDED.connection_id),
5727
- user_id = COALESCE(trade_item_stock.user_id, EXCLUDED.user_id),
5728
- updated_at = now();
5729
- PERFORM set_config('app.bom_consume_in_progress', 'off', true);
5730
-
5731
- IF v_consume.trade_item_key LIKE 'product:%' THEN
5732
- BEGIN v_prod_id := substring(v_consume.trade_item_key from 9)::uuid;
5733
- EXCEPTION WHEN others THEN v_prod_id := NULL; END;
5734
- ELSE
5735
- SELECT id INTO v_prod_id FROM products WHERE trade_item_key = v_consume.trade_item_key LIMIT 1;
5736
- END IF;
5737
- IF v_prod_id IS NOT NULL THEN
5738
- UPDATE products SET quantity = v_after::int, updated_at = now() WHERE id = v_prod_id;
5739
- END IF;
5740
-
5741
- INSERT INTO stock_mutation_log (
5742
- connection_id, trade_item_key, mutation_type, qty_delta,
5743
- physical_before, physical_after, reason, created_by,
5744
- production_batch_id, parent_trade_item_key
5745
- ) VALUES (
5746
- COALESCE(v_consume.connection_id, v_connection_id), v_consume.trade_item_key,
5747
- 'production_reverse', v_restore, v_before, v_after,
5748
- COALESCE(p_reason, 'Terugboeking productie ' || p_parent_key || ' (x' || v_reverse_amount || ')'),
5749
- v_uid, v_batch.batch_id, p_parent_key
5750
- );
5751
-
5752
- v_reversed := v_reversed || jsonb_build_object(
5753
- 'trade_item_key', v_consume.trade_item_key, 'restored', v_restore, 'new_stock', v_after,
5754
- 'batch_id', v_batch.batch_id);
5755
- END;
5756
- END LOOP;
5757
-
5758
- -- Log a reverse entry against the parent output as well, for audit
5759
- INSERT INTO stock_mutation_log (
5760
- connection_id, trade_item_key, mutation_type, qty_delta,
5761
- physical_before, physical_after, reason, created_by,
5762
- production_batch_id, parent_trade_item_key
5763
- ) VALUES (
5764
- v_connection_id, p_parent_key, 'production_reverse', -v_reverse_amount,
5765
- NULL, NULL,
5766
- COALESCE(p_reason, 'Terugdraaien productie (' || v_reverse_amount || ' stuks)'),
5767
- v_uid, v_batch.batch_id, p_parent_key
5768
- );
5769
-
5770
- v_remaining := v_remaining - v_reverse_amount;
5771
- v_batches_processed := v_batches_processed + 1;
5772
- EXIT WHEN v_remaining <= 0;
5773
- END LOOP;
5774
-
5775
- RETURN jsonb_build_object('ok', true, 'reversed', v_reversed,
5776
- 'batches_reversed', v_batches_processed, 'unreversed_qty', v_remaining);
5777
- END;
5778
- $function$
5453
+ AS $function$
5454
+ BEGIN
5455
+ PERFORM public.voorraad_vereis('ingelogd', 'reverse_production');
5456
+ RAISE EXCEPTION 'Gebruik Productieorder maken (of terugdraaien bij de productieorder).' USING ERRCODE = 'P0001';
5457
+ END $function$
5779
5458
  ;
5780
5459
  REVOKE ALL ON FUNCTION reverse_production(text,numeric,uuid,uuid,text) FROM PUBLIC;
5781
5460
  REVOKE ALL ON FUNCTION reverse_production(text,numeric,uuid,uuid,text) FROM anon;
@@ -6922,17 +6601,8 @@ CREATE OR REPLACE FUNCTION public.trg_orders_cancel_release_fn()
6922
6601
  SECURITY DEFINER
6923
6602
  SET search_path TO 'public'
6924
6603
  AS $function$
6925
- DECLARE
6926
- oi RECORD;
6927
- v_key text;
6928
6604
  BEGIN
6929
- IF NEW.status IN ('geannuleerd','cancelled','canceled','annulering')
6930
- AND OLD.status IS DISTINCT FROM NEW.status THEN
6931
- FOR oi IN SELECT id, product_id, quantity FROM logistics_active.order_items WHERE order_id = NEW.id LOOP
6932
- SELECT trade_item_key INTO v_key FROM public.products WHERE id = oi.product_id;
6933
- PERFORM public.apply_stock_reservation(v_key, -COALESCE(oi.quantity,0), NEW.id, oi.id, COALESCE(NEW.order_source,'manual'), 'order cancelled');
6934
- END LOOP;
6935
- END IF;
6605
+ -- Bewust leeg: vrijgeven gebeurt alleen door herberekenen (trg_orders_recompute_reserved), nooit door aftrekken.
6936
6606
  RETURN NEW;
6937
6607
  END $function$
6938
6608
  ;
@@ -7059,7 +6729,8 @@ CREATE OR REPLACE FUNCTION public.trg_orders_recompute_reserved_fn()
7059
6729
  SET search_path TO 'public'
7060
6730
  AS $function$
7061
6731
  BEGIN
7062
- IF TG_OP = 'UPDATE' AND NEW.status IS DISTINCT FROM OLD.status THEN
6732
+ IF TG_OP = 'UPDATE' AND (NEW.status IS DISTINCT FROM OLD.status OR NEW.deleted_at IS DISTINCT FROM OLD.deleted_at
6733
+ OR NEW.cancelled_at IS DISTINCT FROM OLD.cancelled_at) THEN
7063
6734
  PERFORM public.recompute_reserved_for_order(NEW.id);
7064
6735
  ELSIF TG_OP = 'DELETE' THEN
7065
6736
  PERFORM public.recompute_reserved_for_order(OLD.id);
@@ -7925,6 +7596,67 @@ REVOKE ALL ON FUNCTION voorraad_afboeken(uuid,text,numeric,text,text) FROM PUBLI
7925
7596
  REVOKE ALL ON FUNCTION voorraad_afboeken(uuid,text,numeric,text,text) FROM anon;
7926
7597
  GRANT EXECUTE ON FUNCTION voorraad_afboeken(uuid,text,numeric,text,text) TO authenticated;
7927
7598
  GRANT EXECUTE ON FUNCTION voorraad_afboeken(uuid,text,numeric,text,text) TO service_role;
7599
+ CREATE OR REPLACE FUNCTION public.voorraad_afboeken_kern(p_trade_item_key text, p_qty numeric, p_order_id uuid, p_order_item_id uuid, p_source text, p_reason text)
7600
+ RETURNS void
7601
+ LANGUAGE plpgsql
7602
+ SECURITY DEFINER
7603
+ SET search_path TO 'public'
7604
+ AS $function$
7605
+ DECLARE
7606
+ v_norm text := public.normalize_order_source(p_source);
7607
+ v_src text := CASE WHEN v_norm = 'FO' THEN 'FLORIDAY_ORDER' ELSE 'ORDER_DELIVERY' END;
7608
+ v_ctx record; v_key text; r public.trade_item_stock%ROWTYPE; v_after numeric; v_res_after numeric;
7609
+ BEGIN
7610
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_afboeken_kern');
7611
+ IF p_trade_item_key IS NULL OR COALESCE(p_qty,0) <= 0 THEN RETURN; END IF;
7612
+ SELECT * INTO v_ctx FROM public.get_stock_context(true);
7613
+ v_key := public.ensure_trade_item_stock_row_key(p_trade_item_key);
7614
+ IF v_ctx.connection_id IS NULL OR v_key IS NULL THEN
7615
+ INSERT INTO public.stock_mutation_log(trade_item_key, order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
7616
+ VALUES (p_trade_item_key, p_order_id, p_order_item_id, v_norm, 'consumption_skipped', p_qty,
7617
+ CASE WHEN v_ctx.connection_id IS NULL THEN 'geen actieve koppeling' ELSE 'geen voorraadrij' END || ' — ' || COALESCE(p_reason,''), auth.uid());
7618
+ RETURN;
7619
+ END IF;
7620
+ SELECT * INTO r FROM public.trade_item_stock WHERE connection_id = v_ctx.connection_id AND trade_item_key = v_key FOR UPDATE;
7621
+ IF r.id IS NULL THEN
7622
+ INSERT INTO public.stock_mutation_log(trade_item_key, order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
7623
+ VALUES (v_key, p_order_id, p_order_item_id, v_norm, 'consumption_skipped', p_qty, 'geen voorraadrij op de actieve koppeling — ' || COALESCE(p_reason,''), auth.uid());
7624
+ RETURN;
7625
+ END IF;
7626
+ v_after := COALESCE(r.physical_stock,0) - p_qty;
7627
+ v_res_after := GREATEST(COALESCE(r.reserved_stock,0) - p_qty, 0);
7628
+
7629
+ PERFORM set_config('stock.change_source', v_src, true);
7630
+ PERFORM set_config('stock.log_handled',
7631
+ COALESCE(NULLIF(current_setting('stock.log_handled', true),''), ';') || r.connection_id || '|' || v_key || '|' || v_after || '|' || v_res_after || ';', true);
7632
+
7633
+ UPDATE public.trade_item_stock
7634
+ SET physical_stock = v_after, reserved_stock = v_res_after, change_source = v_src, updated_at = now()
7635
+ WHERE id = r.id;
7636
+
7637
+ INSERT INTO public.stock_mutation_log(trade_item_key, connection_id, order_id, order_item_id, order_source,
7638
+ mutation_type, qty_delta, physical_before, physical_after, reserved_before, reserved_after, reason, created_by)
7639
+ VALUES (v_key, r.connection_id, p_order_id, p_order_item_id, v_norm, 'consumption', -p_qty,
7640
+ COALESCE(r.physical_stock,0), v_after, COALESCE(r.reserved_stock,0), v_res_after,
7641
+ COALESCE(p_reason,'afgeboekt') || ' (' || v_src || ')', auth.uid());
7642
+
7643
+ IF v_after < 0 THEN
7644
+ BEGIN
7645
+ INSERT INTO public.stock_integrity_alerts(tx_id, alert_kind, item_count, items)
7646
+ VALUES (-txid_current(), 'NEGATIVE_STOCK', 1, jsonb_build_array(jsonb_build_object(
7647
+ 'trade_item_key', v_key, 'connection_id', r.connection_id, 'physical_before', r.physical_stock,
7648
+ 'physical_after', v_after, 'order_id', p_order_id, 'order_item_id', p_order_item_id, 'change_source', v_src)))
7649
+ ON CONFLICT (tx_id) DO UPDATE SET item_count = public.stock_integrity_alerts.item_count + 1,
7650
+ items = public.stock_integrity_alerts.items || EXCLUDED.items, updated_at = now();
7651
+ EXCEPTION WHEN others THEN RAISE WARNING '[negative-stock-alert] %', SQLERRM;
7652
+ END;
7653
+ END IF;
7654
+ END $function$
7655
+ ;
7656
+ REVOKE ALL ON FUNCTION voorraad_afboeken_kern(text,numeric,uuid,uuid,text,text) FROM PUBLIC;
7657
+ REVOKE ALL ON FUNCTION voorraad_afboeken_kern(text,numeric,uuid,uuid,text,text) FROM anon;
7658
+ GRANT EXECUTE ON FUNCTION voorraad_afboeken_kern(text,numeric,uuid,uuid,text,text) TO authenticated;
7659
+ GRANT EXECUTE ON FUNCTION voorraad_afboeken_kern(text,numeric,uuid,uuid,text,text) TO service_role;
7928
7660
  CREATE OR REPLACE FUNCTION public.voorraad_alert_floriday_not_updated(p_key text, p_qty numeric, p_order_id uuid, p_order_item_id uuid)
7929
7661
  RETURNS void
7930
7662
  LANGUAGE plpgsql
@@ -9395,7 +9127,7 @@ CREATE OR REPLACE FUNCTION public.voorraad_defaults_versie()
9395
9127
  LANGUAGE sql
9396
9128
  IMMUTABLE
9397
9129
  SET search_path TO 'public'
9398
- AS $function$ SELECT 4 $function$
9130
+ AS $function$ SELECT 5 $function$
9399
9131
  ;
9400
9132
  REVOKE ALL ON FUNCTION voorraad_defaults_versie() FROM PUBLIC;
9401
9133
  REVOKE ALL ON FUNCTION voorraad_defaults_versie() FROM anon;
@@ -9469,6 +9201,36 @@ REVOKE ALL ON FUNCTION voorraad_eigen_velden_opslaan(uuid,jsonb,jsonb) FROM PUBL
9469
9201
  REVOKE ALL ON FUNCTION voorraad_eigen_velden_opslaan(uuid,jsonb,jsonb) FROM anon;
9470
9202
  GRANT EXECUTE ON FUNCTION voorraad_eigen_velden_opslaan(uuid,jsonb,jsonb) TO authenticated;
9471
9203
  GRANT EXECUTE ON FUNCTION voorraad_eigen_velden_opslaan(uuid,jsonb,jsonb) TO service_role;
9204
+ CREATE OR REPLACE FUNCTION public.voorraad_eigen_verkoop_mutatie(p_product_id uuid, p_aantal_verkocht numeric, p_referentie text)
9205
+ RETURNS jsonb
9206
+ LANGUAGE plpgsql
9207
+ SECURITY DEFINER
9208
+ SET search_path TO 'public'
9209
+ AS $function$
9210
+ DECLARE v_pid uuid; v_key text; v_delta integer;
9211
+ BEGIN
9212
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_eigen_verkoop_mutatie');
9213
+ IF p_product_id IS NULL OR NULLIF(btrim(COALESCE(p_referentie, '')), '') IS NULL THEN
9214
+ RAISE EXCEPTION 'Geef een artikel en een referentie op.' USING ERRCODE = 'P0001';
9215
+ END IF;
9216
+ IF EXISTS (SELECT 1 FROM public.orders o WHERE o.order_number::text = btrim(p_referentie)) THEN
9217
+ RETURN jsonb_build_object('status', 'via_order', 'tekst', 'Deze verkoop loopt via de ERP-order ' || btrim(p_referentie) || '.');
9218
+ END IF;
9219
+ BEGIN SELECT c.product_id, c.trade_item_key INTO v_pid, v_key FROM public.voorraad_canoniek_artikel(p_product_id::text) c LIMIT 1;
9220
+ EXCEPTION WHEN others THEN v_pid := NULL; END;
9221
+ v_pid := COALESCE(v_pid, p_product_id);
9222
+ IF NOT public.voorraad_floriday_leads_key(COALESCE(v_key, v_pid::text)) THEN
9223
+ RETURN jsonb_build_object('status', 'niet_floriday_leidend');
9224
+ END IF;
9225
+ v_delta := public.voorraad_floriday_mutatie_naar(v_pid, -CEIL(GREATEST(COALESCE(p_aantal_verkocht, 0), 0))::integer, NULL, NULL, btrim(p_referentie), 'verkoop zonder order');
9226
+ IF v_delta <> 0 THEN PERFORM public.voorraad_floriday_mutaties_klaarzetten(v_pid); END IF;
9227
+ RETURN jsonb_build_object('status', CASE WHEN v_delta = 0 THEN 'ongewijzigd' ELSE 'vastgelegd' END, 'verschil', v_delta);
9228
+ END $function$
9229
+ ;
9230
+ REVOKE ALL ON FUNCTION voorraad_eigen_verkoop_mutatie(uuid,numeric,text) FROM PUBLIC;
9231
+ REVOKE ALL ON FUNCTION voorraad_eigen_verkoop_mutatie(uuid,numeric,text) FROM anon;
9232
+ GRANT EXECUTE ON FUNCTION voorraad_eigen_verkoop_mutatie(uuid,numeric,text) TO authenticated;
9233
+ GRANT EXECUTE ON FUNCTION voorraad_eigen_verkoop_mutatie(uuid,numeric,text) TO service_role;
9472
9234
  CREATE OR REPLACE FUNCTION public.voorraad_ensure_defaults()
9473
9235
  RETURNS jsonb
9474
9236
  LANGUAGE plpgsql
@@ -9494,7 +9256,7 @@ BEGIN
9494
9256
  AND EXISTS (SELECT 1 FROM public.voorraad_load_carrier_types)
9495
9257
  AND EXISTS (SELECT 1 FROM public.voorraad_instellingen WHERE id)
9496
9258
  AND NOT EXISTS (SELECT 1 FROM public.fust_types WHERE saldo_bijhouden IS NULL)
9497
- AND (SELECT count(*) FROM public.module_runtime_config WHERE key LIKE 'voorraad_hook_enabled.%') >= 7
9259
+ AND (SELECT count(*) FROM public.module_runtime_config WHERE key LIKE 'voorraad_hook_enabled.%') >= 8
9498
9260
  AND v_job_ok THEN
9499
9261
  RETURN jsonb_build_object('ok', true, 'versie', v_versie, 'overgeslagen', true);
9500
9262
  END IF;
@@ -9536,7 +9298,7 @@ BEGIN
9536
9298
  ('voorraad_hook_enabled.logistics_tasks', 'standaard'),
9537
9299
  ('voorraad_hook_enabled.stock_reconcile', 'false'),
9538
9300
  ('voorraad_hook_enabled.continuous_stock', 'false'),
9539
- ('voorraad_hook_enabled.continuous_stock_pull', 'false')
9301
+ ('voorraad_hook_enabled.floriday_mutaties', 'true'), ('voorraad_hook_enabled.continuous_stock_pull', 'false')
9540
9302
  ON CONFLICT (key) DO NOTHING;
9541
9303
  GET DIAGNOSTICS n_sch = ROW_COUNT;
9542
9304
  PERFORM set_config('voorraad.schakelaar_defaults', '', true);
@@ -9857,6 +9619,48 @@ REVOKE ALL ON FUNCTION voorraad_floriday_dubbele_ids() FROM PUBLIC;
9857
9619
  REVOKE ALL ON FUNCTION voorraad_floriday_dubbele_ids() FROM anon;
9858
9620
  GRANT EXECUTE ON FUNCTION voorraad_floriday_dubbele_ids() TO authenticated;
9859
9621
  GRANT EXECUTE ON FUNCTION voorraad_floriday_dubbele_ids() TO service_role;
9622
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_inhalen_kandidaten()
9623
+ RETURNS TABLE(order_id uuid, order_nummer text, order_item_id uuid, product_id uuid, aantal integer, aangemaakt timestamp with time zone)
9624
+ LANGUAGE plpgsql
9625
+ STABLE SECURITY DEFINER
9626
+ SET search_path TO 'public'
9627
+ AS $function$
9628
+ BEGIN
9629
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_inhalen_kandidaten');
9630
+ IF NOT EXISTS (SELECT 1 FROM public.floriday_inventory_settings WHERE erp_leading = false) THEN RETURN; END IF;
9631
+ RETURN QUERY
9632
+ SELECT o.id, COALESCE(o.order_number::text, o.id::text), oi.id, oi.product_id, CEIL(oi.quantity)::integer, o.created_at
9633
+ FROM public.orders o JOIN public.order_items oi ON oi.order_id = o.id
9634
+ WHERE public.normalize_order_source(o.order_source) <> 'FO'
9635
+ AND oi.quarantined_at IS NULL
9636
+ AND NOT public.voorraad_order_geannuleerd(o.status, o.cancelled_at, o.deleted_at)
9637
+ AND lower(COALESCE(o.status, '')) NOT IN ('verzonden','shipped','geleverd','delivered','afgehandeld','completed')
9638
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_order_terugdraaiing t WHERE t.order_id = o.id)
9639
+ AND COALESCE(oi.quantity, 0) > 0 AND oi.product_id IS NOT NULL
9640
+ AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log l WHERE l.order_item_id = oi.id AND l.mutation_type = 'consumption_done')
9641
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_floriday_mutaties m WHERE m.order_item_id = oi.id)
9642
+ AND public.voorraad_floriday_leads_key(oi.product_id::text);
9643
+ END $function$
9644
+ ;
9645
+ REVOKE ALL ON FUNCTION voorraad_floriday_inhalen_kandidaten() FROM PUBLIC;
9646
+ REVOKE ALL ON FUNCTION voorraad_floriday_inhalen_kandidaten() FROM anon;
9647
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_inhalen_kandidaten() TO service_role;
9648
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_inhalen_open()
9649
+ RETURNS integer
9650
+ LANGUAGE plpgsql
9651
+ STABLE SECURITY DEFINER
9652
+ SET search_path TO 'public'
9653
+ AS $function$
9654
+ DECLARE n integer;
9655
+ BEGIN
9656
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_inhalen_open');
9657
+ SELECT count(*) INTO n FROM public.voorraad_floriday_inhalen_kandidaten() k WHERE k.aangemaakt < now() - interval '5 minutes';
9658
+ RETURN n;
9659
+ END $function$
9660
+ ;
9661
+ REVOKE ALL ON FUNCTION voorraad_floriday_inhalen_open() FROM PUBLIC;
9662
+ REVOKE ALL ON FUNCTION voorraad_floriday_inhalen_open() FROM anon;
9663
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_inhalen_open() TO service_role;
9860
9664
  CREATE OR REPLACE FUNCTION public.voorraad_floriday_klaarzetten_toegestaan()
9861
9665
  RETURNS boolean
9862
9666
  LANGUAGE plpgsql
@@ -9896,6 +9700,324 @@ $function$
9896
9700
  REVOKE ALL ON FUNCTION voorraad_floriday_leads_key(text) FROM PUBLIC;
9897
9701
  REVOKE ALL ON FUNCTION voorraad_floriday_leads_key(text) FROM anon;
9898
9702
  GRANT EXECUTE ON FUNCTION voorraad_floriday_leads_key(text) TO service_role;
9703
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_mutatie_doel(p_product_id uuid)
9704
+ RETURNS integer
9705
+ LANGUAGE plpgsql
9706
+ STABLE SECURITY DEFINER
9707
+ SET search_path TO 'public'
9708
+ AS $function$
9709
+ DECLARE v_f numeric; v_t timestamptz; v_u numeric;
9710
+ BEGIN
9711
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_floriday_mutatie_doel');
9712
+ SELECT st.number_of_pieces, st.opgehaald_at INTO v_f, v_t
9713
+ FROM public.voorraad_floriday_stand st
9714
+ JOIN public.floriday_inventory_settings s ON s.connection_id = st.connection_id AND s.floriday_trade_item_id::text = st.trade_item_id::text
9715
+ WHERE s.product_id = p_product_id AND s.erp_leading = false AND st.opgehaald_at >= now() - interval '2 minutes'
9716
+ ORDER BY st.opgehaald_at DESC LIMIT 1;
9717
+ IF v_t IS NULL OR NOT public.voorraad_floriday_orders_vers() THEN RETURN NULL; END IF;
9718
+ SELECT COALESCE(SUM(m.delta), 0) INTO v_u FROM public.voorraad_floriday_mutaties m
9719
+ WHERE m.product_id = p_product_id
9720
+ AND (m.status IN ('open','in_wachtrij','mislukt') OR (m.status = 'verzonden' AND m.verzonden_at >= v_t));
9721
+ RETURN GREATEST(v_f + v_u, 0)::integer;
9722
+ END $function$
9723
+ ;
9724
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutatie_doel(uuid) FROM PUBLIC;
9725
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutatie_doel(uuid) FROM anon;
9726
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutatie_doel(uuid) TO authenticated;
9727
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutatie_doel(uuid) TO service_role;
9728
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_mutatie_naar(p_product_id uuid, p_doel integer, p_order_id uuid, p_order_item_id uuid, p_referentie text, p_bron text)
9729
+ RETURNS integer
9730
+ LANGUAGE plpgsql
9731
+ SECURITY DEFINER
9732
+ SET search_path TO 'public'
9733
+ AS $function$
9734
+ DECLARE v_huidig integer; s record;
9735
+ BEGIN
9736
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_mutatie_naar');
9737
+ PERFORM pg_advisory_xact_lock(hashtext('voorraad_fm:' || p_product_id::text));
9738
+ SELECT COALESCE(SUM(delta), 0) INTO v_huidig FROM public.voorraad_floriday_mutaties
9739
+ WHERE product_id = p_product_id
9740
+ AND ((p_order_item_id IS NOT NULL AND order_item_id = p_order_item_id)
9741
+ OR (p_order_item_id IS NULL AND order_item_id IS NULL AND referentie = p_referentie));
9742
+ IF v_huidig = p_doel THEN RETURN 0; END IF;
9743
+ SELECT fis.connection_id, fis.floriday_trade_item_id INTO s FROM public.floriday_inventory_settings fis
9744
+ WHERE fis.product_id = p_product_id AND fis.erp_leading = false LIMIT 1;
9745
+ INSERT INTO public.voorraad_floriday_mutaties(connection_id, product_id, trade_item_id, order_id, order_item_id, referentie, delta, bron)
9746
+ VALUES (s.connection_id, p_product_id, NULLIF(s.floriday_trade_item_id::text, '')::uuid, p_order_id, p_order_item_id, p_referentie, p_doel - v_huidig, p_bron);
9747
+ RETURN p_doel - v_huidig;
9748
+ END $function$
9749
+ ;
9750
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutatie_naar(uuid,integer,uuid,uuid,text,text) FROM PUBLIC;
9751
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutatie_naar(uuid,integer,uuid,uuid,text,text) FROM anon;
9752
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutatie_naar(uuid,integer,uuid,uuid,text,text) TO authenticated;
9753
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutatie_naar(uuid,integer,uuid,uuid,text,text) TO service_role;
9754
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_mutaties_inhalen(p_uitvoeren boolean DEFAULT false)
9755
+ RETURNS jsonb
9756
+ LANGUAGE plpgsql
9757
+ SECURITY DEFINER
9758
+ SET search_path TO 'public'
9759
+ AS $function$
9760
+ DECLARE v_orders jsonb; v_art jsonb; r record; n_orders integer := 0; n_mut integer := 0; v_fouten jsonb := '[]'::jsonb;
9761
+ BEGIN
9762
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_mutaties_inhalen');
9763
+ SELECT COALESCE(jsonb_agg(jsonb_build_object('order', x.order_nummer, 'order_id', x.order_id, 'regels', x.regels) ORDER BY x.order_nummer), '[]'::jsonb)
9764
+ INTO v_orders FROM (
9765
+ SELECT k.order_id, k.order_nummer, jsonb_agg(jsonb_build_object('order_item_id', k.order_item_id, 'product_id', k.product_id, 'aantal', k.aantal)) regels
9766
+ FROM public.voorraad_floriday_inhalen_kandidaten() k GROUP BY 1, 2) x;
9767
+ SELECT COALESCE(jsonb_agg(jsonb_build_object('product_id', y.product_id, 'artikel', p.product, 'artikelcode', p.article_number,
9768
+ 'verschil', -y.som, 'orders', y.n) ORDER BY p.article_number), '[]'::jsonb)
9769
+ INTO v_art FROM (SELECT k.product_id, SUM(k.aantal) som, COUNT(DISTINCT k.order_id) n FROM public.voorraad_floriday_inhalen_kandidaten() k GROUP BY 1) y
9770
+ LEFT JOIN public.products p ON p.id = y.product_id;
9771
+ IF NOT p_uitvoeren THEN
9772
+ RETURN jsonb_build_object('uitgevoerd', false, 'orders', v_orders, 'per_artikel', v_art, 'aantal_orders', jsonb_array_length(v_orders));
9773
+ END IF;
9774
+ FOR r IN SELECT DISTINCT k.order_id FROM public.voorraad_floriday_inhalen_kandidaten() k ORDER BY 1 LOOP
9775
+ BEGIN
9776
+ n_mut := n_mut + public.voorraad_floriday_mutaties_order(r.order_id);
9777
+ n_orders := n_orders + 1;
9778
+ EXCEPTION WHEN others THEN
9779
+ RAISE WARNING 'voorraad_floriday_mutaties_inhalen: order % mislukt: %', r.order_id, SQLERRM;
9780
+ v_fouten := v_fouten || jsonb_build_object('order_id', r.order_id, 'fout', left(SQLERRM, 300));
9781
+ END;
9782
+ END LOOP;
9783
+ IF n_orders > 0 OR jsonb_array_length(v_fouten) > 0 THEN
9784
+ INSERT INTO public.voorraad_correcties(correctie, uitgevoerd_door, proef, aantallen, hash_ok)
9785
+ VALUES ('floriday_mutaties_inhalen', 'minuutronde', false,
9786
+ jsonb_build_object('orders', n_orders, 'mutaties', n_mut, 'per_artikel', v_art, 'fouten', v_fouten), true);
9787
+ END IF;
9788
+ RETURN jsonb_build_object('uitgevoerd', true, 'orders', n_orders, 'mutaties', n_mut, 'per_artikel', v_art, 'fouten', v_fouten);
9789
+ END $function$
9790
+ ;
9791
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_inhalen(boolean) FROM PUBLIC;
9792
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_inhalen(boolean) FROM anon;
9793
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutaties_inhalen(boolean) TO service_role;
9794
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_mutaties_klaarzetten(p_product_id uuid)
9795
+ RETURNS jsonb
9796
+ LANGUAGE plpgsql
9797
+ SECURITY DEFINER
9798
+ SET search_path TO 'public'
9799
+ AS $function$
9800
+ DECLARE s record; v_doel integer; v_q uuid; v_fout text;
9801
+ BEGIN
9802
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_mutaties_klaarzetten');
9803
+ PERFORM pg_advisory_xact_lock(hashtext('voorraad_fm:' || p_product_id::text));
9804
+ IF NOT EXISTS (SELECT 1 FROM public.voorraad_floriday_mutaties m WHERE m.product_id = p_product_id
9805
+ AND m.status IN ('open','mislukt') AND m.volgende_poging_at <= now() AND m.pogingen < 10) THEN
9806
+ RETURN jsonb_build_object('status', 'niets');
9807
+ END IF;
9808
+ SELECT fis.* INTO s FROM public.floriday_inventory_settings fis
9809
+ WHERE fis.product_id = p_product_id AND fis.erp_leading = false
9810
+ AND fis.connection_id = COALESCE((SELECT c.connection_id FROM public.get_active_floriday_context() c), fis.connection_id)
9811
+ LIMIT 1;
9812
+ IF NOT FOUND THEN
9813
+ UPDATE public.voorraad_floriday_mutaties SET status = 'bevestigd', bevestigd_at = now(),
9814
+ laatste_fout = 'Floriday is niet meer leidend; het ERP stuurt nu de hele voorraad'
9815
+ WHERE product_id = p_product_id AND status IN ('open','mislukt','in_wachtrij');
9816
+ RETURN jsonb_build_object('status', 'niet_meer_floriday_leidend');
9817
+ END IF;
9818
+ IF NOT public.voorraad_floriday_klaarzetten_toegestaan() THEN RETURN jsonb_build_object('status', 'schrijfstop'); END IF;
9819
+ v_doel := public.voorraad_floriday_mutatie_doel(p_product_id);
9820
+ IF v_doel IS NULL THEN RETURN jsonb_build_object('status', 'wacht_op_ophaalronde'); END IF;
9821
+ BEGIN
9822
+ INSERT INTO public.floriday_continuous_stock_queue(user_id, connection_id, trade_item_id, product_id, number_of_pieces,
9823
+ desired_available, status, next_attempt_at, voorraad_mutatie)
9824
+ VALUES (s.user_id, s.connection_id, s.floriday_trade_item_id::uuid, p_product_id, v_doel, v_doel > 0, 'pending', now(), true)
9825
+ RETURNING id INTO v_q;
9826
+ EXCEPTION WHEN others THEN v_fout := SQLERRM;
9827
+ END;
9828
+ IF v_q IS NULL THEN
9829
+ UPDATE public.voorraad_floriday_mutaties
9830
+ SET status = 'mislukt', pogingen = pogingen + 1, laatste_fout = left(COALESCE(v_fout, 'Niet klaargezet door de wachtrijbewaking'), 300),
9831
+ volgende_poging_at = now() + make_interval(mins => LEAST(power(2, pogingen)::int, 60))
9832
+ WHERE product_id = p_product_id AND status IN ('open','mislukt');
9833
+ RETURN jsonb_build_object('status', 'mislukt', 'fout', v_fout);
9834
+ END IF;
9835
+ UPDATE public.voorraad_floriday_mutaties SET status = 'in_wachtrij', queue_id = v_q, laatste_fout = NULL
9836
+ WHERE product_id = p_product_id AND status IN ('open','mislukt','in_wachtrij');
9837
+ RETURN jsonb_build_object('status', 'klaargezet', 'queue_id', v_q, 'aantal', v_doel);
9838
+ END $function$
9839
+ ;
9840
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_klaarzetten(uuid) FROM PUBLIC;
9841
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_klaarzetten(uuid) FROM anon;
9842
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutaties_klaarzetten(uuid) TO authenticated;
9843
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutaties_klaarzetten(uuid) TO service_role;
9844
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_mutaties_klaarzetten_alle()
9845
+ RETURNS jsonb
9846
+ LANGUAGE plpgsql
9847
+ SECURITY DEFINER
9848
+ SET search_path TO 'public'
9849
+ AS $function$
9850
+ DECLARE r record; v jsonb; n integer := 0; w integer := 0;
9851
+ BEGIN
9852
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_mutaties_klaarzetten_alle');
9853
+ FOR r IN SELECT DISTINCT product_id FROM public.voorraad_floriday_mutaties
9854
+ WHERE status IN ('open','mislukt') AND volgende_poging_at <= now() AND pogingen < 10 ORDER BY 1 LOOP
9855
+ v := public.voorraad_floriday_mutaties_klaarzetten(r.product_id);
9856
+ IF v->>'status' = 'klaargezet' THEN n := n + 1; ELSIF v->>'status' = 'wacht_op_ophaalronde' THEN w := w + 1; END IF;
9857
+ END LOOP;
9858
+ RETURN jsonb_build_object('klaargezet', n, 'wacht', w);
9859
+ END $function$
9860
+ ;
9861
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_klaarzetten_alle() FROM PUBLIC;
9862
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_klaarzetten_alle() FROM anon;
9863
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutaties_klaarzetten_alle() TO authenticated;
9864
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutaties_klaarzetten_alle() TO service_role;
9865
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_mutaties_order(p_order_id uuid)
9866
+ RETURNS integer
9867
+ LANGUAGE plpgsql
9868
+ SECURITY DEFINER
9869
+ SET search_path TO 'public'
9870
+ AS $function$
9871
+ DECLARE o record; v_actief boolean; l record; v_pid uuid; v_key text; v_doel integer; n integer := 0; v_ref text;
9872
+ BEGIN
9873
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_mutaties_order');
9874
+ IF p_order_id IS NULL OR NOT EXISTS (SELECT 1 FROM public.floriday_inventory_settings WHERE erp_leading = false) THEN RETURN 0; END IF;
9875
+ SELECT * INTO o FROM public.orders WHERE id = p_order_id;
9876
+ IF FOUND AND public.normalize_order_source(o.order_source) = 'FO' THEN RETURN 0; END IF; -- Floriday kent deze verkoop al
9877
+ v_actief := FOUND AND o.deleted_at IS NULL AND o.cancelled_at IS NULL
9878
+ AND lower(COALESCE(o.status, '')) NOT IN ('geannuleerd','cancelled','canceled','annulering')
9879
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_order_terugdraaiing t WHERE t.order_id = p_order_id);
9880
+ v_ref := COALESCE(o.order_number::text, p_order_id::text);
9881
+ FOR l IN
9882
+ SELECT oi.id, oi.product_id, oi.quantity, (oi.quarantined_at IS NULL) AS ok FROM public.order_items oi WHERE oi.order_id = p_order_id
9883
+ UNION ALL
9884
+ SELECT DISTINCT ON (m.order_item_id) m.order_item_id, m.product_id, 0, false FROM public.voorraad_floriday_mutaties m
9885
+ WHERE m.order_id = p_order_id AND m.order_item_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM public.order_items x WHERE x.id = m.order_item_id)
9886
+ LOOP
9887
+ IF l.product_id IS NULL THEN CONTINUE; END IF;
9888
+ v_pid := NULL;
9889
+ BEGIN SELECT c.product_id, c.trade_item_key INTO v_pid, v_key FROM public.voorraad_canoniek_artikel(l.product_id::text) c LIMIT 1;
9890
+ EXCEPTION WHEN others THEN v_pid := NULL; END;
9891
+ v_pid := COALESCE(v_pid, l.product_id);
9892
+ IF NOT public.voorraad_floriday_leads_key(COALESCE(v_key, v_pid::text))
9893
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_floriday_mutaties m WHERE m.order_item_id = l.id) THEN CONTINUE; END IF;
9894
+ IF NOT public.voorraad_floriday_leads_key(COALESCE(v_key, v_pid::text)) THEN CONTINUE; END IF; -- niet meer leidend: ERP stuurt alles
9895
+ v_doel := CASE WHEN v_actief AND l.ok THEN -CEIL(COALESCE(l.quantity, 0))::integer ELSE 0 END;
9896
+ IF public.voorraad_floriday_mutatie_naar(v_pid, v_doel, p_order_id, l.id, v_ref, 'order') <> 0 THEN n := n + 1; END IF;
9897
+ END LOOP;
9898
+ IF n > 0 THEN
9899
+ PERFORM public.voorraad_floriday_mutaties_klaarzetten(m.product_id)
9900
+ FROM (SELECT DISTINCT product_id FROM public.voorraad_floriday_mutaties WHERE order_id = p_order_id AND status = 'open') m;
9901
+ END IF;
9902
+ RETURN n;
9903
+ END $function$
9904
+ ;
9905
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_order(uuid) FROM PUBLIC;
9906
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_order(uuid) FROM anon;
9907
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutaties_order(uuid) TO authenticated;
9908
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutaties_order(uuid) TO service_role;
9909
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_mutaties_ronde()
9910
+ RETURNS jsonb
9911
+ LANGUAGE plpgsql
9912
+ SECURITY DEFINER
9913
+ SET search_path TO 'public'
9914
+ AS $function$
9915
+ DECLARE n_verz integer; n_bev integer; n_mis integer; v_lijst jsonb; v_conn uuid; v_inh jsonb;
9916
+ BEGIN
9917
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_mutaties_ronde');
9918
+ BEGIN
9919
+ v_inh := public.voorraad_floriday_mutaties_inhalen(true);
9920
+ EXCEPTION WHEN others THEN
9921
+ RAISE WARNING 'voorraad_floriday_mutaties_ronde: inhalen mislukt: %', SQLERRM;
9922
+ v_inh := jsonb_build_object('fout', left(SQLERRM, 300));
9923
+ END;
9924
+ UPDATE public.voorraad_floriday_mutaties m SET queue_id = q.superseded_by
9925
+ FROM public.floriday_continuous_stock_queue q
9926
+ WHERE m.status = 'in_wachtrij' AND m.queue_id = q.id AND q.superseded_by IS NOT NULL;
9927
+ UPDATE public.voorraad_floriday_mutaties m SET status = 'verzonden', verzonden_at = q.quantity_sent_at
9928
+ FROM public.floriday_continuous_stock_queue q
9929
+ WHERE m.status = 'in_wachtrij' AND m.queue_id = q.id AND q.status = 'sent' AND q.quantity_sent_at IS NOT NULL
9930
+ AND m.created_at <= COALESCE(q.locked_at, q.quantity_sent_at);
9931
+ GET DIAGNOSTICS n_verz = ROW_COUNT;
9932
+ UPDATE public.voorraad_floriday_mutaties m SET status = 'open'
9933
+ FROM public.floriday_continuous_stock_queue q
9934
+ WHERE m.status = 'in_wachtrij' AND m.queue_id = q.id AND q.status = 'sent';
9935
+ UPDATE public.voorraad_floriday_mutaties m
9936
+ SET status = 'mislukt', pogingen = m.pogingen + 1,
9937
+ laatste_fout = left(COALESCE(q.last_error, q.result_label, q.skip_reason, q.status), 300),
9938
+ volgende_poging_at = now() + make_interval(mins => LEAST(power(2, m.pogingen)::int, 60))
9939
+ FROM public.floriday_continuous_stock_queue q
9940
+ WHERE m.status = 'in_wachtrij' AND m.queue_id = q.id AND q.status NOT IN ('pending','processing','sent');
9941
+ GET DIAGNOSTICS n_mis = ROW_COUNT;
9942
+ UPDATE public.voorraad_floriday_mutaties m SET status = 'bevestigd', bevestigd_at = st.opgehaald_at
9943
+ FROM public.voorraad_floriday_stand st
9944
+ WHERE m.status = 'verzonden' AND st.connection_id = m.connection_id AND st.trade_item_id = m.trade_item_id
9945
+ AND st.opgehaald_at > m.verzonden_at;
9946
+ GET DIAGNOSTICS n_bev = ROW_COUNT;
9947
+
9948
+ SELECT c.connection_id INTO v_conn FROM public.get_active_floriday_context() c;
9949
+ SELECT COALESCE(jsonb_agg(DISTINCT jsonb_build_object('product_id', z.product_id, 'trade_item_id', z.trade_item_id)), '[]'::jsonb) INTO v_lijst
9950
+ FROM (
9951
+ SELECT m.product_id, m.trade_item_id FROM public.voorraad_floriday_mutaties m
9952
+ WHERE m.trade_item_id IS NOT NULL AND (m.status = 'verzonden'
9953
+ OR (m.status IN ('open','mislukt') AND m.volgende_poging_at <= now() AND m.pogingen < 10))
9954
+ UNION
9955
+ -- Floriday verkocht zelf: nieuwe FO-regel sinds de laatste stand → stand lezen zodat de eigen voorraad meteen volgt
9956
+ SELECT s.product_id, NULLIF(s.floriday_trade_item_id::text, '')::uuid
9957
+ FROM public.order_items oi JOIN public.orders o ON o.id = oi.order_id
9958
+ JOIN public.floriday_inventory_settings s ON s.product_id = oi.product_id AND s.erp_leading = false
9959
+ AND s.connection_id = COALESCE(v_conn, s.connection_id)
9960
+ LEFT JOIN public.voorraad_floriday_stand st ON st.connection_id = s.connection_id AND st.trade_item_id::text = s.floriday_trade_item_id::text
9961
+ WHERE public.normalize_order_source(o.order_source) = 'FO' AND o.deleted_at IS NULL
9962
+ AND GREATEST(o.updated_at, o.created_at) >= now() - interval '2 days'
9963
+ AND GREATEST(o.updated_at, o.created_at) > COALESCE(st.opgehaald_at, '-infinity'::timestamptz)
9964
+ AND s.floriday_trade_item_id IS NOT NULL AND s.floriday_trade_item_id::text ~* '^[0-9a-f-]{36}$'
9965
+ ) z;
9966
+ RETURN jsonb_build_object('connection_id', v_conn, 'verzonden', n_verz, 'bevestigd', n_bev, 'mislukt', n_mis, 'te_lezen', v_lijst, 'ingehaald', v_inh);
9967
+ END $function$
9968
+ ;
9969
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_ronde() FROM PUBLIC;
9970
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_ronde() FROM anon;
9971
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutaties_ronde() TO authenticated;
9972
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutaties_ronde() TO service_role;
9973
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_mutaties_trg()
9974
+ RETURNS trigger
9975
+ LANGUAGE plpgsql
9976
+ SECURITY DEFINER
9977
+ SET search_path TO 'public'
9978
+ AS $function$
9979
+ DECLARE v_order uuid;
9980
+ BEGIN
9981
+ -- Alleen geneste IF's: een orders-rij heeft geen order_id, en een gecombineerde expressie faalt daarop.
9982
+ IF TG_TABLE_NAME = 'orders' THEN
9983
+ IF TG_OP = 'DELETE' THEN v_order := OLD.id; ELSE v_order := NEW.id; END IF;
9984
+ PERFORM public.voorraad_floriday_mutaties_order(v_order);
9985
+ ELSE
9986
+ IF TG_OP = 'DELETE' THEN v_order := OLD.order_id; ELSE v_order := NEW.order_id; END IF;
9987
+ PERFORM public.voorraad_floriday_mutaties_order(v_order);
9988
+ IF TG_OP = 'UPDATE' THEN
9989
+ IF OLD.order_id IS DISTINCT FROM NEW.order_id THEN
9990
+ PERFORM public.voorraad_floriday_mutaties_order(OLD.order_id);
9991
+ END IF;
9992
+ END IF;
9993
+ END IF;
9994
+ RETURN NULL;
9995
+ EXCEPTION WHEN others THEN
9996
+ -- Nooit de orderwijziging blokkeren; de integriteitscontrole meldt een ontbrekende mutatie.
9997
+ RAISE WARNING '[voorraad-floriday-mutatie] order %: %', v_order, SQLERRM;
9998
+ RETURN NULL;
9999
+ END $function$
10000
+ ;
10001
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_trg() FROM PUBLIC;
10002
+ REVOKE ALL ON FUNCTION voorraad_floriday_mutaties_trg() FROM anon;
10003
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_mutaties_trg() TO service_role;
10004
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_orders_vers()
10005
+ RETURNS boolean
10006
+ LANGUAGE plpgsql
10007
+ STABLE SECURITY DEFINER
10008
+ SET search_path TO 'public'
10009
+ AS $function$
10010
+ BEGIN
10011
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_floriday_orders_vers');
10012
+ RETURN EXISTS (SELECT 1 FROM public.floriday_sync_state s
10013
+ WHERE s.entity_type = 'sales-orders' AND s.last_success_at >= now() - interval '2 minutes'
10014
+ AND s.connection_id = COALESCE((SELECT c.connection_id FROM public.get_active_floriday_context() c), s.connection_id));
10015
+ END $function$
10016
+ ;
10017
+ REVOKE ALL ON FUNCTION voorraad_floriday_orders_vers() FROM PUBLIC;
10018
+ REVOKE ALL ON FUNCTION voorraad_floriday_orders_vers() FROM anon;
10019
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_orders_vers() TO authenticated;
10020
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_orders_vers() TO service_role;
9899
10021
  CREATE OR REPLACE FUNCTION public.voorraad_floriday_product_van(p_floriday_id text)
9900
10022
  RETURNS uuid
9901
10023
  LANGUAGE plpgsql
@@ -9922,6 +10044,42 @@ REVOKE ALL ON FUNCTION voorraad_floriday_product_van(text) FROM PUBLIC;
9922
10044
  REVOKE ALL ON FUNCTION voorraad_floriday_product_van(text) FROM anon;
9923
10045
  GRANT EXECUTE ON FUNCTION voorraad_floriday_product_van(text) TO authenticated;
9924
10046
  GRANT EXECUTE ON FUNCTION voorraad_floriday_product_van(text) TO service_role;
10047
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_stand_vastleggen(p_connection_id uuid, p_jaar integer, p_week integer, p_rows jsonb)
10048
+ RETURNS integer
10049
+ LANGUAGE plpgsql
10050
+ SECURITY DEFINER
10051
+ SET search_path TO 'public'
10052
+ AS $function$
10053
+ DECLARE n integer; v_items jsonb; v_res jsonb;
10054
+ BEGIN
10055
+ PERFORM public.voorraad_vereis('server', 'voorraad_floriday_stand_vastleggen');
10056
+ INSERT INTO public.voorraad_floriday_stand(connection_id, trade_item_id, number_of_pieces, jaar, week, opgehaald_at)
10057
+ SELECT p_connection_id, (e->>'trade_item_id')::uuid, GREATEST(COALESCE((e->>'number_of_pieces')::numeric, 0), 0)::integer, p_jaar, p_week, now()
10058
+ FROM jsonb_array_elements(COALESCE(p_rows, '[]'::jsonb)) e
10059
+ WHERE e->>'trade_item_id' ~* '^[0-9a-f-]{36}$'
10060
+ ON CONFLICT (connection_id, trade_item_id) DO UPDATE
10061
+ SET number_of_pieces = EXCLUDED.number_of_pieces, jaar = EXCLUDED.jaar, week = EXCLUDED.week, opgehaald_at = EXCLUDED.opgehaald_at;
10062
+ GET DIAGNOSTICS n = ROW_COUNT;
10063
+ SELECT COALESCE(jsonb_agg(jsonb_build_object('trade_item_id', e->>'trade_item_id',
10064
+ 'stock', GREATEST(COALESCE((e->>'number_of_pieces')::numeric, 0), 0))), '[]'::jsonb)
10065
+ INTO v_items FROM jsonb_array_elements(COALESCE(p_rows, '[]'::jsonb)) e WHERE e->>'trade_item_id' ~* '^[0-9a-f-]{36}$';
10066
+ IF jsonb_array_length(v_items) > 0 THEN
10067
+ BEGIN
10068
+ v_res := public.floriday_follow_apply(p_connection_id, v_items);
10069
+ IF COALESCE((v_res->>'failed')::int, 0) > 0 THEN
10070
+ RAISE WARNING 'voorraad_floriday_stand_vastleggen: % artikelen niet bijgewerkt (zie follow_floriday_result)', v_res->>'failed';
10071
+ END IF;
10072
+ EXCEPTION WHEN others THEN
10073
+ RAISE WARNING 'voorraad_floriday_stand_vastleggen: volgen mislukt: %', SQLERRM;
10074
+ END;
10075
+ END IF;
10076
+ RETURN n;
10077
+ END $function$
10078
+ ;
10079
+ REVOKE ALL ON FUNCTION voorraad_floriday_stand_vastleggen(uuid,integer,integer,jsonb) FROM PUBLIC;
10080
+ REVOKE ALL ON FUNCTION voorraad_floriday_stand_vastleggen(uuid,integer,integer,jsonb) FROM anon;
10081
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_stand_vastleggen(uuid,integer,integer,jsonb) TO authenticated;
10082
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_stand_vastleggen(uuid,integer,integer,jsonb) TO service_role;
9925
10083
  CREATE OR REPLACE FUNCTION public.voorraad_floriday_versieronde_geslaagd(p_connection_id uuid)
9926
10084
  RETURNS boolean
9927
10085
  LANGUAGE plpgsql
@@ -10230,6 +10388,38 @@ REVOKE ALL ON FUNCTION voorraad_floriday_vervallen_verwerken(boolean) FROM PUBLI
10230
10388
  REVOKE ALL ON FUNCTION voorraad_floriday_vervallen_verwerken(boolean) FROM anon;
10231
10389
  GRANT EXECUTE ON FUNCTION voorraad_floriday_vervallen_verwerken(boolean) TO authenticated;
10232
10390
  GRANT EXECUTE ON FUNCTION voorraad_floriday_vervallen_verwerken(boolean) TO service_role;
10391
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_volg_waarde(p_product_id uuid, p_connection_id uuid, p_floriday numeric)
10392
+ RETURNS numeric
10393
+ LANGUAGE plpgsql
10394
+ STABLE SECURITY DEFINER
10395
+ SET search_path TO 'public'
10396
+ AS $function$
10397
+ DECLARE v_f numeric := p_floriday; v_t timestamptz; v_u numeric; v_r numeric;
10398
+ BEGIN
10399
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_floriday_volg_waarde');
10400
+ SELECT st.number_of_pieces, st.opgehaald_at INTO v_f, v_t
10401
+ FROM public.voorraad_floriday_stand st JOIN public.products p ON lower(p.floriday_trade_item_id) = lower(st.trade_item_id::text)
10402
+ WHERE p.id = p_product_id AND st.connection_id = p_connection_id AND st.opgehaald_at >= now() - interval '65 minutes'
10403
+ ORDER BY st.opgehaald_at DESC LIMIT 1;
10404
+ IF v_t IS NULL THEN v_f := p_floriday; END IF;
10405
+ SELECT COALESCE(SUM(m.delta), 0) INTO v_u FROM public.voorraad_floriday_mutaties m
10406
+ WHERE m.product_id = p_product_id
10407
+ AND (m.status IN ('open','in_wachtrij','mislukt') OR (m.status = 'verzonden' AND (v_t IS NULL OR m.verzonden_at >= v_t)));
10408
+ SELECT COALESCE(SUM(-x.som * x.rest), 0) INTO v_r FROM (
10409
+ SELECT SUM(m.delta) AS som,
10410
+ CASE WHEN EXISTS (SELECT 1 FROM public.stock_mutation_log l WHERE l.order_item_id = m.order_item_id AND l.mutation_type = 'consumption_done') THEN 0
10411
+ ELSE COALESCE((SELECT GREATEST(v.besteld - v.verzonden, 0) / NULLIF(v.besteld, 0) FROM public.voorraad_orderregel_verzending v
10412
+ WHERE v.order_item_id = m.order_item_id), 1) END AS rest
10413
+ FROM public.voorraad_floriday_mutaties m
10414
+ WHERE m.product_id = p_product_id AND m.order_item_id IS NOT NULL
10415
+ GROUP BY m.order_item_id) x;
10416
+ RETURN v_f + v_u + v_r;
10417
+ END $function$
10418
+ ;
10419
+ REVOKE ALL ON FUNCTION voorraad_floriday_volg_waarde(uuid,uuid,numeric) FROM PUBLIC;
10420
+ REVOKE ALL ON FUNCTION voorraad_floriday_volg_waarde(uuid,uuid,numeric) FROM anon;
10421
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_volg_waarde(uuid,uuid,numeric) TO authenticated;
10422
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_volg_waarde(uuid,uuid,numeric) TO service_role;
10233
10423
  CREATE OR REPLACE FUNCTION public.voorraad_floriday_volgt(p_product_id uuid)
10234
10424
  RETURNS boolean
10235
10425
  LANGUAGE plpgsql
@@ -10675,7 +10865,7 @@ BEGIN
10675
10865
  -- Zelfde regel als src/server-hooks/guard.ts (hookEnabled).
10676
10866
  SELECT lower(trim(value)) INTO v FROM public.module_runtime_config WHERE key = 'voorraad_hook_enabled.' || p_name;
10677
10867
  IF v IS NOT NULL AND v <> 'standaard' THEN RETURN v = 'true'; END IF;
10678
- IF p_name IN ('follow_floriday', 'floriday_versies') THEN RETURN true; END IF;
10868
+ IF p_name IN ('follow_floriday', 'floriday_versies', 'floriday_mutaties') THEN RETURN true; END IF;
10679
10869
  IF p_name IN ('floriday_base_supply', 'logistics_tasks') THEN
10680
10870
  RETURN EXISTS (SELECT 1 FROM public.floriday_inventory_settings WHERE erp_leading = true)
10681
10871
  OR EXISTS (SELECT 1 FROM public.floriday_inventory_settings WHERE erp_leading IS NULL AND stock_mode <> 'DISABLED');
@@ -10750,7 +10940,7 @@ CREATE OR REPLACE FUNCTION public.voorraad_install_check()
10750
10940
  STABLE SECURITY DEFINER
10751
10941
  SET search_path TO 'public'
10752
10942
  AS $function$
10753
- DECLARE v_f text[] := ARRAY['voorraad_foto_info','voorraad_lijst_v2','voorraad_lijst_v2_rijen','voorraad_kpi','voorraad_validatie_pagina','voorraad_groep_volgorde_opslaan','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'];
10943
+ DECLARE v_f text[] := ARRAY['voorraad_floriday_mutaties_inhalen','voorraad_floriday_inhalen_kandidaten','voorraad_floriday_inhalen_open','voorraad_floriday_stand_vastleggen','voorraad_integriteit_check','voorraad_integriteit_status','voorraad_push_wacht_status','voorraad_order_terugdraaien','consume_order_item_v2','voorraad_lock_sleutels','voorraad_reservering_extra','voorraad_floriday_mutaties_order','voorraad_floriday_mutaties_ronde','voorraad_floriday_mutaties_klaarzetten','voorraad_eigen_verkoop_mutatie','voorraad_floriday_volg_waarde','voorraad_floriday_mutatie_doel','voorraad_floriday_orders_vers','productie_order_aanmaken','productie_order_verwerken','productie_order_annuleren','productie_order_terugdraaien','productie_orders_lijst','voorraad_foto_info','voorraad_lijst_v2','voorraad_lijst_v2_rijen','voorraad_kpi','voorraad_validatie_pagina','voorraad_groep_volgorde_opslaan','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'];
10754
10944
  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'];
10755
10945
  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'];
10756
10946
  v_open text[] := ARRAY['voorraad_is_server','voorraad_vereis'];
@@ -10787,7 +10977,7 @@ BEGIN
10787
10977
  'policies_ontbrekend', (SELECT count(*) FROM jsonb_array_elements(v_pol) e WHERE NOT (e->>'aanwezig')::boolean),
10788
10978
  'nog_zonder_controle_volgende_ronde', (SELECT COALESCE(jsonb_agg(e->>'functie' ORDER BY e->>'functie'), '[]'::jsonb) FROM jsonb_array_elements(v_func) e WHERE e->>'soort' = 'sql' AND NOT COALESCE((e->>'interne_controle')::boolean, false) AND NOT (e->>'functie' = ANY(v_open))),
10789
10979
  'bewust_open', 'geen pickscherm/scanner-functies zonder inlog in Voorraad'),
10790
- 'correctie_1098', public.voorraad_correctie_status('1098'), 'voorraad_context', public.voorraad_stock_context_status(),
10980
+ 'integriteit', public.voorraad_integriteit_status(), 'floriday_mutaties_inhalen_open', public.voorraad_floriday_inhalen_open(), 'correctie_1098', public.voorraad_correctie_status('1098'), 'voorraad_context', public.voorraad_stock_context_status(),
10791
10981
  'functies', v_func, 'triggers', v_trig, 'policies', v_pol);
10792
10982
  END $function$
10793
10983
  ;
@@ -10832,6 +11022,127 @@ REVOKE ALL ON FUNCTION voorraad_instellingen_opslaan(text) FROM PUBLIC;
10832
11022
  REVOKE ALL ON FUNCTION voorraad_instellingen_opslaan(text) FROM anon;
10833
11023
  GRANT EXECUTE ON FUNCTION voorraad_instellingen_opslaan(text) TO authenticated;
10834
11024
  GRANT EXECUTE ON FUNCTION voorraad_instellingen_opslaan(text) TO service_role;
11025
+ CREATE OR REPLACE FUNCTION public.voorraad_integriteit_check()
11026
+ RETURNS jsonb
11027
+ LANGUAGE plpgsql
11028
+ SECURITY DEFINER
11029
+ SET search_path TO 'public'
11030
+ AS $function$
11031
+ DECLARE
11032
+ v_voor jsonb; v_res jsonb := '[]'::jsonb; v_fout text; v_neg jsonb; v_zonder jsonb; v_dubbel jsonb; v_meer jsonb;
11033
+ v_prod jsonb; v_mut jsonb; v_ontbr jsonb; v_push jsonb; v_samen jsonb; v_uit jsonb; v_id uuid;
11034
+ BEGIN
11035
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_integriteit_check');
11036
+ -- 1. Reservering: herberekenen in een subtransactie die altijd wordt teruggedraaid (verandert niets).
11037
+ SELECT jsonb_object_agg(s.id::text, COALESCE(s.reserved_stock, 0)) INTO v_voor FROM public.trade_item_stock s WHERE s.archived_at IS NULL;
11038
+ BEGIN
11039
+ PERFORM public.recompute_all_reserved_stock();
11040
+ SELECT COALESCE(jsonb_agg(jsonb_build_object('trade_item_key', s.trade_item_key, 'artikel', p.product, 'artikelcode', p.article_number,
11041
+ 'opgeslagen', (v_voor->>s.id::text)::int, 'berekend', COALESCE(s.reserved_stock, 0),
11042
+ 'voorstel', 'Gereserveerd van ' || (v_voor->>s.id::text) || ' naar ' || COALESCE(s.reserved_stock, 0) || ' (reservering herberekenen)')
11043
+ ORDER BY s.trade_item_key), '[]'::jsonb) INTO v_res
11044
+ FROM public.trade_item_stock s
11045
+ LEFT JOIN LATERAL (SELECT c.product_id FROM public.voorraad_canoniek_artikel(s.trade_item_key) c LIMIT 1) ca ON true
11046
+ LEFT JOIN public.products p ON p.id = ca.product_id
11047
+ WHERE s.archived_at IS NULL AND v_voor ? s.id::text AND COALESCE(s.reserved_stock, 0) IS DISTINCT FROM (v_voor->>s.id::text)::int;
11048
+ RAISE EXCEPTION 'voorraad_integriteit_terugdraaien';
11049
+ EXCEPTION WHEN others THEN
11050
+ IF SQLERRM <> 'voorraad_integriteit_terugdraaien' THEN v_fout := SQLERRM; v_res := '[]'::jsonb; END IF;
11051
+ END;
11052
+ -- 2. Negatieve voorraad
11053
+ SELECT COALESCE(jsonb_agg(jsonb_build_object('trade_item_key', s.trade_item_key, 'fysiek', s.physical_stock,
11054
+ 'voorstel', 'Tel de voorraad; staat nu op ' || s.physical_stock) ORDER BY s.physical_stock), '[]'::jsonb) INTO v_neg
11055
+ FROM public.trade_item_stock s WHERE s.archived_at IS NULL AND s.physical_stock < 0;
11056
+ -- 3. Verzonden zonder afboeking (laatste 90 dagen)
11057
+ SELECT COALESCE(jsonb_agg(x ORDER BY x->>'order'), '[]'::jsonb) INTO v_zonder FROM (
11058
+ SELECT jsonb_build_object('order', o.order_number, 'order_id', o.id, 'regel', oi.id, 'aantal', oi.quantity, 'status', o.status,
11059
+ 'voorstel', 'Controleer of deze regel echt vertrokken is; boek dan af via de order') AS x
11060
+ FROM public.orders o JOIN logistics_active.order_items oi ON oi.order_id = o.id
11061
+ WHERE lower(COALESCE(o.status, '')) IN ('verzonden','shipped','geleverd','delivered','afgehandeld','completed')
11062
+ AND o.updated_at >= now() - interval '90 days' AND o.deleted_at IS NULL AND COALESCE(oi.quantity, 0) > 0
11063
+ AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log l WHERE l.order_item_id = oi.id
11064
+ AND l.mutation_type IN ('consumption','consumption_done','consumption_skipped','consumption_blocked'))
11065
+ -- oudere afboekroute logde per order zonder regel
11066
+ AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log l WHERE l.order_id = o.id AND l.order_item_id IS NULL
11067
+ AND l.mutation_type IN ('consumption','consumption_done','consumption_skipped','consumption_blocked'))
11068
+ -- Floriday leidend: FO-regels worden bewust niet afgeboekt (Floriday trok ze al af)
11069
+ AND NOT (public.normalize_order_source(o.order_source) = 'FO' AND oi.product_id IS NOT NULL
11070
+ AND public.voorraad_floriday_leads_key(oi.product_id::text))
11071
+ LIMIT 200) z;
11072
+ -- 4. Dubbel afgeboekt: dezelfde regel en hetzelfde artikel via zending én via een andere route (van vóór deze versie)
11073
+ SELECT COALESCE(jsonb_agg(x), '[]'::jsonb) INTO v_dubbel FROM (
11074
+ SELECT jsonb_build_object('regel', l.order_item_id, 'order_id', max(l.order_id::text), 'trade_item_key', l.trade_item_key,
11075
+ 'afgeboekt', -sum(l.qty_delta), 'besteld', max(oi.quantity),
11076
+ 'voorstel', 'Mogelijk ' || (-sum(l.qty_delta) - COALESCE(max(oi.quantity), 0)) || ' te veel afgeboekt; na controle terugboeken via een voorraadcorrectie') AS x
11077
+ FROM public.stock_mutation_log l LEFT JOIN public.order_items oi ON oi.id = l.order_item_id
11078
+ WHERE l.mutation_type = 'consumption' AND l.order_item_id IS NOT NULL
11079
+ GROUP BY l.order_item_id, l.trade_item_key
11080
+ HAVING bool_or(l.reason LIKE 'shipment %') AND bool_or(l.reason NOT LIKE 'shipment %')
11081
+ LIMIT 200) z;
11082
+ SELECT COALESCE(jsonb_agg(jsonb_build_object('regel', v.order_item_id, 'order_id', v.order_id, 'besteld', v.besteld, 'verzonden', v.verzonden,
11083
+ 'voorstel', 'Meer verzonden dan besteld; controleer de order')), '[]'::jsonb) INTO v_meer
11084
+ FROM public.voorraad_orderregel_verzending v WHERE v.meer_dan_besteld;
11085
+ -- 5. Productie: onderdelen onder 0
11086
+ SELECT COALESCE(jsonb_agg(jsonb_build_object('nummer', po.nummer, 'waarschuwingen', po.waarschuwingen) ORDER BY po.verwerkt_op DESC), '[]'::jsonb) INTO v_prod
11087
+ FROM public.productie_orders po WHERE po.status = 'verwerkt' AND jsonb_array_length(po.waarschuwingen) > 0 AND po.verwerkt_op >= now() - interval '90 days';
11088
+ -- 6. Floriday-mutaties
11089
+ SELECT COALESCE(jsonb_agg(jsonb_build_object('product_id', m.product_id, 'referentie', m.referentie, 'verschil', m.delta, 'status', m.status,
11090
+ 'pogingen', m.pogingen, 'fout', m.laatste_fout)), '[]'::jsonb) INTO v_mut
11091
+ FROM public.voorraad_floriday_mutaties m
11092
+ WHERE m.status = 'mislukt' OR (m.status IN ('open','in_wachtrij') AND m.created_at < now() - interval '30 minutes');
11093
+ SELECT COALESCE(jsonb_agg(x), '[]'::jsonb) INTO v_ontbr FROM (
11094
+ SELECT jsonb_build_object('order', o.order_number, 'regel', oi.id, 'aantal', oi.quantity,
11095
+ 'voorstel', 'Eigen verkoop op een Floriday-leidend artikel zonder mutatie naar Floriday') AS x
11096
+ FROM public.orders o JOIN logistics_active.order_items oi ON oi.order_id = o.id
11097
+ WHERE public.normalize_order_source(o.order_source) <> 'FO' AND o.deleted_at IS NULL AND o.cancelled_at IS NULL
11098
+ AND o.created_at >= now() - interval '30 days' AND COALESCE(oi.quantity, 0) > 0
11099
+ AND oi.product_id IS NOT NULL AND public.voorraad_floriday_leads_key(oi.product_id::text)
11100
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_floriday_mutaties m WHERE m.order_item_id = oi.id)
11101
+ LIMIT 200) z;
11102
+ v_push := public.voorraad_push_wacht_status();
11103
+
11104
+ v_samen := jsonb_build_object(
11105
+ 'reservering_afwijkend', jsonb_array_length(v_res), 'negatief', jsonb_array_length(v_neg),
11106
+ 'verzonden_zonder_afboeking', jsonb_array_length(v_zonder), 'dubbel_afgeboekt', jsonb_array_length(v_dubbel),
11107
+ 'meer_dan_besteld', jsonb_array_length(v_meer), 'productie_onder_nul', jsonb_array_length(v_prod),
11108
+ 'floriday_mutaties_mislukt', jsonb_array_length(v_mut), 'floriday_mutatie_ontbreekt', jsonb_array_length(v_ontbr),
11109
+ 'push_staat_stil', COALESCE((v_push->>'wacht')::boolean, false), 'herberekenen_mislukt', v_fout);
11110
+ v_samen := v_samen || jsonb_build_object('ok',
11111
+ v_fout IS NULL AND jsonb_array_length(v_res) + jsonb_array_length(v_neg) + jsonb_array_length(v_zonder) + jsonb_array_length(v_dubbel)
11112
+ + jsonb_array_length(v_meer) + jsonb_array_length(v_prod) + jsonb_array_length(v_mut) + jsonb_array_length(v_ontbr) = 0
11113
+ AND NOT COALESCE((v_push->>'wacht')::boolean, false));
11114
+ v_uit := jsonb_build_object('reservering', v_res, 'negatief', v_neg, 'verzonden_zonder_afboeking', v_zonder, 'dubbel_afgeboekt', v_dubbel,
11115
+ 'meer_dan_besteld', v_meer, 'productie_onder_nul', v_prod, 'floriday_mutaties', v_mut, 'floriday_mutatie_ontbreekt', v_ontbr, 'push', v_push);
11116
+ INSERT INTO public.voorraad_integriteit_runs(samenvatting, uitkomst) VALUES (v_samen, v_uit) RETURNING id INTO v_id;
11117
+ DELETE FROM public.voorraad_integriteit_runs WHERE uitgevoerd_op < now() - interval '30 days'; -- bewaartermijn
11118
+ RETURN jsonb_build_object('id', v_id, 'samenvatting', v_samen, 'uitkomst', v_uit);
11119
+ END $function$
11120
+ ;
11121
+ REVOKE ALL ON FUNCTION voorraad_integriteit_check() FROM PUBLIC;
11122
+ REVOKE ALL ON FUNCTION voorraad_integriteit_check() FROM anon;
11123
+ GRANT EXECUTE ON FUNCTION voorraad_integriteit_check() TO authenticated;
11124
+ GRANT EXECUTE ON FUNCTION voorraad_integriteit_check() TO service_role;
11125
+ CREATE OR REPLACE FUNCTION public.voorraad_integriteit_status()
11126
+ RETURNS jsonb
11127
+ LANGUAGE plpgsql
11128
+ STABLE SECURITY DEFINER
11129
+ SET search_path TO 'public'
11130
+ AS $function$
11131
+ DECLARE r record;
11132
+ BEGIN
11133
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_integriteit_status');
11134
+ SELECT * INTO r FROM public.voorraad_integriteit_runs ORDER BY uitgevoerd_op DESC LIMIT 1;
11135
+ RETURN jsonb_build_object('laatste_controle', r.uitgevoerd_op, 'samenvatting', COALESCE(r.samenvatting, '{}'::jsonb),
11136
+ 'push', public.voorraad_push_wacht_status(),
11137
+ 'tekst', CASE WHEN r.id IS NULL THEN 'De voorraadcontrole heeft nog niet gedraaid.'
11138
+ WHEN COALESCE((r.samenvatting->>'ok')::boolean, false) THEN 'De voorraad klopt.'
11139
+ ELSE 'De voorraadcontrole vond afwijkingen; bekijk de uitkomst per artikel.' END);
11140
+ END $function$
11141
+ ;
11142
+ REVOKE ALL ON FUNCTION voorraad_integriteit_status() FROM PUBLIC;
11143
+ REVOKE ALL ON FUNCTION voorraad_integriteit_status() FROM anon;
11144
+ GRANT EXECUTE ON FUNCTION voorraad_integriteit_status() TO authenticated;
11145
+ GRANT EXECUTE ON FUNCTION voorraad_integriteit_status() TO service_role;
10835
11146
  CREATE OR REPLACE FUNCTION public.voorraad_is_beheerder()
10836
11147
  RETURNS boolean
10837
11148
  LANGUAGE plpgsql
@@ -11289,6 +11600,25 @@ REVOKE ALL ON FUNCTION voorraad_load_carriers() FROM PUBLIC;
11289
11600
  REVOKE ALL ON FUNCTION voorraad_load_carriers() FROM anon;
11290
11601
  GRANT EXECUTE ON FUNCTION voorraad_load_carriers() TO authenticated;
11291
11602
  GRANT EXECUTE ON FUNCTION voorraad_load_carriers() TO service_role;
11603
+ CREATE OR REPLACE FUNCTION public.voorraad_lock_sleutels(p_keys text[])
11604
+ RETURNS void
11605
+ LANGUAGE plpgsql
11606
+ SECURITY DEFINER
11607
+ SET search_path TO 'public'
11608
+ AS $function$
11609
+ DECLARE k text;
11610
+ BEGIN
11611
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_lock_sleutels');
11612
+ FOR k IN SELECT DISTINCT btrim(x) FROM unnest(COALESCE(p_keys, '{}'::text[])) x
11613
+ WHERE x IS NOT NULL AND btrim(x) <> '' ORDER BY 1 LOOP
11614
+ PERFORM pg_advisory_xact_lock(hashtext('voorraad_res:' || k));
11615
+ END LOOP;
11616
+ END $function$
11617
+ ;
11618
+ REVOKE ALL ON FUNCTION voorraad_lock_sleutels(text[]) FROM PUBLIC;
11619
+ REVOKE ALL ON FUNCTION voorraad_lock_sleutels(text[]) FROM anon;
11620
+ GRANT EXECUTE ON FUNCTION voorraad_lock_sleutels(text[]) TO authenticated;
11621
+ GRANT EXECUTE ON FUNCTION voorraad_lock_sleutels(text[]) TO service_role;
11292
11622
  CREATE OR REPLACE FUNCTION public.voorraad_mark_floriday_sold_before_switch(p_product_id uuid)
11293
11623
  RETURNS integer
11294
11624
  LANGUAGE plpgsql
@@ -11337,6 +11667,51 @@ GRANT EXECUTE ON FUNCTION voorraad_order_geannuleerd(text,timestamp with time zo
11337
11667
  REVOKE EXECUTE ON FUNCTION voorraad_order_geannuleerd(text,timestamp with time zone,timestamp with time zone) FROM anon;
11338
11668
  GRANT EXECUTE ON FUNCTION voorraad_order_geannuleerd(text,timestamp with time zone,timestamp with time zone) TO authenticated;
11339
11669
  GRANT EXECUTE ON FUNCTION voorraad_order_geannuleerd(text,timestamp with time zone,timestamp with time zone) TO service_role;
11670
+ CREATE OR REPLACE FUNCTION public.voorraad_order_terugdraaien(p_order_id uuid, p_reden text)
11671
+ RETURNS jsonb
11672
+ LANGUAGE plpgsql
11673
+ SECURITY DEFINER
11674
+ SET search_path TO 'public'
11675
+ AS $function$
11676
+ DECLARE v_nr text; v_keys text[]; k text; v_bestaat boolean;
11677
+ BEGIN
11678
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_order_terugdraaien');
11679
+ IF p_order_id IS NULL THEN RAISE EXCEPTION 'Geen order opgegeven.' USING ERRCODE = 'P0001'; END IF;
11680
+ SELECT o.order_number::text, true INTO v_nr, v_bestaat FROM public.orders o WHERE o.id = p_order_id FOR UPDATE;
11681
+ PERFORM 1 FROM public.order_items oi WHERE oi.order_id = p_order_id ORDER BY oi.id FOR UPDATE;
11682
+ IF EXISTS (SELECT 1 FROM public.voorraad_order_terugdraaiing t WHERE t.order_id = p_order_id) THEN
11683
+ RETURN jsonb_build_object('status', 'al_gedaan');
11684
+ END IF;
11685
+ IF NOT COALESCE(v_bestaat, false) THEN RAISE EXCEPTION 'Deze order bestaat niet (meer).' USING ERRCODE = 'P0001'; END IF;
11686
+ IF EXISTS (SELECT 1 FROM public.stock_mutation_log l JOIN public.order_items oi ON oi.id = l.order_item_id
11687
+ WHERE oi.order_id = p_order_id AND l.mutation_type = 'consumption')
11688
+ OR EXISTS (SELECT 1 FROM public.voorraad_orderregel_afboeking a WHERE a.order_id = p_order_id AND a.afgeboekt > 0) THEN
11689
+ RAISE EXCEPTION 'Deze order is al (deels) verzonden; corrigeer de voorraad via een voorraadcorrectie.' USING ERRCODE = 'P0001';
11690
+ END IF;
11691
+ SELECT array_agg(DISTINCT x ORDER BY x) INTO v_keys FROM (
11692
+ SELECT public.voorraad_orderregel_sleutel(oi.product_id::text) AS x FROM public.order_items oi
11693
+ WHERE oi.order_id = p_order_id AND oi.product_id IS NOT NULL) s WHERE x IS NOT NULL;
11694
+ INSERT INTO public.voorraad_order_terugdraaiing(order_id, order_number, reden, sleutels, door)
11695
+ VALUES (p_order_id, v_nr, NULLIF(btrim(COALESCE(p_reden, '')), ''), COALESCE(v_keys, '{}'), auth.uid())
11696
+ ON CONFLICT (order_id) DO NOTHING;
11697
+ IF v_keys IS NOT NULL THEN
11698
+ PERFORM public.voorraad_lock_sleutels(v_keys);
11699
+ FOREACH k IN ARRAY v_keys LOOP
11700
+ PERFORM public.recompute_reserved_stock(k);
11701
+ INSERT INTO public.stock_mutation_log(trade_item_key, order_id, mutation_type, qty_delta, reason, created_by)
11702
+ VALUES (k, p_order_id, 'reservation_recompute', 0,
11703
+ 'order ' || COALESCE(v_nr, p_order_id::text) || ': terugdraaien, reden ' || COALESCE(NULLIF(btrim(p_reden), ''), 'niet opgegeven'), auth.uid());
11704
+ END LOOP;
11705
+ END IF;
11706
+ BEGIN PERFORM public.voorraad_floriday_mutaties_order(p_order_id);
11707
+ EXCEPTION WHEN others THEN RAISE WARNING '[voorraad-floriday-mutatie] terugdraaien %: %', p_order_id, SQLERRM; END;
11708
+ RETURN jsonb_build_object('status', 'teruggedraaid', 'order_number', v_nr, 'sleutels', COALESCE(to_jsonb(v_keys), '[]'::jsonb));
11709
+ END $function$
11710
+ ;
11711
+ REVOKE ALL ON FUNCTION voorraad_order_terugdraaien(uuid,text) FROM PUBLIC;
11712
+ REVOKE ALL ON FUNCTION voorraad_order_terugdraaien(uuid,text) FROM anon;
11713
+ GRANT EXECUTE ON FUNCTION voorraad_order_terugdraaien(uuid,text) TO authenticated;
11714
+ GRANT EXECUTE ON FUNCTION voorraad_order_terugdraaien(uuid,text) TO service_role;
11340
11715
  CREATE OR REPLACE FUNCTION public.voorraad_order_vertrokken_voor(p_order_id uuid, p_moment timestamp with time zone)
11341
11716
  RETURNS boolean
11342
11717
  LANGUAGE plpgsql
@@ -11627,6 +12002,98 @@ GRANT EXECUTE ON FUNCTION voorraad_production_enabled() TO service_role;
11627
12002
  REVOKE EXECUTE ON FUNCTION voorraad_production_enabled() FROM anon;
11628
12003
  GRANT EXECUTE ON FUNCTION voorraad_production_enabled() TO authenticated;
11629
12004
  GRANT EXECUTE ON FUNCTION voorraad_production_enabled() TO service_role;
12005
+ CREATE OR REPLACE FUNCTION public.voorraad_push_wacht_status()
12006
+ RETURNS jsonb
12007
+ LANGUAGE plpgsql
12008
+ STABLE SECURITY DEFINER
12009
+ SET search_path TO 'public'
12010
+ AS $function$
12011
+ DECLARE v_sinds timestamptz; v_min integer; v_mis integer;
12012
+ BEGIN
12013
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_push_wacht_status');
12014
+ SELECT min(q.created_at) INTO v_sinds FROM public.floriday_continuous_stock_queue q
12015
+ WHERE q.status = 'pending' AND q.result_label LIKE 'Wacht op een verse ophaalronde%';
12016
+ SELECT count(*) INTO v_mis FROM public.voorraad_floriday_mutaties WHERE status = 'mislukt';
12017
+ v_min := CASE WHEN v_sinds IS NULL THEN 0 ELSE floor(extract(epoch FROM now() - v_sinds) / 60)::int END;
12018
+ RETURN jsonb_build_object(
12019
+ 'wacht', v_min > 30, 'sinds', v_sinds, 'minuten', v_min, 'mislukte_mutaties', v_mis,
12020
+ 'tekst', CASE WHEN v_min > 30 THEN 'De voorraad gaat al ' || v_min || ' minuten niet naar Floriday, omdat de nieuwste gegevens uit Floriday niet zijn opgehaald. Neem contact op met de beheerder als dit zo blijft.' END,
12021
+ 'tekst_mutaties', CASE WHEN v_mis > 0 THEN v_mis || ' eigen verkoop' || CASE WHEN v_mis = 1 THEN '' ELSE 'en' END || ' kon' || CASE WHEN v_mis = 1 THEN '' ELSE 'den' END || ' nog niet naar Floriday; dit wordt automatisch opnieuw geprobeerd.' END);
12022
+ END $function$
12023
+ ;
12024
+ REVOKE ALL ON FUNCTION voorraad_push_wacht_status() FROM PUBLIC;
12025
+ REVOKE ALL ON FUNCTION voorraad_push_wacht_status() FROM anon;
12026
+ GRANT EXECUTE ON FUNCTION voorraad_push_wacht_status() TO authenticated;
12027
+ GRANT EXECUTE ON FUNCTION voorraad_push_wacht_status() TO service_role;
12028
+ CREATE OR REPLACE FUNCTION public.voorraad_reservering_extra(p_key text)
12029
+ RETURNS integer
12030
+ LANGUAGE plpgsql
12031
+ STABLE SECURITY DEFINER
12032
+ SET search_path TO 'public'
12033
+ AS $function$
12034
+ DECLARE v_rest numeric := 0; v_prod numeric := 0;
12035
+ BEGIN
12036
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_reservering_extra');
12037
+ IF p_key IS NULL OR btrim(p_key) = '' THEN RETURN 0; END IF;
12038
+ SELECT COALESCE(SUM(GREATEST(a.besteld - a.afgeboekt, 0)), 0) INTO v_rest
12039
+ FROM public.voorraad_orderregel_afboeking a
12040
+ JOIN logistics_active.order_items oi ON oi.id = a.order_item_id
12041
+ JOIN public.orders o ON o.id = oi.order_id
12042
+ WHERE a.trade_item_key = p_key AND a.afgeboekt > 0 AND a.afgeboekt < a.besteld
12043
+ AND o.deleted_at IS NULL AND o.cancelled_at IS NULL
12044
+ AND lower(COALESCE(o.status, '')) NOT IN ('geannuleerd','cancelled','canceled','annulering','verzonden','shipped','geleverd','delivered','afgehandeld','completed','done')
12045
+ AND NOT EXISTS (SELECT 1 FROM public.voorraad_order_terugdraaiing t WHERE t.order_id = o.id)
12046
+ AND NOT (public.normalize_order_source(o.order_source) = 'FO' AND public.voorraad_floriday_leads_key(p_key));
12047
+ SELECT COALESCE(SUM(d.aantal), 0) INTO v_prod
12048
+ FROM public.productie_order_onderdelen d JOIN public.productie_orders p ON p.id = d.productie_order_id
12049
+ WHERE p.status = 'open' AND d.trade_item_key = p_key;
12050
+ RETURN CEIL(v_rest + v_prod)::integer;
12051
+ END $function$
12052
+ ;
12053
+ REVOKE ALL ON FUNCTION voorraad_reservering_extra(text) FROM PUBLIC;
12054
+ REVOKE ALL ON FUNCTION voorraad_reservering_extra(text) FROM anon;
12055
+ GRANT EXECUTE ON FUNCTION voorraad_reservering_extra(text) TO authenticated;
12056
+ GRANT EXECUTE ON FUNCTION voorraad_reservering_extra(text) TO service_role;
12057
+ CREATE OR REPLACE FUNCTION public.voorraad_reservering_extra_trg()
12058
+ RETURNS trigger
12059
+ LANGUAGE plpgsql
12060
+ SECURITY DEFINER
12061
+ SET search_path TO 'public'
12062
+ AS $function$
12063
+ BEGIN
12064
+ IF NEW.change_source = 'RESERVATION_RECOMPUTE'
12065
+ AND COALESCE(current_setting('stock.change_source', true), '') = 'RESERVATION_RECOMPUTE' THEN
12066
+ NEW.reserved_stock := COALESCE(NEW.reserved_stock, 0) + public.voorraad_reservering_extra(NEW.trade_item_key);
12067
+ END IF;
12068
+ RETURN NEW;
12069
+ EXCEPTION WHEN others THEN
12070
+ RAISE WARNING '[voorraad-reservering-extra] % (sleutel %)', SQLERRM, NEW.trade_item_key;
12071
+ RETURN NEW;
12072
+ END $function$
12073
+ ;
12074
+ REVOKE ALL ON FUNCTION voorraad_reservering_extra_trg() FROM PUBLIC;
12075
+ REVOKE ALL ON FUNCTION voorraad_reservering_extra_trg() FROM anon;
12076
+ GRANT EXECUTE ON FUNCTION voorraad_reservering_extra_trg() TO authenticated;
12077
+ GRANT EXECUTE ON FUNCTION voorraad_reservering_extra_trg() TO service_role;
12078
+ CREATE OR REPLACE FUNCTION public.voorraad_reservering_verschil(p_key text, p_delta numeric)
12079
+ RETURNS void
12080
+ LANGUAGE plpgsql
12081
+ SECURITY DEFINER
12082
+ SET search_path TO 'public'
12083
+ AS $function$
12084
+ BEGIN
12085
+ PERFORM public.voorraad_vereis('server', 'voorraad_reservering_verschil');
12086
+ IF p_key IS NULL OR COALESCE(p_delta, 0) = 0 THEN RETURN; END IF;
12087
+ PERFORM set_config('stock.change_source', 'PRODUCTION', true);
12088
+ UPDATE public.trade_item_stock
12089
+ SET reserved_stock = GREATEST(COALESCE(reserved_stock, 0) + CEIL(p_delta)::integer, 0), change_source = 'PRODUCTION', updated_at = now()
12090
+ WHERE trade_item_key = p_key;
12091
+ END $function$
12092
+ ;
12093
+ REVOKE ALL ON FUNCTION voorraad_reservering_verschil(text,numeric) FROM PUBLIC;
12094
+ REVOKE ALL ON FUNCTION voorraad_reservering_verschil(text,numeric) FROM anon;
12095
+ GRANT EXECUTE ON FUNCTION voorraad_reservering_verschil(text,numeric) TO authenticated;
12096
+ GRANT EXECUTE ON FUNCTION voorraad_reservering_verschil(text,numeric) TO service_role;
11630
12097
  CREATE OR REPLACE FUNCTION public.voorraad_reverse_order_mutations(p_order_id uuid, p_reason text)
11631
12098
  RETURNS jsonb
11632
12099
  LANGUAGE plpgsql
@@ -12411,6 +12878,8 @@ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'order_f
12411
12878
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'product_merge_log_pkey' AND conrelid = 'public.product_merge_log'::regclass) THEN ALTER TABLE public.product_merge_log ADD CONSTRAINT product_merge_log_pkey PRIMARY KEY (id); END IF; END $c$;
12412
12879
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'product_packings_pkey' AND conrelid = 'public.product_packings'::regclass) THEN ALTER TABLE public.product_packings ADD CONSTRAINT product_packings_pkey PRIMARY KEY (id); END IF; END $c$;
12413
12880
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'product_tray_stickers_pkey' AND conrelid = 'public.product_tray_stickers'::regclass) THEN ALTER TABLE public.product_tray_stickers ADD CONSTRAINT product_tray_stickers_pkey PRIMARY KEY (product_id); END IF; END $c$;
12881
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'productie_order_onderdelen_pkey' AND conrelid = 'public.productie_order_onderdelen'::regclass) THEN ALTER TABLE public.productie_order_onderdelen ADD CONSTRAINT productie_order_onderdelen_pkey PRIMARY KEY (id); END IF; END $c$;
12882
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'productie_orders_pkey' AND conrelid = 'public.productie_orders'::regclass) THEN ALTER TABLE public.productie_orders ADD CONSTRAINT productie_orders_pkey PRIMARY KEY (id); END IF; END $c$;
12414
12883
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'stock_import_chunks_pkey' AND conrelid = 'public.stock_import_chunks'::regclass) THEN ALTER TABLE public.stock_import_chunks ADD CONSTRAINT stock_import_chunks_pkey PRIMARY KEY (run_id, chunk_no); END IF; END $c$;
12415
12884
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'stock_import_rows_pkey' AND conrelid = 'public.stock_import_rows'::regclass) THEN ALTER TABLE public.stock_import_rows ADD CONSTRAINT stock_import_rows_pkey PRIMARY KEY (id); END IF; END $c$;
12416
12885
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'stock_import_runs_pkey' AND conrelid = 'public.stock_import_runs'::regclass) THEN ALTER TABLE public.stock_import_runs ADD CONSTRAINT stock_import_runs_pkey PRIMARY KEY (id); END IF; END $c$;
@@ -12428,18 +12897,24 @@ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraa
12428
12897
  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$;
12429
12898
  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$;
12430
12899
  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$;
12900
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_floriday_mutaties_pkey' AND conrelid = 'public.voorraad_floriday_mutaties'::regclass) THEN ALTER TABLE public.voorraad_floriday_mutaties ADD CONSTRAINT voorraad_floriday_mutaties_pkey PRIMARY KEY (id); END IF; END $c$;
12901
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_floriday_stand_pkey' AND conrelid = 'public.voorraad_floriday_stand'::regclass) THEN ALTER TABLE public.voorraad_floriday_stand ADD CONSTRAINT voorraad_floriday_stand_pkey PRIMARY KEY (connection_id, trade_item_id); END IF; END $c$;
12431
12902
  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$;
12432
12903
  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$;
12433
12904
  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$;
12434
12905
  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$;
12435
12906
  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$;
12436
12907
  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$;
12908
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_integriteit_runs_pkey' AND conrelid = 'public.voorraad_integriteit_runs'::regclass) THEN ALTER TABLE public.voorraad_integriteit_runs ADD CONSTRAINT voorraad_integriteit_runs_pkey PRIMARY KEY (id); END IF; END $c$;
12437
12909
  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$;
12438
12910
  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$;
12439
12911
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_load_carrier_types_pkey' AND conrelid = 'public.voorraad_load_carrier_types'::regclass) THEN ALTER TABLE public.voorraad_load_carrier_types ADD CONSTRAINT voorraad_load_carrier_types_pkey PRIMARY KEY (code); END IF; END $c$;
12440
12912
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_local_context_pkey' AND conrelid = 'public.voorraad_local_context'::regclass) THEN ALTER TABLE public.voorraad_local_context ADD CONSTRAINT voorraad_local_context_pkey PRIMARY KEY (id); END IF; END $c$;
12441
12913
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_logistics_event_log_pkey' AND conrelid = 'public.voorraad_logistics_event_log'::regclass) THEN ALTER TABLE public.voorraad_logistics_event_log ADD CONSTRAINT voorraad_logistics_event_log_pkey PRIMARY KEY (event_id); END IF; END $c$;
12442
12914
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_media_upload_failures_pkey' AND conrelid = 'public.voorraad_media_upload_failures'::regclass) THEN ALTER TABLE public.voorraad_media_upload_failures ADD CONSTRAINT voorraad_media_upload_failures_pkey PRIMARY KEY (id); END IF; END $c$;
12915
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_order_terugdraaiing_pkey' AND conrelid = 'public.voorraad_order_terugdraaiing'::regclass) THEN ALTER TABLE public.voorraad_order_terugdraaiing ADD CONSTRAINT voorraad_order_terugdraaiing_pkey PRIMARY KEY (order_id); END IF; END $c$;
12916
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_orderregel_afboeking_pkey' AND conrelid = 'public.voorraad_orderregel_afboeking'::regclass) THEN ALTER TABLE public.voorraad_orderregel_afboeking ADD CONSTRAINT voorraad_orderregel_afboeking_pkey PRIMARY KEY (order_item_id, trade_item_key); END IF; END $c$;
12917
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_orderregel_verzending_pkey' AND conrelid = 'public.voorraad_orderregel_verzending'::regclass) THEN ALTER TABLE public.voorraad_orderregel_verzending ADD CONSTRAINT voorraad_orderregel_verzending_pkey PRIMARY KEY (order_item_id); END IF; END $c$;
12443
12918
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_placeholder_codes_pkey' AND conrelid = 'public.voorraad_placeholder_codes'::regclass) THEN ALTER TABLE public.voorraad_placeholder_codes ADD CONSTRAINT voorraad_placeholder_codes_pkey PRIMARY KEY (code); END IF; END $c$;
12444
12919
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_publish_attempts_pkey' AND conrelid = 'public.voorraad_publish_attempts'::regclass) THEN ALTER TABLE public.voorraad_publish_attempts ADD CONSTRAINT voorraad_publish_attempts_pkey PRIMARY KEY (id); END IF; END $c$;
12445
12920
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'voorraad_publish_jobs_pkey' AND conrelid = 'public.voorraad_publish_jobs'::regclass) THEN ALTER TABLE public.voorraad_publish_jobs ADD CONSTRAINT voorraad_publish_jobs_pkey PRIMARY KEY (id); END IF; END $c$;
@@ -12453,6 +12928,7 @@ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'florida
12453
12928
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fust_types_user_id_connection_id_fust_key_key' AND conrelid = 'public.fust_types'::regclass) THEN ALTER TABLE public.fust_types ADD CONSTRAINT fust_types_user_id_connection_id_fust_key_key UNIQUE (user_id, connection_id, fust_key); END IF; END $c$;
12454
12929
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'inventory_stock_snapshots_unique' AND conrelid = 'public.inventory_stock_snapshots'::regclass) THEN ALTER TABLE public.inventory_stock_snapshots ADD CONSTRAINT inventory_stock_snapshots_unique UNIQUE (connection_id, snapshot_at, granularity); END IF; END $c$;
12455
12930
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'order_fust_bookings_order_item_id_fust_type_id_key' AND conrelid = 'public.order_fust_bookings'::regclass) THEN ALTER TABLE public.order_fust_bookings ADD CONSTRAINT order_fust_bookings_order_item_id_fust_type_id_key UNIQUE (order_item_id, fust_type_id); END IF; END $c$;
12931
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'productie_orders_nummer_key' AND conrelid = 'public.productie_orders'::regclass) THEN ALTER TABLE public.productie_orders ADD CONSTRAINT productie_orders_nummer_key UNIQUE (nummer); END IF; END $c$;
12456
12932
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'stock_import_rows_run_id_row_no_key' AND conrelid = 'public.stock_import_rows'::regclass) THEN ALTER TABLE public.stock_import_rows ADD CONSTRAINT stock_import_rows_run_id_row_no_key UNIQUE (run_id, row_no); END IF; END $c$;
12457
12933
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'stock_integrity_alerts_tx_id_key' AND conrelid = 'public.stock_integrity_alerts'::regclass) THEN ALTER TABLE public.stock_integrity_alerts ADD CONSTRAINT stock_integrity_alerts_tx_id_key UNIQUE (tx_id); END IF; END $c$;
12458
12934
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'trade_item_stock_connection_id_trade_item_key_key' AND conrelid = 'public.trade_item_stock'::regclass) THEN ALTER TABLE public.trade_item_stock ADD CONSTRAINT trade_item_stock_connection_id_trade_item_key_key UNIQUE (connection_id, trade_item_key); END IF; END $c$;
@@ -12492,6 +12968,12 @@ ALTER TABLE public.product_packings DROP CONSTRAINT IF EXISTS product_packings_p
12492
12968
  ALTER TABLE public.product_packings ADD CONSTRAINT product_packings_positive_check CHECK ((((pieces_per_package IS NULL) OR (pieces_per_package > 0)) AND ((packages_per_layer IS NULL) OR (packages_per_layer > 0)) AND ((layers_per_load_carrier IS NULL) OR (layers_per_load_carrier > 0))));
12493
12969
  ALTER TABLE public.product_packings DROP CONSTRAINT IF EXISTS product_packings_source_check;
12494
12970
  ALTER TABLE public.product_packings ADD CONSTRAINT product_packings_source_check CHECK ((source = ANY (ARRAY['floriday'::text, 'eigen'::text])));
12971
+ ALTER TABLE public.productie_order_onderdelen DROP CONSTRAINT IF EXISTS productie_order_onderdelen_aantal_check;
12972
+ ALTER TABLE public.productie_order_onderdelen ADD CONSTRAINT productie_order_onderdelen_aantal_check CHECK ((aantal >= (0)::numeric));
12973
+ ALTER TABLE public.productie_orders DROP CONSTRAINT IF EXISTS productie_orders_aantal_check;
12974
+ ALTER TABLE public.productie_orders ADD CONSTRAINT productie_orders_aantal_check CHECK ((aantal > (0)::numeric));
12975
+ ALTER TABLE public.productie_orders DROP CONSTRAINT IF EXISTS productie_orders_status_check;
12976
+ ALTER TABLE public.productie_orders ADD CONSTRAINT productie_orders_status_check CHECK ((status = ANY (ARRAY['open'::text, 'verwerkt'::text, 'geannuleerd'::text, 'teruggedraaid'::text])));
12495
12977
  ALTER TABLE public.stock_import_chunks DROP CONSTRAINT IF EXISTS stock_import_chunks_status_check;
12496
12978
  ALTER TABLE public.stock_import_chunks ADD CONSTRAINT stock_import_chunks_status_check CHECK ((status = ANY (ARRAY['wacht'::text, 'klaar'::text, 'ongedaan'::text])));
12497
12979
  ALTER TABLE public.stock_import_rows DROP CONSTRAINT IF EXISTS stock_import_rows_status_check;
@@ -12506,6 +12988,8 @@ ALTER TABLE public.trade_item_drafts DROP CONSTRAINT IF EXISTS trade_item_drafts
12506
12988
  ALTER TABLE public.trade_item_drafts ADD CONSTRAINT trade_item_drafts_link_status_chk CHECK ((floriday_link_status = ANY (ARRAY['unlinked'::text, 'linked'::text, 'waiting_for_data'::text, 'ready_to_publish'::text, 'publish_failed'::text, 'ambiguous_match'::text, 'published'::text])));
12507
12989
  ALTER TABLE public.voorraad_defaults_state DROP CONSTRAINT IF EXISTS voorraad_defaults_state_id_check;
12508
12990
  ALTER TABLE public.voorraad_defaults_state ADD CONSTRAINT voorraad_defaults_state_id_check CHECK (id);
12991
+ ALTER TABLE public.voorraad_floriday_mutaties DROP CONSTRAINT IF EXISTS voorraad_floriday_mutaties_status_check;
12992
+ ALTER TABLE public.voorraad_floriday_mutaties ADD CONSTRAINT voorraad_floriday_mutaties_status_check CHECK ((status = ANY (ARRAY['open'::text, 'in_wachtrij'::text, 'verzonden'::text, 'bevestigd'::text, 'mislukt'::text])));
12509
12993
  ALTER TABLE public.voorraad_instellingen DROP CONSTRAINT IF EXISTS voorraad_instellingen_id_check;
12510
12994
  ALTER TABLE public.voorraad_instellingen ADD CONSTRAINT voorraad_instellingen_id_check CHECK (id);
12511
12995
  ALTER TABLE public.voorraad_instellingen DROP CONSTRAINT IF EXISTS voorraad_instellingen_standaard_sturing_nieuwe_floriday_check;
@@ -12523,6 +13007,7 @@ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'order_f
12523
13007
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'product_packings_fust_type_id_fkey' AND conrelid = 'public.product_packings'::regclass) THEN ALTER TABLE public.product_packings ADD CONSTRAINT product_packings_fust_type_id_fkey FOREIGN KEY (fust_type_id) REFERENCES fust_types(id) ON DELETE SET NULL; END IF; END $c$;
12524
13008
  DO $g$ BEGIN IF to_regclass('public.products') IS NULL THEN RAISE NOTICE 'Voorraad: products (core) ontbreekt, controles overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'product_packings_product_id_fkey' AND conrelid = 'public.product_packings'::regclass) THEN ALTER TABLE public.product_packings ADD CONSTRAINT product_packings_product_id_fkey FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE; END IF; END $c$$q$; END IF; END $g$;
12525
13009
  DO $g$ BEGIN IF to_regclass('public.products') IS NULL THEN RAISE NOTICE 'Voorraad: products (core) ontbreekt, controles overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'product_tray_stickers_product_id_fkey' AND conrelid = 'public.product_tray_stickers'::regclass) THEN ALTER TABLE public.product_tray_stickers ADD CONSTRAINT product_tray_stickers_product_id_fkey FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE; END IF; END $c$$q$; END IF; END $g$;
13010
+ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'productie_order_onderdelen_productie_order_id_fkey' AND conrelid = 'public.productie_order_onderdelen'::regclass) THEN ALTER TABLE public.productie_order_onderdelen ADD CONSTRAINT productie_order_onderdelen_productie_order_id_fkey FOREIGN KEY (productie_order_id) REFERENCES productie_orders(id) ON DELETE RESTRICT; END IF; END $c$;
12526
13011
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'stock_import_chunks_run_id_fkey' AND conrelid = 'public.stock_import_chunks'::regclass) THEN ALTER TABLE public.stock_import_chunks ADD CONSTRAINT stock_import_chunks_run_id_fkey FOREIGN KEY (run_id) REFERENCES stock_import_runs(id) ON DELETE CASCADE; END IF; END $c$;
12527
13012
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'stock_import_rows_run_id_fkey' AND conrelid = 'public.stock_import_rows'::regclass) THEN ALTER TABLE public.stock_import_rows ADD CONSTRAINT stock_import_rows_run_id_fkey FOREIGN KEY (run_id) REFERENCES stock_import_runs(id) ON DELETE CASCADE; END IF; END $c$;
12528
13013
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'stock_import_runs_user_id_fkey' AND conrelid = 'public.stock_import_runs'::regclass) THEN ALTER TABLE public.stock_import_runs ADD CONSTRAINT stock_import_runs_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; END IF; END $c$;
@@ -12557,6 +13042,11 @@ CREATE INDEX IF NOT EXISTS idx_voorraad_publish_attempts_user_created ON public.
12557
13042
  CREATE INDEX IF NOT EXISTS inventory_stock_snapshots_time_idx ON public.inventory_stock_snapshots USING btree (connection_id, snapshot_at DESC);
12558
13043
  CREATE INDEX IF NOT EXISTS product_packings_primary_idx ON public.product_packings USING btree (product_id) WHERE is_primary;
12559
13044
  CREATE INDEX IF NOT EXISTS product_packings_product_idx ON public.product_packings USING btree (product_id);
13045
+ CREATE INDEX IF NOT EXISTS productie_order_onderdelen_key_idx ON public.productie_order_onderdelen USING btree (trade_item_key);
13046
+ CREATE INDEX IF NOT EXISTS productie_order_onderdelen_order_idx ON public.productie_order_onderdelen USING btree (productie_order_id);
13047
+ CREATE INDEX IF NOT EXISTS productie_orders_nummer_trgm_idx ON public.productie_orders USING btree (lower(nummer) text_pattern_ops);
13048
+ CREATE INDEX IF NOT EXISTS productie_orders_product_idx ON public.productie_orders USING btree (product_id);
13049
+ CREATE INDEX IF NOT EXISTS productie_orders_status_idx ON public.productie_orders USING btree (status, aangemaakt_op DESC);
12560
13050
  CREATE INDEX IF NOT EXISTS stock_import_rows_chunk_idx ON public.stock_import_rows USING btree (run_id, chunk_no);
12561
13051
  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);
12562
13052
  CREATE INDEX IF NOT EXISTS stock_import_runs_user_created_idx ON public.stock_import_runs USING btree (user_id, created_at DESC);
@@ -12564,8 +13054,14 @@ CREATE INDEX IF NOT EXISTS stock_integrity_alerts_open_idx ON public.stock_integ
12564
13054
  CREATE INDEX IF NOT EXISTS trade_item_prices_lookup ON public.trade_item_prices USING btree (trade_item_key, price_group_id);
12565
13055
  CREATE INDEX IF NOT EXISTS trade_item_stock_trade_item_key_idx ON public.trade_item_stock USING btree (trade_item_key);
12566
13056
  CREATE INDEX IF NOT EXISTS voorraad_floriday_alias_lower_idx ON public.voorraad_floriday_alias USING btree (lower(floriday_id));
13057
+ CREATE INDEX IF NOT EXISTS voorraad_floriday_mutaties_open_idx ON public.voorraad_floriday_mutaties USING btree (product_id) WHERE (status <> 'bevestigd'::text);
13058
+ CREATE INDEX IF NOT EXISTS voorraad_floriday_mutaties_ref_idx ON public.voorraad_floriday_mutaties USING btree (referentie);
13059
+ CREATE INDEX IF NOT EXISTS voorraad_floriday_mutaties_regel_idx ON public.voorraad_floriday_mutaties USING btree (order_item_id);
13060
+ CREATE INDEX IF NOT EXISTS voorraad_integriteit_runs_op_idx ON public.voorraad_integriteit_runs USING btree (uitgevoerd_op DESC);
12567
13061
  CREATE INDEX IF NOT EXISTS voorraad_koppel_log_item_idx ON public.voorraad_koppel_log USING btree (order_item_id);
12568
13062
  CREATE INDEX IF NOT EXISTS voorraad_koppel_log_run_idx ON public.voorraad_koppel_log USING btree (run_id);
13063
+ CREATE INDEX IF NOT EXISTS voorraad_orderregel_afboeking_key_idx ON public.voorraad_orderregel_afboeking USING btree (trade_item_key);
13064
+ CREATE INDEX IF NOT EXISTS voorraad_orderregel_afboeking_order_idx ON public.voorraad_orderregel_afboeking USING btree (order_id);
12569
13065
  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]));
12570
13066
  CREATE UNIQUE INDEX IF NOT EXISTS fust_startmoment_uniek ON public.fust_startmoment USING btree (COALESCE(fust_type_id, '00000000-0000-0000-0000-000000000000'::uuid));
12571
13067
  CREATE UNIQUE INDEX IF NOT EXISTS fust_types_fust_key_active_uidx ON public.fust_types USING btree (fust_key) WHERE ((connection_id IS NULL) AND (merged_into IS NULL));
@@ -12662,6 +13158,14 @@ ALTER TABLE public.product_tray_stickers ENABLE ROW LEVEL SECURITY;
12662
13158
  REVOKE ALL ON public.product_tray_stickers FROM anon;
12663
13159
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_tray_stickers TO authenticated;
12664
13160
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_tray_stickers TO service_role;
13161
+ ALTER TABLE public.productie_order_onderdelen ENABLE ROW LEVEL SECURITY;
13162
+ REVOKE ALL ON public.productie_order_onderdelen FROM anon;
13163
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.productie_order_onderdelen TO authenticated;
13164
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.productie_order_onderdelen TO service_role;
13165
+ ALTER TABLE public.productie_orders ENABLE ROW LEVEL SECURITY;
13166
+ REVOKE ALL ON public.productie_orders FROM anon;
13167
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.productie_orders TO authenticated;
13168
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.productie_orders TO service_role;
12665
13169
  ALTER TABLE public.stock_import_chunks ENABLE ROW LEVEL SECURITY;
12666
13170
  REVOKE ALL ON public.stock_import_chunks FROM anon;
12667
13171
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.stock_import_chunks TO authenticated;
@@ -12728,6 +13232,14 @@ ALTER TABLE public.voorraad_floriday_artikel_wachtrij ENABLE ROW LEVEL SECURITY;
12728
13232
  REVOKE ALL ON public.voorraad_floriday_artikel_wachtrij FROM anon;
12729
13233
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_artikel_wachtrij TO authenticated;
12730
13234
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_artikel_wachtrij TO service_role;
13235
+ ALTER TABLE public.voorraad_floriday_mutaties ENABLE ROW LEVEL SECURITY;
13236
+ REVOKE ALL ON public.voorraad_floriday_mutaties FROM anon;
13237
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_mutaties TO authenticated;
13238
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_mutaties TO service_role;
13239
+ ALTER TABLE public.voorraad_floriday_stand ENABLE ROW LEVEL SECURITY;
13240
+ REVOKE ALL ON public.voorraad_floriday_stand FROM anon;
13241
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_stand TO authenticated;
13242
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_stand TO service_role;
12731
13243
  ALTER TABLE public.voorraad_floriday_versie_fouten ENABLE ROW LEVEL SECURITY;
12732
13244
  REVOKE ALL ON public.voorraad_floriday_versie_fouten FROM anon;
12733
13245
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_floriday_versie_fouten TO authenticated;
@@ -12752,6 +13264,10 @@ ALTER TABLE public.voorraad_instellingen ENABLE ROW LEVEL SECURITY;
12752
13264
  REVOKE ALL ON public.voorraad_instellingen FROM anon;
12753
13265
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_instellingen TO authenticated;
12754
13266
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_instellingen TO service_role;
13267
+ ALTER TABLE public.voorraad_integriteit_runs ENABLE ROW LEVEL SECURITY;
13268
+ REVOKE ALL ON public.voorraad_integriteit_runs FROM anon;
13269
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_integriteit_runs TO authenticated;
13270
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_integriteit_runs TO service_role;
12755
13271
  ALTER TABLE public.voorraad_koppel_log ENABLE ROW LEVEL SECURITY;
12756
13272
  REVOKE ALL ON public.voorraad_koppel_log FROM anon;
12757
13273
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_koppel_log TO authenticated;
@@ -12775,6 +13291,18 @@ ALTER TABLE public.voorraad_media_upload_failures ENABLE ROW LEVEL SECURITY;
12775
13291
  REVOKE ALL ON public.voorraad_media_upload_failures FROM anon;
12776
13292
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_media_upload_failures TO authenticated;
12777
13293
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_media_upload_failures TO service_role;
13294
+ ALTER TABLE public.voorraad_order_terugdraaiing ENABLE ROW LEVEL SECURITY;
13295
+ REVOKE ALL ON public.voorraad_order_terugdraaiing FROM anon;
13296
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_order_terugdraaiing TO authenticated;
13297
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_order_terugdraaiing TO service_role;
13298
+ ALTER TABLE public.voorraad_orderregel_afboeking ENABLE ROW LEVEL SECURITY;
13299
+ REVOKE ALL ON public.voorraad_orderregel_afboeking FROM anon;
13300
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_orderregel_afboeking TO authenticated;
13301
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_orderregel_afboeking TO service_role;
13302
+ ALTER TABLE public.voorraad_orderregel_verzending ENABLE ROW LEVEL SECURITY;
13303
+ REVOKE ALL ON public.voorraad_orderregel_verzending FROM anon;
13304
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_orderregel_verzending TO authenticated;
13305
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_orderregel_verzending TO service_role;
12778
13306
  ALTER TABLE public.voorraad_placeholder_codes ENABLE ROW LEVEL SECURITY;
12779
13307
  REVOKE ALL ON public.voorraad_placeholder_codes FROM anon;
12780
13308
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.voorraad_placeholder_codes TO authenticated;
@@ -12865,6 +13393,10 @@ DROP POLICY IF EXISTS product_packings_write ON public.product_packings;
12865
13393
  CREATE POLICY product_packings_write ON public.product_packings AS PERMISSIVE FOR ALL TO authenticated USING (voorraad_can_write()) WITH CHECK (voorraad_can_write());
12866
13394
  DROP POLICY IF EXISTS "Ingelogden beheren traystickers" ON public.product_tray_stickers;
12867
13395
  CREATE POLICY "Ingelogden beheren traystickers" ON public.product_tray_stickers AS PERMISSIVE FOR ALL TO authenticated USING (true) WITH CHECK (true);
13396
+ DROP POLICY IF EXISTS "Ingelogd lezen" ON public.productie_order_onderdelen;
13397
+ CREATE POLICY "Ingelogd lezen" ON public.productie_order_onderdelen AS PERMISSIVE FOR SELECT TO authenticated USING (true);
13398
+ DROP POLICY IF EXISTS "Ingelogd lezen" ON public.productie_orders;
13399
+ CREATE POLICY "Ingelogd lezen" ON public.productie_orders AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12868
13400
  DROP POLICY IF EXISTS "Eigenaar leest importporties" ON public.stock_import_chunks;
12869
13401
  CREATE POLICY "Eigenaar leest importporties" ON public.stock_import_chunks AS PERMISSIVE FOR SELECT TO authenticated USING ((EXISTS ( SELECT 1
12870
13402
  FROM stock_import_runs r
@@ -12929,6 +13461,10 @@ DROP POLICY IF EXISTS "Ingelogd lezen alias" ON public.voorraad_floriday_alias;
12929
13461
  CREATE POLICY "Ingelogd lezen alias" ON public.voorraad_floriday_alias AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12930
13462
  DROP POLICY IF EXISTS voorraad_floriday_artikel_wachtrij_lezen ON public.voorraad_floriday_artikel_wachtrij;
12931
13463
  CREATE POLICY voorraad_floriday_artikel_wachtrij_lezen ON public.voorraad_floriday_artikel_wachtrij AS PERMISSIVE FOR SELECT TO authenticated USING (true);
13464
+ DROP POLICY IF EXISTS "Ingelogd lezen" ON public.voorraad_floriday_mutaties;
13465
+ CREATE POLICY "Ingelogd lezen" ON public.voorraad_floriday_mutaties AS PERMISSIVE FOR SELECT TO authenticated USING (true);
13466
+ DROP POLICY IF EXISTS "Ingelogd lezen" ON public.voorraad_floriday_stand;
13467
+ CREATE POLICY "Ingelogd lezen" ON public.voorraad_floriday_stand AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12932
13468
  DROP POLICY IF EXISTS "Ingelogd lezen versie fouten" ON public.voorraad_floriday_versie_fouten;
12933
13469
  CREATE POLICY "Ingelogd lezen versie fouten" ON public.voorraad_floriday_versie_fouten AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12934
13470
  DROP POLICY IF EXISTS "Ingelogd lezen versierondes" ON public.voorraad_floriday_versie_rondes;
@@ -12939,6 +13475,8 @@ DROP POLICY IF EXISTS "Ingelogd lezen vervallen" ON public.voorraad_floriday_ver
12939
13475
  CREATE POLICY "Ingelogd lezen vervallen" ON public.voorraad_floriday_vervallen AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12940
13476
  DROP POLICY IF EXISTS voorraad_instellingen_lezen ON public.voorraad_instellingen;
12941
13477
  CREATE POLICY voorraad_instellingen_lezen ON public.voorraad_instellingen AS PERMISSIVE FOR SELECT TO authenticated USING (true);
13478
+ DROP POLICY IF EXISTS "Ingelogd lezen" ON public.voorraad_integriteit_runs;
13479
+ CREATE POLICY "Ingelogd lezen" ON public.voorraad_integriteit_runs AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12942
13480
  DROP POLICY IF EXISTS voorraad_koppel_log_lezen ON public.voorraad_koppel_log;
12943
13481
  CREATE POLICY voorraad_koppel_log_lezen ON public.voorraad_koppel_log AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12944
13482
  DROP POLICY IF EXISTS voorraad_koppel_twijfel_lezen ON public.voorraad_koppel_twijfel;
@@ -12951,6 +13489,12 @@ DROP POLICY IF EXISTS "Ingelogde gebruikers kunnen de eventlog lezen" ON public.
12951
13489
  CREATE POLICY "Ingelogde gebruikers kunnen de eventlog lezen" ON public.voorraad_logistics_event_log AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12952
13490
  DROP POLICY IF EXISTS "service role only" ON public.voorraad_media_upload_failures;
12953
13491
  CREATE POLICY "service role only" ON public.voorraad_media_upload_failures AS PERMISSIVE FOR ALL TO service_role USING (true) WITH CHECK (true);
13492
+ DROP POLICY IF EXISTS "Ingelogd lezen" ON public.voorraad_order_terugdraaiing;
13493
+ CREATE POLICY "Ingelogd lezen" ON public.voorraad_order_terugdraaiing AS PERMISSIVE FOR SELECT TO authenticated USING (true);
13494
+ DROP POLICY IF EXISTS "Ingelogd lezen" ON public.voorraad_orderregel_afboeking;
13495
+ CREATE POLICY "Ingelogd lezen" ON public.voorraad_orderregel_afboeking AS PERMISSIVE FOR SELECT TO authenticated USING (true);
13496
+ DROP POLICY IF EXISTS "Ingelogd lezen" ON public.voorraad_orderregel_verzending;
13497
+ CREATE POLICY "Ingelogd lezen" ON public.voorraad_orderregel_verzending AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12954
13498
  DROP POLICY IF EXISTS "Ingelogd mag invulteksten lezen" ON public.voorraad_placeholder_codes;
12955
13499
  CREATE POLICY "Ingelogd mag invulteksten lezen" ON public.voorraad_placeholder_codes AS PERMISSIVE FOR SELECT TO authenticated USING (true);
12956
13500
  DROP POLICY IF EXISTS "Users can insert own publish attempts" ON public.voorraad_publish_attempts;
@@ -13026,12 +13570,14 @@ DO $g$ BEGIN IF to_regclass('public.order_items') IS NULL THEN RAISE NOTICE 'Voo
13026
13570
  DO $g$ BEGIN IF to_regclass('public.order_items') IS NULL THEN RAISE NOTICE 'Voorraad: order_items (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_order_items_fusten_aid ON public.order_items$q$; EXECUTE $q$CREATE TRIGGER trg_order_items_fusten_aid AFTER INSERT OR DELETE ON public.order_items FOR EACH ROW EXECUTE FUNCTION trg_order_items_fusten()$q$; END IF; END $g$;
13027
13571
  DO $g$ BEGIN IF to_regclass('public.order_items') IS NULL THEN RAISE NOTICE 'Voorraad: order_items (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_order_items_fusten_au ON public.order_items$q$; EXECUTE $q$CREATE TRIGGER trg_order_items_fusten_au AFTER UPDATE OF quantity, product_id, plants_per_crate, quantity_crates, vbn_package_code, packing_source, quarantined_at ON public.order_items FOR EACH ROW WHEN (((old.quantity IS DISTINCT FROM new.quantity) OR (old.product_id IS DISTINCT FROM new.product_id) OR (old.plants_per_crate IS DISTINCT FROM new.plants_per_crate) OR (old.quantity_crates IS DISTINCT FROM new.quantity_crates) OR (old.vbn_package_code IS DISTINCT FROM new.vbn_package_code) OR (old.packing_source IS DISTINCT FROM new.packing_source) OR ((old.quarantined_at IS NULL) IS DISTINCT FROM (new.quarantined_at IS NULL)))) EXECUTE FUNCTION trg_order_items_fusten()$q$; END IF; END $g$;
13028
13572
  DO $g$ BEGIN IF to_regclass('public.order_items') IS NULL THEN RAISE NOTICE 'Voorraad: order_items (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_order_items_reserve ON public.order_items$q$; EXECUTE $q$CREATE TRIGGER trg_order_items_reserve AFTER INSERT OR DELETE OR UPDATE OF quantity, product_id, order_id, quarantined_at, source_line_key ON public.order_items FOR EACH ROW EXECUTE FUNCTION trg_order_items_reserve_fn()$q$; END IF; END $g$;
13573
+ DO $g$ BEGIN IF to_regclass('public.order_items') IS NULL THEN RAISE NOTICE 'Voorraad: order_items (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS zz_voorraad_floriday_mutaties_items ON public.order_items$q$; EXECUTE $q$CREATE TRIGGER zz_voorraad_floriday_mutaties_items AFTER INSERT OR DELETE OR UPDATE OF quantity, product_id, quarantined_at ON public.order_items FOR EACH ROW EXECUTE FUNCTION voorraad_floriday_mutaties_trg()$q$; END IF; END $g$;
13029
13574
  DO $g$ BEGIN IF to_regclass('public.orders') IS NULL THEN RAISE NOTICE 'Voorraad: orders (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_orders_cancel_release ON public.orders$q$; EXECUTE $q$CREATE TRIGGER trg_orders_cancel_release AFTER UPDATE OF status ON public.orders FOR EACH ROW EXECUTE FUNCTION trg_orders_cancel_release_fn()$q$; END IF; END $g$;
13030
13575
  DO $g$ BEGIN IF to_regclass('public.orders') IS NULL THEN RAISE NOTICE 'Voorraad: orders (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_orders_consume_on_complete ON public.orders$q$; EXECUTE $q$CREATE TRIGGER trg_orders_consume_on_complete AFTER UPDATE OF status ON public.orders FOR EACH ROW EXECUTE FUNCTION trg_orders_consume_on_complete_fn()$q$; END IF; END $g$;
13031
13576
  DO $g$ BEGIN IF to_regclass('public.orders') IS NULL THEN RAISE NOTICE 'Voorraad: orders (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_orders_default_phase ON public.orders$q$; EXECUTE $q$CREATE TRIGGER trg_orders_default_phase BEFORE INSERT ON public.orders FOR EACH ROW EXECUTE FUNCTION trg_orders_default_phase_fn()$q$; END IF; END $g$;
13032
13577
  DO $g$ BEGIN IF to_regclass('public.orders') IS NULL THEN RAISE NOTICE 'Voorraad: orders (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_orders_external_fulfilled ON public.orders$q$; EXECUTE $q$CREATE TRIGGER trg_orders_external_fulfilled AFTER UPDATE OF custom_fields ON public.orders FOR EACH ROW EXECUTE FUNCTION trg_orders_external_fulfilled_fn()$q$; END IF; END $g$;
13033
13578
  DO $g$ BEGIN IF to_regclass('public.orders') IS NULL THEN RAISE NOTICE 'Voorraad: orders (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_orders_fusten_au ON public.orders$q$; EXECUTE $q$CREATE TRIGGER trg_orders_fusten_au AFTER UPDATE OF status, cancelled_at, deleted_at ON public.orders FOR EACH ROW WHEN (((old.status IS DISTINCT FROM new.status) OR (old.cancelled_at IS DISTINCT FROM new.cancelled_at) OR (old.deleted_at IS DISTINCT FROM new.deleted_at))) EXECUTE FUNCTION trg_orders_fusten()$q$; END IF; END $g$;
13034
13579
  DO $g$ BEGIN IF to_regclass('public.orders') IS NULL THEN RAISE NOTICE 'Voorraad: orders (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_orders_recompute_reserved ON public.orders$q$; EXECUTE $q$CREATE TRIGGER trg_orders_recompute_reserved AFTER DELETE OR UPDATE ON public.orders FOR EACH ROW EXECUTE FUNCTION trg_orders_recompute_reserved_fn()$q$; END IF; END $g$;
13580
+ DO $g$ BEGIN IF to_regclass('public.orders') IS NULL THEN RAISE NOTICE 'Voorraad: orders (Verkoop) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS zz_voorraad_floriday_mutaties_orders ON public.orders$q$; EXECUTE $q$CREATE TRIGGER zz_voorraad_floriday_mutaties_orders AFTER DELETE OR UPDATE OF status, deleted_at, cancelled_at, order_source ON public.orders FOR EACH ROW EXECUTE FUNCTION voorraad_floriday_mutaties_trg()$q$; END IF; END $g$;
13035
13581
  DO $g$ BEGIN IF to_regclass('public.product_bom_items') IS NULL THEN RAISE NOTICE 'Voorraad: product_bom_items (core) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS set_product_bom_tenant_trigger ON public.product_bom_items$q$; EXECUTE $q$CREATE TRIGGER set_product_bom_tenant_trigger BEFORE INSERT ON public.product_bom_items FOR EACH ROW EXECUTE FUNCTION set_product_bom_tenant()$q$; END IF; END $g$;
13036
13582
  DO $g$ BEGIN IF to_regclass('public.product_bom_items') IS NULL THEN RAISE NOTICE 'Voorraad: product_bom_items (core) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_check_product_structure ON public.product_bom_items$q$; EXECUTE $q$CREATE TRIGGER trg_check_product_structure BEFORE INSERT OR UPDATE ON public.product_bom_items FOR EACH ROW EXECUTE FUNCTION trg_check_product_structure()$q$; END IF; END $g$;
13037
13583
  DO $g$ BEGIN IF to_regclass('public.product_bom_items') IS NULL THEN RAISE NOTICE 'Voorraad: product_bom_items (core) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_structure_change_recompute ON public.product_bom_items$q$; EXECUTE $q$CREATE TRIGGER trg_structure_change_recompute AFTER INSERT OR DELETE OR UPDATE ON public.product_bom_items FOR EACH ROW EXECUTE FUNCTION trg_structure_change_recompute()$q$; END IF; END $g$;
@@ -13047,6 +13593,8 @@ DROP TRIGGER IF EXISTS product_packings_updated_at ON public.product_packings;
13047
13593
  CREATE TRIGGER product_packings_updated_at BEFORE UPDATE ON public.product_packings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
13048
13594
  DROP TRIGGER IF EXISTS trg_product_tray_stickers_touch ON public.product_tray_stickers;
13049
13595
  CREATE TRIGGER trg_product_tray_stickers_touch BEFORE UPDATE ON public.product_tray_stickers FOR EACH ROW EXECUTE FUNCTION product_tray_stickers_touch();
13596
+ DROP TRIGGER IF EXISTS trg_productie_orders_updated_at ON public.productie_orders;
13597
+ CREATE TRIGGER trg_productie_orders_updated_at BEFORE UPDATE ON public.productie_orders FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
13050
13598
  DO $g$ BEGIN IF to_regclass('public.products') IS NULL THEN RAISE NOTICE 'Voorraad: products (core) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_floriday_ensure_inventory_settings ON public.products$q$; EXECUTE $q$CREATE TRIGGER trg_floriday_ensure_inventory_settings AFTER INSERT ON public.products FOR EACH ROW EXECUTE FUNCTION floriday_ensure_inventory_settings()$q$; END IF; END $g$;
13051
13599
  DO $g$ BEGIN IF to_regclass('public.products') IS NULL THEN RAISE NOTICE 'Voorraad: products (core) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_products_autolink ON public.products$q$; EXECUTE $q$CREATE TRIGGER trg_products_autolink BEFORE INSERT OR UPDATE OF product, trade_item_key ON public.products FOR EACH ROW EXECUTE FUNCTION trg_products_autolink_fn()$q$; END IF; END $g$;
13052
13600
  DO $g$ BEGIN IF to_regclass('public.products') IS NULL THEN RAISE NOTICE 'Voorraad: products (core) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$DROP TRIGGER IF EXISTS trg_products_floriday_link_enqueue ON public.products$q$; EXECUTE $q$CREATE TRIGGER trg_products_floriday_link_enqueue AFTER UPDATE ON public.products REFERENCING OLD TABLE AS old_rows NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION trg_products_floriday_link_enqueue()$q$; END IF; END $g$;
@@ -13067,6 +13615,8 @@ DROP TRIGGER IF EXISTS update_trade_item_drafts_updated_at ON public.trade_item_
13067
13615
  CREATE TRIGGER update_trade_item_drafts_updated_at BEFORE UPDATE ON public.trade_item_drafts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
13068
13616
  DROP TRIGGER IF EXISTS trade_item_prices_set_updated_at ON public.trade_item_prices;
13069
13617
  CREATE TRIGGER trade_item_prices_set_updated_at BEFORE UPDATE ON public.trade_item_prices FOR EACH ROW EXECUTE FUNCTION trade_item_prices_touch_updated_at();
13618
+ DROP TRIGGER IF EXISTS aa_voorraad_reservering_extra ON public.trade_item_stock;
13619
+ CREATE TRIGGER aa_voorraad_reservering_extra BEFORE UPDATE OF reserved_stock ON public.trade_item_stock FOR EACH ROW EXECUTE FUNCTION voorraad_reservering_extra_trg();
13070
13620
  DROP TRIGGER IF EXISTS trg_floriday_stock_change_recompute ON public.trade_item_stock;
13071
13621
  CREATE TRIGGER trg_floriday_stock_change_recompute AFTER INSERT OR UPDATE ON public.trade_item_stock FOR EACH ROW EXECUTE FUNCTION floriday_stock_change_recompute();
13072
13622
  DROP TRIGGER IF EXISTS trg_stock_structure_recompute_ins ON public.trade_item_stock;
@@ -13103,6 +13653,8 @@ DROP TRIGGER IF EXISTS trg_voorraad_buffer_state_updated_at ON public.voorraad_b
13103
13653
  CREATE TRIGGER trg_voorraad_buffer_state_updated_at BEFORE UPDATE ON public.voorraad_buffer_state FOR EACH ROW EXECUTE FUNCTION voorraad_touch_updated_at();
13104
13654
  DROP TRIGGER IF EXISTS update_voorraad_custom_fields_updated_at ON public.voorraad_custom_fields;
13105
13655
  CREATE TRIGGER update_voorraad_custom_fields_updated_at BEFORE UPDATE ON public.voorraad_custom_fields FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
13656
+ DROP TRIGGER IF EXISTS trg_voorraad_floriday_mutaties_updated_at ON public.voorraad_floriday_mutaties;
13657
+ CREATE TRIGGER trg_voorraad_floriday_mutaties_updated_at BEFORE UPDATE ON public.voorraad_floriday_mutaties FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
13106
13658
  DROP TRIGGER IF EXISTS voorraad_floriday_vervallen_touch ON public.voorraad_floriday_vervallen;
13107
13659
  CREATE TRIGGER voorraad_floriday_vervallen_touch BEFORE UPDATE ON public.voorraad_floriday_vervallen FOR EACH ROW EXECUTE FUNCTION voorraad_touch_updated_at();
13108
13660
  DROP TRIGGER IF EXISTS trg_voorraad_event_log_updated_at ON public.voorraad_logistics_event_log;