@flowselections/floriday-voorraad 1.0.92 → 1.0.96

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 (33) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  2. package/dist-lib/components/voorraad/ArtikelWizard.js +21 -41
  3. package/dist-lib/components/voorraad/FloridayTakeoverPanel.d.ts.map +1 -1
  4. package/dist-lib/components/voorraad/FloridayTakeoverPanel.js +18 -1
  5. package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
  6. package/dist-lib/components/voorraad/StockBulkButtons.js +59 -0
  7. package/dist-lib/components/voorraad/StockIntegrityAlert.d.ts.map +1 -1
  8. package/dist-lib/components/voorraad/StockIntegrityAlert.js +5 -5
  9. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  10. package/dist-lib/components/voorraad/VoorraadTable.js +16 -1
  11. package/dist-lib/integrations/supabase/types.d.ts +405 -0
  12. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  13. package/dist-lib/lib/__tests__/product-packings.test.d.ts +2 -0
  14. package/dist-lib/lib/__tests__/product-packings.test.d.ts.map +1 -0
  15. package/dist-lib/lib/__tests__/product-packings.test.js +42 -0
  16. package/dist-lib/lib/ensure-article-codes.d.ts.map +1 -1
  17. package/dist-lib/lib/ensure-article-codes.js +2 -12
  18. package/dist-lib/lib/floriday-payload.d.ts +15 -0
  19. package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
  20. package/dist-lib/lib/floriday-payload.js +36 -10
  21. package/dist-lib/lib/fusten.d.ts.map +1 -1
  22. package/dist-lib/lib/fusten.js +6 -1
  23. package/dist-lib/lib/product-packing.d.ts +8 -5
  24. package/dist-lib/lib/product-packing.d.ts.map +1 -1
  25. package/dist-lib/lib/product-packing.js +44 -36
  26. package/dist-lib/lib/product-packings.d.ts +33 -0
  27. package/dist-lib/lib/product-packings.d.ts.map +1 -0
  28. package/dist-lib/lib/product-packings.js +89 -0
  29. package/dist-lib/server-hooks/floriday-follow.d.ts.map +1 -1
  30. package/dist-lib/server-hooks/floriday-follow.js +22 -2
  31. package/package.json +1 -1
  32. package/sql/AGENTS.md +5 -0
  33. package/sql/floriday-voorraad-schema.sql +279 -7
@@ -1,4 +1,4 @@
1
- -- @flowselections/floriday-voorraad 1.0.91 – volledig schema (idempotent, geen gegevens).
1
+ -- @flowselections/floriday-voorraad 1.0.95 – volledig schema (idempotent, geen gegevens).
2
2
  -- Gegenereerd uit de database met scripts/generate-voorraad-schema.py. Niet met de hand wijzigen.
3
3
  -- Volgorde 20. Ontbrekende tabellen van andere modules worden overgeslagen met een NOTICE; draai opnieuw na die module.
4
4
  -- De statuscontrole van floriday_continuous_stock_queue is van Voorraad en wordt hier opnieuw gemaakt.
@@ -276,6 +276,25 @@ ALTER TABLE public.product_merge_log ADD COLUMN IF NOT EXISTS snapshot jsonb DEF
276
276
  ALTER TABLE public.product_merge_log ADD COLUMN IF NOT EXISTS notes text;
277
277
  ALTER TABLE public.product_merge_log ADD COLUMN IF NOT EXISTS merged_at timestamp with time zone DEFAULT now() NOT NULL;
278
278
 
279
+ -- product_packings
280
+ CREATE TABLE IF NOT EXISTS public.product_packings ();
281
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS id uuid DEFAULT gen_random_uuid() NOT NULL;
282
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS product_id uuid NOT NULL;
283
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS source text DEFAULT 'eigen'::text NOT NULL;
284
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS floriday_packing_id text;
285
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS vbn_package_code integer;
286
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS custom_package_id text;
287
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS fust_type_id uuid;
288
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS load_carrier text;
289
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS pieces_per_package integer;
290
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS packages_per_layer integer;
291
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS layers_per_load_carrier integer;
292
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS is_primary boolean DEFAULT false NOT NULL;
293
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS customer_organization_ids text[] DEFAULT '{}'::text[] NOT NULL;
294
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS sort_order integer DEFAULT 0 NOT NULL;
295
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT now() NOT NULL;
296
+ ALTER TABLE public.product_packings ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT now() NOT NULL;
297
+
279
298
  -- product_tray_stickers
280
299
  CREATE TABLE IF NOT EXISTS public.product_tray_stickers ();
281
300
  ALTER TABLE public.product_tray_stickers ADD COLUMN IF NOT EXISTS product_id uuid NOT NULL;
@@ -901,7 +920,8 @@ BEGIN
901
920
  SELECT * INTO s FROM public.floriday_inventory_settings
902
921
  WHERE connection_id = p_connection_id AND product_id = p_product_id;
903
922
  stock_mode := COALESCE(s.stock_mode, 'FOLLOW_ERP');
904
- SELECT NULLIF(p.plants_per_crate, 0) INTO v_pack FROM public.products p WHERE p.id = p_product_id;
923
+ -- Verpakking alleen uit de echte (primaire) verpakking, nooit uit products.*.
924
+ v_pack := public.product_primary_pieces(p_product_id);
905
925
 
906
926
  v_calc := public.calculate_structure_available(p_connection_id, p_product_id);
907
927
  v_type := v_calc->>'type';
@@ -910,11 +930,9 @@ BEGIN
910
930
  v_reason := v_calc->>'reason';
911
931
  erp_available := v_avail; packing_size := v_pack; reason := NULL;
912
932
 
913
- -- Lid van een actieve productgroep: nooit los naar Floriday (de groep publiceert).
914
933
  IF v_type IS DISTINCT FROM 'GROUP' AND cardinality(public.product_member_group_ids(p_product_id)) > 0 THEN
915
934
  desired_quantity := 0; desired_availability := false; reason := 'GROUP_MEMBER_OFF'; RETURN NEXT; RETURN;
