@flowselections/floriday-voorraad 1.0.94 → 1.0.97

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/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/VoorraadTable.d.ts.map +1 -1
  8. package/dist-lib/components/voorraad/VoorraadTable.js +16 -1
  9. package/dist-lib/integrations/supabase/types.d.ts +294 -18
  10. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  11. package/dist-lib/lib/__tests__/product-packings.test.d.ts +2 -0
  12. package/dist-lib/lib/__tests__/product-packings.test.d.ts.map +1 -0
  13. package/dist-lib/lib/__tests__/product-packings.test.js +42 -0
  14. package/dist-lib/lib/ensure-article-codes.d.ts.map +1 -1
  15. package/dist-lib/lib/ensure-article-codes.js +2 -12
  16. package/dist-lib/lib/floriday-payload.d.ts +15 -0
  17. package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
  18. package/dist-lib/lib/floriday-payload.js +36 -10
  19. package/dist-lib/lib/fusten.d.ts.map +1 -1
  20. package/dist-lib/lib/fusten.js +6 -1
  21. package/dist-lib/lib/product-packing.d.ts +8 -5
  22. package/dist-lib/lib/product-packing.d.ts.map +1 -1
  23. package/dist-lib/lib/product-packing.js +44 -36
  24. package/dist-lib/lib/product-packings.d.ts +33 -0
  25. package/dist-lib/lib/product-packings.d.ts.map +1 -0
  26. package/dist-lib/lib/product-packings.js +89 -0
  27. package/dist-lib/server-hooks/floriday-follow.d.ts.map +1 -1
  28. package/dist-lib/server-hooks/floriday-follow.js +11 -1
  29. package/package.json +1 -1
  30. package/sql/AGENTS.md +5 -0
  31. package/sql/floriday-voorraad-schema.sql +182 -5
@@ -1,4 +1,4 @@
1
- -- @flowselections/floriday-voorraad 1.0.93 – 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;
@@ -2806,7 +2824,7 @@ CREATE OR REPLACE FUNCTION public.floriday_desired_stock_regression_check()
2806
2824
  AS $function$
2807
2825
  WITH b AS (
2808
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,
2809
- NULLIF(p.plants_per_crate,0) AS pack,
2827
+ public.product_primary_pieces(p.id) AS pack,
2810
2828
  (SELECT GREATEST(0, floor(ts.physical_stock - ts.reserved_stock))::int FROM public.trade_item_stock ts
2811
2829
  WHERE ts.connection_id = s.connection_id
2812
2830
  AND ts.trade_item_key = COALESCE(NULLIF(btrim(p.floriday_trade_item_id),''), NULLIF(btrim(p.trade_item_key),''))) AS avail
@@ -4490,6 +4508,128 @@ END $function$
4490
4508
  ;
4491
4509
  REVOKE ALL ON FUNCTION product_own_available(uuid,uuid) FROM PUBLIC;
4492
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;
4493
4633
  CREATE OR REPLACE FUNCTION public.product_set_depth_above(p_product_id uuid, p_path uuid[] DEFAULT '{}'::uuid[])
4494
4634
  RETURNS integer
4495
4635
  LANGUAGE plpgsql
@@ -6708,6 +6848,22 @@ END $function$
6708
6848
  ;
6709
6849
  REVOKE ALL ON FUNCTION trg_process_recompute_on_commit() FROM PUBLIC;
6710
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;
6711
6867
  CREATE OR REPLACE FUNCTION public.trg_products_autolink_fn()
6712
6868
  RETURNS trigger
6713
6869
  LANGUAGE plpgsql
@@ -7745,6 +7901,7 @@ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'invento
7745
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$;
7746
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$;
7747
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$;
7748
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$;
7749
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$;
7750
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$;
@@ -7805,6 +7962,10 @@ ALTER TABLE public.inventory_recompute_limits DROP CONSTRAINT IF EXISTS inventor
7805
7962
  ALTER TABLE public.inventory_recompute_limits ADD CONSTRAINT inventory_recompute_limits_max_parents_inline_check CHECK ((max_parents_inline >= 0));
7806
7963
  ALTER TABLE public.inventory_stock_snapshots DROP CONSTRAINT IF EXISTS inventory_stock_snapshots_gran_check;
7807
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])));
7808
7969
  ALTER TABLE public.stock_import_chunks DROP CONSTRAINT IF EXISTS stock_import_chunks_status_check;