916
935
  END IF;
917
-
918
936
  IF stock_mode = 'DISABLED' THEN
919
937
  desired_quantity := 0; desired_availability := false; RETURN NEXT; RETURN;
920
938
  END IF;
@@ -2142,7 +2160,6 @@ AS $function$
2142
2160
  DECLARE a record; n integer := 0; v_ins integer; v_norm text := public.normalize_order_source(p_source);
2143
2161
  BEGIN
2144
2162
  IF p_order_item_id IS NULL THEN RETURN 0; END IF;
2145
- -- Oude route had al afgeboekt (voor de markering bestond)
2146
2163
  IF EXISTS (SELECT 1 FROM public.stock_mutation_log WHERE order_item_id = p_order_item_id AND mutation_type = 'consumption')
2147
2164
  AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log WHERE order_item_id = p_order_item_id AND mutation_type = 'consumption_done') THEN
2148
2165
  RETURN 0;
@@ -2158,9 +2175,17 @@ BEGIN
2158
2175
  INSERT INTO public.stock_mutation_log(trade_item_key, order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
2159
2176
  VALUES (a.trade_item_key, p_order_id, p_order_item_id, v_norm, 'consumption_blocked', COALESCE(a.qty,0),
2160
2177
  'toewijzing Engine: ' || COALESCE(a.note, 'fout'), auth.uid());
2178
+ ELSIF COALESCE(a.qty,0) > 0 AND v_norm = 'FO' AND public.voorraad_floriday_leads_key(a.trade_item_key) THEN
2179
+ -- Floriday leidt: Floriday heeft deze verkoop al van het eigen aanbod afgetrokken.
2180
+ INSERT INTO public.stock_mutation_log(trade_item_key, order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
2181
+ VALUES (a.trade_item_key, p_order_id, p_order_item_id, v_norm, 'consumption_skipped', a.qty,
2182
+ 'Niet afgeboekt: Floriday leidt dit artikel en heeft de verkoop al verwerkt — ' || COALESCE(p_reason,''), auth.uid());
2161
2183
  ELSIF COALESCE(a.qty,0) > 0 THEN
2162
2184
  PERFORM public.apply_stock_consumption(a.trade_item_key, a.qty, p_order_id, p_order_item_id, p_source,
2163
2185
  COALESCE(p_reason,'afgeboekt') || ' [' || a.source || ']');
2186
+ IF v_norm <> 'FO' AND public.voorraad_floriday_leads_key(a.trade_item_key) THEN
2187
+ PERFORM public.voorraad_alert_floriday_not_updated(a.trade_item_key, a.qty, p_order_id, p_order_item_id);
2188
+ END IF;
2164
2189
  n := n + 1;
2165
2190
  END IF;
2166
2191
  END LOOP;
@@ -2799,7 +2824,7 @@ CREATE OR REPLACE FUNCTION public.floriday_desired_stock_regression_check()
2799
2824
  AS $function$
2800
2825
  WITH b AS (
2801
2826
  SELECT s.connection_id, s.product_id, p.product, s.stock_mode, s.buffer_quantity, s.maximum_quantity, s.fixed_quantity, s.manual_quantity,
2802
- NULLIF(p.plants_per_crate,0) AS pack,
2827
+ public.product_primary_pieces(p.id) AS pack,
2803
2828
  (SELECT GREATEST(0, floor(ts.physical_stock - ts.reserved_stock))::int FROM public.trade_item_stock ts
2804
2829
  WHERE ts.connection_id = s.connection_id
2805
2830
  AND ts.trade_item_key = COALESCE(NULLIF(btrim(p.floriday_trade_item_id),''), NULLIF(btrim(p.trade_item_key),''))) AS avail
@@ -2987,6 +3012,7 @@ BEGIN
2987
3012
  WHERE t.trade_item_key IN (v_tid, 'product:'||v_pid::text, v_pid::text)
2988
3013
  ORDER BY (t.trade_item_key = v_tid) DESC, t.updated_at DESC NULLS LAST LIMIT 1;
2989
3014
  v_stock := NULLIF(v_it->>'stock','')::numeric;
3015
+ IF v_stock IS NULL THEN v_res := 'Geen Floriday-voorraad gevonden (geen doorlopende voorraad en geen weekaanbod voor deze week)'; END IF;
2990
3016
  IF v_stock IS NOT NULL AND v_stock >= 0 AND v_stock IS DISTINCT FROM v_cur_stock THEN
2991
3017
  IF v_uid IS NULL THEN RAISE EXCEPTION 'Geen eigenaar bij de voorraadsturing'; END IF;
2992
3018
  PERFORM set_config('request.jwt.claims', json_build_object('sub', v_uid, 'role', 'authenticated')::text, true);
@@ -3266,6 +3292,9 @@ BEGIN
3266
3292
  UPDATE public.floriday_inventory_settings s
3267
3293
  SET erp_leading = true, erp_leading_at = now(), updated_by = auth.uid(), updated_at = now()
3268
3294
  WHERE s.connection_id = p_connection_id AND s.product_id = v_pid;
3295
+ -- Floriday-orders van voor de overstap zitten al in de Floriday-stand: nooit nog eens afboeken.
3296
+ PERFORM public.voorraad_mark_floriday_sold_before_switch(v_pid);
3297
+ PERFORM public.recompute_reserved_stock((SELECT COALESCE(NULLIF(btrim(p.floriday_trade_item_id),''), p.trade_item_key) FROM public.products p WHERE p.id = v_pid));
3269
3298
  PERFORM public.floriday_recompute_inventory(p_connection_id, v_pid, true);
3270
3299
  v_n := v_n + 1;
3271
3300
  END LOOP;
@@ -4479,6 +4508,128 @@ END $function$
4479
4508
  ;
4480
4509
  REVOKE ALL ON FUNCTION product_own_available(uuid,uuid) FROM PUBLIC;
4481
4510
  GRANT EXECUTE ON FUNCTION product_own_available(uuid,uuid) TO service_role;
4511
+ CREATE OR REPLACE FUNCTION public.product_packings_derive(p_product_id uuid)
4512
+ RETURNS void
4513
+ LANGUAGE plpgsql
4514
+ SECURITY DEFINER
4515
+ SET search_path TO 'public'
4516
+ AS $function$
4517
+ DECLARE r record; v_nullable boolean;
4518
+ BEGIN
4519
+ SELECT * INTO r FROM public.product_packings WHERE product_id = p_product_id AND is_primary LIMIT 1;
4520
+ IF FOUND AND r.pieces_per_package > 0 AND r.packages_per_layer > 0 AND r.layers_per_load_carrier > 0 THEN
4521
+ UPDATE public.products SET
4522
+ plants_per_crate = r.pieces_per_package,
4523
+ crates_per_layer = r.packages_per_layer,
4524
+ layers_per_cart = r.layers_per_load_carrier,
4525
+ fust_type_id = r.fust_type_id,
4526
+ floriday_packing_confirmed_at = now(),
4527
+ floriday_packing_source = 'product_packings:' || r.source
4528
+ WHERE id = p_product_id;
4529
+ ELSE
4530
+ -- Onvolledig: niets verzinnen. Kolommen leeg als core dat toestaat, anders alleen "onbekend" markeren.
4531
+ SELECT NOT attnotnull INTO v_nullable FROM pg_attribute
4532
+ WHERE attrelid = 'public.products'::regclass AND attname = 'plants_per_crate';
4533
+ IF v_nullable THEN
4534
+ EXECUTE 'UPDATE public.products SET plants_per_crate = NULL, crates_per_layer = NULL, layers_per_cart = NULL WHERE id = $1' USING p_product_id;
4535
+ END IF;
4536
+ UPDATE public.products SET floriday_packing_confirmed_at = NULL, floriday_packing_source = NULL,
4537
+ fust_type_id = CASE WHEN FOUND THEN r.fust_type_id ELSE fust_type_id END
4538
+ WHERE id = p_product_id;
4539
+ END IF;
4540
+ END $function$
4541
+ ;
4542
+ REVOKE ALL ON FUNCTION product_packings_derive(uuid) FROM PUBLIC;
4543
+ GRANT EXECUTE ON FUNCTION product_packings_derive(uuid) TO service_role;
4544
+ CREATE OR REPLACE FUNCTION public.product_packings_replace(p_product_id uuid, p_source text, p_packings jsonb)
4545
+ RETURNS integer
4546
+ LANGUAGE plpgsql
4547
+ SET search_path TO 'public'
4548
+ AS $function$
4549
+ DECLARE n integer;
4550
+ BEGIN
4551
+ IF p_source NOT IN ('floriday','eigen') THEN RAISE EXCEPTION 'Onbekende verpakkingsbron %', p_source; END IF;
4552
+ DELETE FROM public.product_packings WHERE product_id = p_product_id;
4553
+ INSERT INTO public.product_packings(product_id, source, floriday_packing_id, vbn_package_code, custom_package_id,
4554
+ fust_type_id, load_carrier, pieces_per_package, packages_per_layer, layers_per_load_carrier, is_primary,
4555
+ customer_organization_ids, sort_order)
4556
+ SELECT p_product_id, p_source, NULLIF(e->>'floriday_packing_id',''), NULLIF(e->>'vbn_package_code','')::int,
4557
+ NULLIF(e->>'custom_package_id',''), NULLIF(e->>'fust_type_id','')::uuid, NULLIF(e->>'load_carrier',''),
4558
+ NULLIF(NULLIF(e->>'pieces_per_package',''),'0')::int, NULLIF(NULLIF(e->>'packages_per_layer',''),'0')::int,
4559
+ NULLIF(NULLIF(e->>'layers_per_load_carrier',''),'0')::int, COALESCE((e->>'is_primary')::boolean,false),
4560
+ COALESCE(ARRAY(SELECT jsonb_array_elements_text(e->'customer_organization_ids')), '{}'), (o - 1)::int
4561
+ FROM jsonb_array_elements(COALESCE(p_packings,'[]'::jsonb)) WITH ORDINALITY AS t(e, o);
4562
+ GET DIAGNOSTICS n = ROW_COUNT;
4563
+ RETURN n;
4564
+ END $function$
4565
+ ;
4566
+ REVOKE ALL ON FUNCTION product_packings_replace(uuid,text,jsonb) FROM PUBLIC;
4567
+ GRANT EXECUTE ON FUNCTION product_packings_replace(uuid,text,jsonb) TO PUBLIC;
4568
+ GRANT EXECUTE ON FUNCTION product_packings_replace(uuid,text,jsonb) TO anon;
4569
+ GRANT EXECUTE ON FUNCTION product_packings_replace(uuid,text,jsonb) TO authenticated;
4570
+ GRANT EXECUTE ON FUNCTION product_packings_replace(uuid,text,jsonb) TO service_role;
4571
+ CREATE OR REPLACE FUNCTION public.product_packings_sync_from_floriday(p_connection_id uuid, p_product_ids uuid[], p_only_existing boolean DEFAULT false)
4572
+ RETURNS jsonb
4573
+ LANGUAGE plpgsql
4574
+ SET search_path TO 'public'
4575
+ AS $function$
4576
+ DECLARE r record; v_list jsonb; n_done int := 0; n_missing int := 0;
4577
+ BEGIN
4578
+ FOR r IN
4579
+ SELECT p.id, c.data
4580
+ FROM public.products p
4581
+ JOIN public.floriday_trade_items_cache c
4582
+ ON c.connection_id = p_connection_id AND c.floriday_id = NULLIF(btrim(p.floriday_trade_item_id),'')
4583
+ AND COALESCE(c.is_deleted,false) = false
4584
+ WHERE p.id = ANY(p_product_ids)
4585
+ AND (NOT p_only_existing OR EXISTS (SELECT 1 FROM public.product_packings x WHERE x.product_id = p.id AND x.source = 'floriday'))
4586
+ LOOP
4587
+ IF jsonb_typeof(r.data->'packingConfigurations') <> 'array' OR jsonb_array_length(r.data->'packingConfigurations') = 0 THEN
4588
+ n_missing := n_missing + 1; CONTINUE;
4589
+ END IF;
4590
+ SELECT jsonb_agg(jsonb_build_object(
4591
+ 'floriday_packing_id', COALESCE(e->>'packingConfigurationId', e->>'id'),
4592
+ 'vbn_package_code', e#>>'{package,vbnPackageCode}',
4593
+ 'custom_package_id', e#>>'{package,customPackageId}',
4594
+ 'fust_type_id', (SELECT f.id FROM public.fust_types f WHERE f.connection_id IS NULL
4595
+ AND f.vbn_code IS NOT NULL AND f.vbn_code::text = e#>>'{package,vbnPackageCode}' LIMIT 1),
4596
+ 'load_carrier', e->>'loadCarrier',
4597
+ 'pieces_per_package', e->>'piecesPerPackage',
4598
+ 'packages_per_layer', e->>'packagesPerLayer',
4599
+ 'layers_per_load_carrier', e->>'layersPerLoadCarrier',
4600
+ 'is_primary', COALESCE((e->>'primary')::boolean, false),
4601
+ 'customer_organization_ids', COALESCE(e->'allowedCustomerOrganizationIds', '[]'::jsonb)) ORDER BY o)
4602
+ INTO v_list
4603
+ FROM jsonb_array_elements(r.data->'packingConfigurations') WITH ORDINALITY t(e, o)
4604
+ WHERE jsonb_typeof(e) = 'object';
4605
+ PERFORM public.product_packings_replace(r.id, 'floriday', v_list);
4606
+ n_done := n_done + 1;
4607
+ END LOOP;
4608
+ RETURN jsonb_build_object('copied', n_done, 'no_packings_in_floriday', n_missing);
4609
+ END $function$
4610
+ ;
4611
+ REVOKE ALL ON FUNCTION product_packings_sync_from_floriday(uuid,uuid[],boolean) FROM PUBLIC;
4612
+ GRANT EXECUTE ON FUNCTION product_packings_sync_from_floriday(uuid,uuid[],boolean) TO PUBLIC;
4613
+ GRANT EXECUTE ON FUNCTION product_packings_sync_from_floriday(uuid,uuid[],boolean) TO anon;
4614
+ GRANT EXECUTE ON FUNCTION product_packings_sync_from_floriday(uuid,uuid[],boolean) TO authenticated;
4615
+ GRANT EXECUTE ON FUNCTION product_packings_sync_from_floriday(uuid,uuid[],boolean) TO service_role;
4616
+ CREATE OR REPLACE FUNCTION public.product_primary_pieces(p_product_id uuid)
4617
+ RETURNS integer
4618
+ LANGUAGE sql
4619
+ STABLE
4620
+ SET search_path TO 'public'
4621
+ AS $function$
4622
+ SELECT pp.pieces_per_package FROM public.product_packings pp
4623
+ WHERE pp.product_id = p_product_id AND pp.is_primary
4624
+ AND pp.pieces_per_package > 0 AND pp.packages_per_layer > 0 AND pp.layers_per_load_carrier > 0
4625
+ LIMIT 1
4626
+ $function$
4627
+ ;
4628
+ REVOKE ALL ON FUNCTION product_primary_pieces(uuid) FROM PUBLIC;
4629
+ GRANT EXECUTE ON FUNCTION product_primary_pieces(uuid) TO PUBLIC;
4630
+ GRANT EXECUTE ON FUNCTION product_primary_pieces(uuid) TO anon;
4631
+ GRANT EXECUTE ON FUNCTION product_primary_pieces(uuid) TO authenticated;
4632
+ GRANT EXECUTE ON FUNCTION product_primary_pieces(uuid) TO service_role;
4482
4633
  CREATE OR REPLACE FUNCTION public.product_set_depth_above(p_product_id uuid, p_path uuid[] DEFAULT '{}'::uuid[])
4483
4634
  RETURNS integer
4484
4635
  LANGUAGE plpgsql
@@ -4803,6 +4954,17 @@ BEGIN
4803
4954
  GROUP BY r.order_item_id, r.trade_item_key, r.source, r.group_id;
4804
4955
  DELETE FROM public.logistics_reservation_allocations WHERE computed_at < now() - interval '90 days';
4805
4956
 
4957
+ -- Floriday-leidend: Floriday-orders reserveren niet (Floriday heeft de verkoop al afgetrokken).
4958
+ DELETE FROM _reserved_demand WHERE true;
4959
+ INSERT INTO _reserved_demand
4960
+ SELECT a.trade_item_key, SUM(a.qty)
4961
+ FROM _reserved_alloc a
4962
+ LEFT JOIN logistics_active.order_items oi ON oi.id = a.order_item_id
4963
+ LEFT JOIN public.orders o ON o.id = oi.order_id
4964
+ WHERE NOT (public.normalize_order_source(o.order_source) = 'FO'
4965
+ AND public.voorraad_floriday_leads_key(a.trade_item_key))
4966
+ GROUP BY a.trade_item_key;
4967
+
4806
4968
  PERFORM set_config('stock.change_source', 'RESERVATION_RECOMPUTE', true);
4807
4969
  UPDATE public.trade_item_stock s
4808
4970
  SET reserved_stock = CEIL(COALESCE(d.qty, 0))::integer,
@@ -4934,6 +5096,7 @@ DECLARE
4934
5096
  v_sum integer;
4935
5097
  v_products uuid[];
4936
5098
  v_pid uuid;
5099
+ v_leads boolean;
4937
5100
  BEGIN
4938
5101
  IF p_trade_item_key IS NULL OR btrim(p_trade_item_key) = '' THEN RETURN; END IF;
4939
5102
  IF current_setting('app.recomputing_reserved', true) = 'on' THEN RETURN; END IF;
@@ -4988,6 +5151,7 @@ BEGIN
4988
5151
  RETURN;
4989
5152
  END IF;
4990
5153
 
5154
+ v_leads := public.voorraad_floriday_leads_key(v_key);
4991
5155
  SELECT CEIL(COALESCE(SUM(COALESCE(oi.quantity, 0)), 0))::integer INTO v_sum
4992
5156
  FROM logistics_active.order_items oi
4993
5157
  JOIN public.orders o ON o.id = oi.order_id
@@ -4995,6 +5159,7 @@ BEGIN
4995
5159
  WHERE COALESCE(NULLIF(btrim(COALESCE(p.floriday_trade_item_id, '')), ''),
4996
5160
  NULLIF(btrim(COALESCE(p.trade_item_key, '')), ''), p.id::text) = v_key
4997
5161
  AND o.status = 'Nog te picken' AND NOT public.logistics_reserved_order_excluded(o.id)
5162
+ AND NOT (public.normalize_order_source(o.order_source) = 'FO' AND v_leads)
4998
5163
  AND NOT EXISTS (SELECT 1 FROM public.product_bom_items b WHERE b.parent_product_id = p.id)
4999
5164
  AND NOT EXISTS (SELECT 1 FROM public.product_groups pg
5000
5165
  WHERE pg.is_active = true AND (pg.id = p.id OR pg.head_product_id = p.id))
@@ -6683,6 +6848,22 @@ END $function$
6683
6848
  ;
6684
6849
  REVOKE ALL ON FUNCTION trg_process_recompute_on_commit() FROM PUBLIC;
6685
6850
  GRANT EXECUTE ON FUNCTION trg_process_recompute_on_commit() TO service_role;
6851
+ CREATE OR REPLACE FUNCTION public.trg_product_packings_derive_fn()
6852
+ RETURNS trigger
6853
+ LANGUAGE plpgsql
6854
+ SECURITY DEFINER
6855
+ SET search_path TO 'public'
6856
+ AS $function$
6857
+ BEGIN
6858
+ IF TG_OP IN ('UPDATE','DELETE') THEN PERFORM public.product_packings_derive(OLD.product_id); END IF;
6859
+ IF TG_OP IN ('INSERT','UPDATE') AND (TG_OP = 'INSERT' OR NEW.product_id <> OLD.product_id) THEN
6860
+ PERFORM public.product_packings_derive(NEW.product_id);
6861
+ END IF;
6862
+ RETURN NULL;
6863
+ END $function$
6864
+ ;
6865
+ REVOKE ALL ON FUNCTION trg_product_packings_derive_fn() FROM PUBLIC;
6866
+ GRANT EXECUTE ON FUNCTION trg_product_packings_derive_fn() TO service_role;
6686
6867
  CREATE OR REPLACE FUNCTION public.trg_products_autolink_fn()
6687
6868
  RETURNS trigger
6688
6869
  LANGUAGE plpgsql
@@ -7284,6 +7465,30 @@ REVOKE ALL ON FUNCTION update_updated_at_column() FROM PUBLIC;
7284
7465
  GRANT EXECUTE ON FUNCTION update_updated_at_column() TO anon;
7285
7466
  GRANT EXECUTE ON FUNCTION update_updated_at_column() TO authenticated;
7286
7467
  GRANT EXECUTE ON FUNCTION update_updated_at_column() TO service_role;
7468
+ 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)
7469
+ RETURNS void
7470
+ LANGUAGE plpgsql
7471
+ SECURITY DEFINER
7472
+ SET search_path TO 'public'
7473
+ AS $function$
7474
+ DECLARE v_code text; v_name text; v_nr text; v_tx bigint := -(txid_current() + 1125899906842624);
7475
+ BEGIN
7476
+ SELECT p.article_number, p.product INTO v_code, v_name FROM public.products p
7477
+ WHERE lower(p.floriday_trade_item_id) = lower(p_key) OR p.trade_item_key = p_key OR 'product:' || p.id::text = p_key OR p.id::text = p_key
7478
+ LIMIT 1;
7479
+ BEGIN EXECUTE 'SELECT order_number::text FROM public.orders WHERE id = $1' INTO v_nr USING p_order_id;
7480
+ EXCEPTION WHEN others THEN v_nr := NULL; END;
7481
+ INSERT INTO public.stock_integrity_alerts(tx_id, alert_kind, item_count, items)
7482
+ VALUES (v_tx, 'FLORIDAY_NOT_UPDATED', 1, jsonb_build_array(jsonb_build_object(
7483
+ 'trade_item_key', p_key, 'article_code', v_code, 'name', v_name, 'qty', p_qty,
7484
+ 'order_id', p_order_id, 'order_item_id', p_order_item_id, 'order_number', v_nr, 'physical_before', NULL, 'change_source', 'ORDER_DELIVERY')))
7485
+ ON CONFLICT (tx_id) DO UPDATE SET item_count = public.stock_integrity_alerts.item_count + 1,
7486
+ items = public.stock_integrity_alerts.items || EXCLUDED.items, updated_at = now();
7487
+ EXCEPTION WHEN others THEN RAISE WARNING '[floriday-not-updated-alert] %', SQLERRM;
7488
+ END $function$
7489
+ ;
7490
+ REVOKE ALL ON FUNCTION voorraad_alert_floriday_not_updated(text,numeric,uuid,uuid) FROM PUBLIC;
7491
+ GRANT EXECUTE ON FUNCTION voorraad_alert_floriday_not_updated(text,numeric,uuid,uuid) TO service_role;
7287
7492
  CREATE OR REPLACE FUNCTION public.voorraad_can_write()
7288
7493
  RETURNS boolean
7289
7494
  LANGUAGE sql
@@ -7380,6 +7585,23 @@ GRANT EXECUTE ON FUNCTION voorraad_external_fulfilled_ids(jsonb) TO PUBLIC;
7380
7585
  GRANT EXECUTE ON FUNCTION voorraad_external_fulfilled_ids(jsonb) TO anon;
7381
7586
  GRANT EXECUTE ON FUNCTION voorraad_external_fulfilled_ids(jsonb) TO authenticated;
7382
7587
  GRANT EXECUTE ON FUNCTION voorraad_external_fulfilled_ids(jsonb) TO service_role;
7588
+ CREATE OR REPLACE FUNCTION public.voorraad_floriday_leads_key(p_key text)
7589
+ RETURNS boolean
7590
+ LANGUAGE sql
7591
+ STABLE SECURITY DEFINER
7592
+ SET search_path TO 'public'
7593
+ AS $function$
7594
+ SELECT CASE WHEN p_key IS NULL OR btrim(p_key) = '' THEN false ELSE EXISTS (
7595
+ SELECT 1 FROM public.products p
7596
+ JOIN public.floriday_inventory_settings s ON s.product_id = p.id AND s.erp_leading = false
7597
+ WHERE (lower(p.floriday_trade_item_id) = lower(btrim(p_key)) OR p.trade_item_key = btrim(p_key)
7598
+ OR p.id::text = btrim(p_key) OR 'product:' || p.id::text = btrim(p_key))
7599
+ AND s.connection_id = COALESCE((SELECT c.connection_id FROM public.get_active_floriday_context() c), s.connection_id)
7600
+ ) END
7601
+ $function$
7602
+ ;
7603
+ REVOKE ALL ON FUNCTION voorraad_floriday_leads_key(text) FROM PUBLIC;
7604
+ GRANT EXECUTE ON FUNCTION voorraad_floriday_leads_key(text) TO service_role;
7383
7605
  CREATE OR REPLACE FUNCTION public.voorraad_hook_base_url()
7384
7606
  RETURNS text
7385
7607
  LANGUAGE plpgsql
@@ -7485,6 +7707,35 @@ END $function$
7485
7707
  ;
7486
7708
  REVOKE ALL ON FUNCTION voorraad_hook_switch_jobs() FROM PUBLIC;
7487
7709
  GRANT EXECUTE ON FUNCTION voorraad_hook_switch_jobs() TO service_role;
7710
+ CREATE OR REPLACE FUNCTION public.voorraad_mark_floriday_sold_before_switch(p_product_id uuid)
7711
+ RETURNS integer
7712
+ LANGUAGE plpgsql
7713
+ SECURITY DEFINER
7714
+ SET search_path TO 'public'
7715
+ AS $function$
7716
+ DECLARE r record; n int := 0; v_key text;
7717
+ BEGIN
7718
+ SELECT COALESCE(NULLIF(btrim(p.floriday_trade_item_id),''), p.trade_item_key, 'product:'||p.id::text) INTO v_key
7719
+ FROM public.products p WHERE p.id = p_product_id;
7720
+ FOR r IN
7721
+ SELECT oi.id, oi.order_id
7722
+ FROM logistics_active.order_items oi JOIN public.orders o ON o.id = oi.order_id
7723
+ WHERE oi.product_id = p_product_id
7724
+ AND public.normalize_order_source(o.order_source) = 'FO'
7725
+ AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log m WHERE m.order_item_id = oi.id AND m.mutation_type = 'consumption')
7726
+ LOOP
7727
+ INSERT INTO public.stock_mutation_log(order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
7728
+ VALUES (r.order_id, r.id, 'FO', 'consumption_done', 0, 'Verwerkt in Floriday-stand', auth.uid())
7729
+ ON CONFLICT (order_item_id) WHERE mutation_type = 'consumption_done' AND order_item_id IS NOT NULL DO NOTHING;
7730
+ INSERT INTO public.stock_mutation_log(trade_item_key, order_id, order_item_id, order_source, mutation_type, qty_delta, reason, created_by)
7731
+ VALUES (v_key, r.order_id, r.id, 'FO', 'consumption', 0, 'Verwerkt in Floriday-stand bij overstap naar eigen voorraad leidend (niet opnieuw afgeboekt)', auth.uid());
7732
+ n := n + 1;
7733
+ END LOOP;
7734
+ RETURN n;
7735
+ END $function$
7736
+ ;
7737
+ REVOKE ALL ON FUNCTION voorraad_mark_floriday_sold_before_switch(uuid) FROM PUBLIC;
7738
+ GRANT EXECUTE ON FUNCTION voorraad_mark_floriday_sold_before_switch(uuid) TO service_role;
7488
7739
  CREATE OR REPLACE FUNCTION public.voorraad_production_enabled()
7489
7740
  RETURNS boolean
7490
7741
  LANGUAGE sql
@@ -7650,6 +7901,7 @@ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'invento
7650
7901
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'inventory_stock_snapshots_pkey' AND conrelid = 'public.inventory_stock_snapshots'::regclass) THEN ALTER TABLE public.inventory_stock_snapshots ADD CONSTRAINT inventory_stock_snapshots_pkey PRIMARY KEY (id); END IF; END $c$;
7651
7902
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'order_fust_bookings_pkey' AND conrelid = 'public.order_fust_bookings'::regclass) THEN ALTER TABLE public.order_fust_bookings ADD CONSTRAINT order_fust_bookings_pkey PRIMARY KEY (id); END IF; END $c$;
7652
7903
  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$;
7904
+ 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$;
7653
7905
  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$;
7654
7906
  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$;
7655
7907
  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$;
@@ -7710,6 +7962,10 @@ ALTER TABLE public.inventory_recompute_limits DROP CONSTRAINT IF EXISTS inventor
7710
7962
  ALTER TABLE public.inventory_recompute_limits ADD CONSTRAINT inventory_recompute_limits_max_parents_inline_check CHECK ((max_parents_inline >= 0));
7711
7963
  ALTER TABLE public.inventory_stock_snapshots DROP CONSTRAINT IF EXISTS inventory_stock_snapshots_gran_check;
7712
7964
  ALTER TABLE public.inventory_stock_snapshots ADD CONSTRAINT inventory_stock_snapshots_gran_check CHECK ((granularity = ANY (ARRAY['hour'::text, 'day'::text])));
7965
+ ALTER TABLE public.product_packings DROP CONSTRAINT IF EXISTS product_packings_positive_check;
7966
+ 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))));
7967
+ ALTER TABLE public.product_packings DROP CONSTRAINT IF EXISTS product_packings_source_check;
7968
+ ALTER TABLE public.product_packings ADD CONSTRAINT product_packings_source_check CHECK ((source = ANY (ARRAY['floriday'::text, 'eigen'::text])));
7713
7969
  ALTER TABLE public.stock_import_chunks DROP CONSTRAINT IF EXISTS stock_import_chunks_status_check;
7714
7970
  ALTER TABLE public.stock_import_chunks ADD CONSTRAINT stock_import_chunks_status_check CHECK ((status = ANY (ARRAY['wacht'::text, 'klaar'::text, 'ongedaan'::text])));
7715
7971
  ALTER TABLE public.stock_import_rows DROP CONSTRAINT IF EXISTS stock_import_rows_status_check;
@@ -7717,7 +7973,7 @@ ALTER TABLE public.stock_import_rows ADD CONSTRAINT stock_import_rows_status_che
7717
7973
  ALTER TABLE public.stock_import_runs DROP CONSTRAINT IF EXISTS stock_import_runs_status_check;
7718
7974
  ALTER TABLE public.stock_import_runs ADD CONSTRAINT stock_import_runs_status_check CHECK ((status = ANY (ARRAY['ontvangen'::text, 'bezig'::text, 'gestopt'::text, 'voltooid'::text, 'ongedaan'::text, 'deels_ongedaan'::text])));
7719
7975
  ALTER TABLE public.stock_integrity_alerts DROP CONSTRAINT IF EXISTS stock_integrity_alerts_kind_check;
7720
- ALTER TABLE public.stock_integrity_alerts ADD CONSTRAINT stock_integrity_alerts_kind_check CHECK ((alert_kind = ANY (ARRAY['ZERO_BATCH'::text, 'NEGATIVE_STOCK'::text])));
7976
+ ALTER TABLE public.stock_integrity_alerts ADD CONSTRAINT stock_integrity_alerts_kind_check CHECK ((alert_kind = ANY (ARRAY['ZERO_BATCH'::text, 'NEGATIVE_STOCK'::text, 'FLORIDAY_NOT_UPDATED'::text])));
7721
7977
  ALTER TABLE public.stock_mutation_log DROP CONSTRAINT IF EXISTS stock_mutation_log_mutation_type_check;