7809
7970
  ALTER TABLE public.stock_import_chunks ADD CONSTRAINT stock_import_chunks_status_check CHECK ((status = ANY (ARRAY['wacht'::text, 'klaar'::text, 'ongedaan'::text])));
7810
7971
  ALTER TABLE public.stock_import_rows DROP CONSTRAINT IF EXISTS stock_import_rows_status_check;
@@ -7822,6 +7983,8 @@ DO $c$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'florida
7822
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$;
7823
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$;
7824
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$;
7825
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$;
7826
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$;
7827
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$;
@@ -7854,6 +8017,7 @@ CREATE INDEX IF NOT EXISTS idx_voorraad_buffer_state_closed_at ON public.voorraa
7854
8017
  CREATE INDEX IF NOT EXISTS idx_voorraad_event_log_processed_at ON public.voorraad_logistics_event_log USING btree (processed_at DESC);
7855
8018
  CREATE INDEX IF NOT EXISTS idx_voorraad_publish_attempts_user_created ON public.voorraad_publish_attempts USING btree (user_id, created_at DESC);
7856
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);
7857
8021
  CREATE INDEX IF NOT EXISTS stock_import_rows_chunk_idx ON public.stock_import_rows USING btree (run_id, chunk_no);
7858
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);
7859
8023
  CREATE INDEX IF NOT EXISTS stock_import_runs_user_created_idx ON public.stock_import_runs USING btree (user_id, created_at DESC);
@@ -7862,6 +8026,7 @@ CREATE INDEX IF NOT EXISTS trade_item_prices_lookup ON public.trade_item_prices
7862
8026
  CREATE INDEX IF NOT EXISTS trade_item_stock_trade_item_key_idx ON public.trade_item_stock USING btree (trade_item_key);
7863
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]));
7864
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;
7865
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]));
7866
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);
7867
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);
@@ -7937,6 +8102,10 @@ ALTER TABLE public.product_merge_log ENABLE ROW LEVEL SECURITY;
7937
8102
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_merge_log TO anon;
7938
8103
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_merge_log TO authenticated;
7939
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;
7940
8109
  ALTER TABLE public.product_tray_stickers ENABLE ROW LEVEL SECURITY;
7941
8110
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_tray_stickers TO anon;
7942
8111
  GRANT DELETE, INSERT, REFERENCES, SELECT, TRIGGER, TRUNCATE, UPDATE ON public.product_tray_stickers TO authenticated;
@@ -8069,6 +8238,10 @@ DROP POLICY IF EXISTS "authenticated can insert merge log" ON public.product_mer
8069
8238
  CREATE POLICY "authenticated can insert merge log" ON public.product_merge_log AS PERMISSIVE FOR INSERT TO authenticated WITH CHECK (true);
8070
8239
  DROP POLICY IF EXISTS "authenticated can read merge log" ON public.product_merge_log;
8071
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());
8072
8245
  DROP POLICY IF EXISTS "Ingelogden beheren traystickers" ON public.product_tray_stickers;
8073
8246
  CREATE POLICY "Ingelogden beheren traystickers" ON public.product_tray_stickers AS PERMISSIVE FOR ALL TO authenticated USING (true) WITH CHECK (true);
8074
8247
  DROP POLICY IF EXISTS "Eigenaar leest importporties" ON public.stock_import_chunks;
@@ -8212,6 +8385,10 @@ DO $g$ BEGIN IF to_regclass('public.product_group_items') IS NULL THEN RAISE NOT
8212
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$;
8213
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$;
8214
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();
8215
8392
  DROP TRIGGER IF EXISTS trg_product_tray_stickers_touch ON public.product_tray_stickers;
8216
8393
  CREATE TRIGGER trg_product_tray_stickers_touch BEFORE UPDATE ON public.product_tray_stickers FOR EACH ROW EXECUTE FUNCTION product_tray_stickers_touch();
8217
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$;