7722
7978
  ALTER TABLE public.stock_mutation_log ADD CONSTRAINT stock_mutation_log_mutation_type_check CHECK ((mutation_type = ANY (ARRAY['reservation'::text, 'reservation_release'::text, 'consumption'::text, 'stock_check'::text, 'reservation_skipped'::text, 'consumption_skipped'::text, 'consumption_blocked'::text, 'production_consume'::text, 'production_output'::text, 'production_reverse'::text, 'manual_correction'::text, 'order_sync'::text, 'import'::text, 'production'::text, 'stock_update'::text, 'reservation_recompute'::text, 'unknown'::text, 'consumption_done'::text, 'new_article'::text])));
7723
7979
  ALTER TABLE public.trade_item_drafts DROP CONSTRAINT IF EXISTS trade_item_drafts_link_status_chk;
@@ -7727,6 +7983,8 @@ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'florida
7727
7983
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'floriday_vbn_required_characteristics_updated_by_fkey' AND conrelid = 'public.floriday_vbn_required_characteristics'::regclass) THEN ALTER TABLE public.floriday_vbn_required_characteristics ADD CONSTRAINT floriday_vbn_required_characteristics_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES auth.users(id) ON DELETE SET NULL; END IF; END $c$;
7728
7984
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fust_stock_mutations_fust_type_id_fkey' AND conrelid = 'public.fust_stock_mutations'::regclass) THEN ALTER TABLE public.fust_stock_mutations ADD CONSTRAINT fust_stock_mutations_fust_type_id_fkey FOREIGN KEY (fust_type_id) REFERENCES fust_types(id) ON DELETE CASCADE; END IF; END $c$;
7729
7985
  DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'order_fust_bookings_fust_type_id_fkey' AND conrelid = 'public.order_fust_bookings'::regclass) THEN ALTER TABLE public.order_fust_bookings ADD CONSTRAINT order_fust_bookings_fust_type_id_fkey FOREIGN KEY (fust_type_id) REFERENCES fust_types(id) ON DELETE CASCADE; END IF; END $c$;
7986
+ 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$;
7987
+ 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$;
7730
7988
  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$;
7731
7989
  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$;
7732
7990
  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$;
@@ -7759,6 +8017,7 @@ CREATE INDEX IF NOT EXISTS idx_voorraad_buffer_state_closed_at ON public.voorraa
7759
8017
  CREATE INDEX IF NOT EXISTS idx_voorraad_event_log_processed_at ON public.voorraad_logistics_event_log USING btree (processed_at DESC);
7760
8018
  CREATE INDEX IF NOT EXISTS idx_voorraad_publish_attempts_user_created ON public.voorraad_publish_attempts USING btree (user_id, created_at DESC);
7761
8019
  CREATE INDEX IF NOT EXISTS inventory_stock_snapshots_time_idx ON public.inventory_stock_snapshots USING btree (connection_id, snapshot_at DESC);
8020
+ CREATE INDEX IF NOT EXISTS product_packings_product_idx ON public.product_packings USING btree (product_id);
7762
8021
  CREATE INDEX IF NOT EXISTS stock_import_rows_chunk_idx ON public.stock_import_rows USING btree (run_id, chunk_no);
7763
8022
  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);
7764
8023
  CREATE INDEX IF NOT EXISTS stock_import_runs_user_created_idx ON public.stock_import_runs USING btree (user_id, created_at DESC);
@@ -7767,6 +8026,7 @@ CREATE INDEX IF NOT EXISTS trade_item_prices_lookup ON public.trade_item_prices
7767
8026
  CREATE INDEX IF NOT EXISTS trade_item_stock_trade_item_key_idx ON public.trade_item_stock USING btree (trade_item_key);
7768
8027
  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]));
7769
8028
  CREATE UNIQUE INDEX IF NOT EXISTS fust_types_user_key_global_idx ON public.fust_types USING btree (user_id, fust_key) WHERE (connection_id IS NULL);
8029
+ CREATE UNIQUE INDEX IF NOT EXISTS product_packings_one_primary ON public.product_packings USING btree (product_id) WHERE is_primary;
7770
8030
  CREATE UNIQUE INDEX IF NOT EXISTS stock_import_runs_one_active ON public.stock_import_runs USING btree (COALESCE(connection_id, '00000000-0000-0000-0000-000000000000'::uuid)) WHERE (status = ANY (ARRAY['ontvangen'::text, 'bezig'::text, 'gestopt'::text]));
7771
8031
  CREATE UNIQUE INDEX IF NOT EXISTS trade_item_prices_group_item_uidx ON public.trade_item_prices USING btree (price_group_id, trade_item_key) WHERE (price_group_id IS NOT NULL);
7772
8032
  CREATE UNIQUE INDEX IF NOT EXISTS trade_item_prices_unique_base ON public.trade_item_prices USING btree (user_id, trade_item_key) WHERE (price_group_id IS NULL);
@@ -7842,6 +8102,10 @@ ALTER TABLE public.product_merge_log ENABLE ROW LEVEL SECURITY;
7842
8102
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_merge_log TO anon;
7843
8103
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_merge_log TO authenticated;
7844
8104
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_merge_log TO service_role;
8105
+ ALTER TABLE public.product_packings ENABLE ROW LEVEL SECURITY;
8106
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_packings TO anon;
8107
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_packings TO authenticated;
8108
+ GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_packings TO service_role;
7845
8109
  ALTER TABLE public.product_tray_stickers ENABLE ROW LEVEL SECURITY;
7846
8110
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_tray_stickers TO anon;
7847
8111
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_tray_stickers TO authenticated;
@@ -7974,6 +8238,10 @@ DROP POLICY IF EXISTS "authenticated can insert merge log" ON public.product_mer
7974
8238
  CREATE POLICY "authenticated can insert merge log" ON public.product_merge_log AS PERMISSIVE FOR INSERT TO authenticated WITH CHECK (true);
7975
8239
  DROP POLICY IF EXISTS "authenticated can read merge log" ON public.product_merge_log;
7976
8240
  CREATE POLICY "authenticated can read merge log" ON public.product_merge_log AS PERMISSIVE FOR SELECT TO authenticated USING (true);
8241
+ DROP POLICY IF EXISTS product_packings_read ON public.product_packings;
8242
+ CREATE POLICY product_packings_read ON public.product_packings AS PERMISSIVE FOR SELECT TO authenticated USING (true);
8243
+ DROP POLICY IF EXISTS product_packings_write ON public.product_packings;
8244
+ CREATE POLICY product_packings_write ON public.product_packings AS PERMISSIVE FOR ALL TO authenticated USING (voorraad_can_write()) WITH CHECK (voorraad_can_write());
7977
8245
  DROP POLICY IF EXISTS "Ingelogden beheren traystickers" ON public.product_tray_stickers;
7978
8246
  CREATE POLICY "Ingelogden beheren traystickers" ON public.product_tray_stickers AS PERMISSIVE FOR ALL TO authenticated USING (true) WITH CHECK (true);
7979
8247
  DROP POLICY IF EXISTS "Eigenaar leest importporties" ON public.stock_import_chunks;
@@ -8117,6 +8385,10 @@ DO $g$ BEGIN IF to_regclass('public.product_group_items') IS NULL THEN RAISE NOT
8117
8385
  DO $g$ BEGIN IF to_regclass('public.product_group_items') IS NULL THEN RAISE NOTICE 'Voorraad: product_group_items (core) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$CREATE TRIGGER trg_structure_change_recompute AFTER INSERT OR DELETE OR UPDATE ON public.product_group_items FOR EACH ROW EXECUTE FUNCTION trg_structure_change_recompute()$q$; END IF; END $g$;
8118
8386
  DO $g$ BEGIN IF to_regclass('public.product_groups') IS NULL THEN RAISE NOTICE 'Voorraad: product_groups (core) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$CREATE TRIGGER trg_check_product_structure BEFORE INSERT OR UPDATE ON public.product_groups FOR EACH ROW EXECUTE FUNCTION trg_check_product_structure()$q$; END IF; END $g$;
8119
8387
  DO $g$ BEGIN IF to_regclass('public.product_groups') IS NULL THEN RAISE NOTICE 'Voorraad: product_groups (core) ontbreekt, triggers overgeslagen; draai de schema-update opnieuw na die module'; ELSE EXECUTE $q$CREATE TRIGGER trg_structure_change_recompute AFTER INSERT OR DELETE OR UPDATE OF is_active, head_product_id, head_trade_item_floriday_id ON public.product_groups FOR EACH ROW EXECUTE FUNCTION trg_structure_change_recompute()$q$; END IF; END $g$;
8388
+ DROP TRIGGER IF EXISTS product_packings_derive_trg ON public.product_packings;
8389
+ CREATE TRIGGER product_packings_derive_trg AFTER INSERT OR DELETE OR UPDATE ON public.product_packings FOR EACH ROW EXECUTE FUNCTION trg_product_packings_derive_fn();
8390
+ DROP TRIGGER IF EXISTS product_packings_updated_at ON public.product_packings;
8391
+ CREATE TRIGGER product_packings_updated_at BEFORE UPDATE ON public.product_packings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
8120
8392
  DROP TRIGGER IF EXISTS trg_product_tray_stickers_touch ON public.product_tray_stickers;
8121
8393
  CREATE TRIGGER trg_product_tray_stickers_touch BEFORE UPDATE ON public.product_tray_stickers FOR EACH ROW EXECUTE FUNCTION product_tray_stickers_touch();
8122
8394
  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$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$;