@flowselections/logistiek-engine 1.0.212 → 1.0.216

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.
@@ -36719,3 +36719,466 @@ RETURNS TABLE(punt text, ok boolean, uitleg text) LANGUAGE sql STABLE SECURITY D
36719
36719
  'Verouderde kar toont geen lagen en %'
36720
36720
  $$;
36721
36721
  GRANT EXECUTE ON FUNCTION public.logistics_install_check_210() TO authenticated, service_role;
36722
+
36723
+
36724
+ -- ============================================================
36725
+ -- 1.0.213
36726
+ -- ============================================================
36727
+ -- Punt 1: geldige buffer nooit splitsen in de voorbeeldberekening.
36728
+ CREATE OR REPLACE FUNCTION public.logistics_rebuffer_simulate_open()
36729
+ RETURNS TABLE(id uuid, order_number text, delivery_date text, buffer_id uuid, cur_label text, new_label text, changed boolean, reason text)
36730
+ LANGUAGE plpgsql
36731
+ STABLE SECURITY DEFINER
36732
+ SET search_path TO 'public'
36733
+ AS $function$
36734
+ BEGIN
36735
+ PERFORM public.logistics_acc_guard('server', 'logistics_rebuffer_simulate_open');
36736
+ RETURN QUERY
36737
+ WITH c AS MATERIALIZED (
36738
+ SELECT o.id, o.order_number, o.delivery_date, o.buffer_id, public.logistics_buffer_target(o.id) AS t,
36739
+ public.logistics_order_afgemeld(o.id) AS afgemeld,
36740
+ (o.buffer_id IS NOT NULL AND coalesce(public.logistics_buffer_frozen(o.buffer_id), false)) AS buf_vast
36741
+ FROM public.orders o JOIN public.logistics_rebuffer_candidates() k ON k.order_id = o.id
36742
+ ), m AS (
36743
+ SELECT c.*,
36744
+ (c.afgemeld OR c.buf_vast) AS beschermd,
36745
+ (SELECT array_agg(x.id ORDER BY x.id) FROM public.orders x WHERE c.buffer_id IS NOT NULL AND x.buffer_id = c.buffer_id AND x.deleted_at IS NULL AND x.id <> c.id) AS cur_mates,
36746
+ (SELECT array_agg(x.id ORDER BY x.id) FROM public.orders x WHERE c.t->>'soort' = 'buffer' AND x.buffer_id = (c.t->>'bufferId')::uuid AND x.deleted_at IS NULL AND x.id <> c.id) AS new_mates
36747
+ FROM c
36748
+ ), l AS (
36749
+ SELECT m.*,
36750
+ CASE WHEN m.buffer_id IS NULL THEN 'Geen buffer (eigen kar)'
36751
+ WHEN m.cur_mates IS NULL THEN 'Eigen buffer'
36752
+ ELSE 'Buffer met ' || (SELECT string_agg(x.order_number, ', ' ORDER BY x.order_number) FROM public.orders x WHERE x.id = ANY(m.cur_mates)) END AS cur_l
36753
+ FROM m
36754
+ )
36755
+ SELECT l.id, l.order_number, l.delivery_date, l.buffer_id, l.cur_l,
36756
+ CASE WHEN l.beschermd THEN l.cur_l
36757
+ WHEN l.t->>'soort' IN ('solo','uitgesloten') THEN 'Geen buffer (eigen kar)'
36758
+ WHEN l.new_mates IS NULL THEN 'Eigen buffer'
36759
+ ELSE 'Buffer met ' || (SELECT string_agg(x.order_number, ', ' ORDER BY x.order_number) FROM public.orders x WHERE x.id = ANY(l.new_mates)) END,
36760
+ CASE WHEN l.beschermd THEN false
36761
+ ELSE coalesce(l.cur_mates, '{}') IS DISTINCT FROM coalesce(l.new_mates, '{}')
36762
+ OR ((l.t->>'soort') IN ('solo','uitgesloten') AND l.buffer_id IS NOT NULL AND l.cur_mates IS NOT NULL) END,
36763
+ CASE WHEN l.afgemeld THEN 'Ongewijzigd: de order is al verzonden of afgemeld; een bestaande buffer wordt niet gesplitst.'
36764
+ WHEN l.buf_vast THEN 'Ongewijzigd: de buffer ligt al vast (geprint, gescand of verstuurd) en wordt niet gesplitst.'
36765
+ WHEN jsonb_array_length(coalesce(l.t->'overgeslagen','[]'::jsonb)) > 0
36766
+ THEN 'Niet samengevoegd: buffer van ' || (SELECT string_agg(v, '; ') FROM jsonb_array_elements_text(l.t->'overgeslagen') v)
36767
+ || ' ligt vast (geprint, gescand of verstuurd).'
36768
+ WHEN l.t->>'soort' = 'uitgesloten' THEN 'Deze order is uitgesloten van combineren (handmatig uitgesloten van indeling).'
36769
+ WHEN l.t->>'soort' = 'solo' THEN 'Volgens de huidige combineerregels krijgt deze order een eigen kar (' || coalesce(l.t->>'reason','onbekend') || ').'
36770
+ ELSE 'Volgens de huidige combineerregels (' || coalesce(l.t->>'reason','onbekend') || ').' END
36771
+ FROM l;
36772
+ END $function$;
36773
+
36774
+ -- Punt 3: oordeel per product, ladingdrager uit de hoofdverpakking eerst.
36775
+ CREATE OR REPLACE FUNCTION public.logistics_product_oordeel_many(p_product_ids uuid[])
36776
+ RETURNS TABLE(product_id uuid, oordeel text, tekst text, "actieUrl" text)
36777
+ LANGUAGE plpgsql
36778
+ STABLE SECURITY DEFINER
36779
+ SET search_path TO 'public'
36780
+ AS $function$
36781
+ #variable_conflict use_column
36782
+ DECLARE r record; c record; cd record; v_carrier text; hv jsonb; rs jsonb; v_code text; v_lfout text; ls int; fh numeric; ph numeric; cpl numeric; lpc numeric; laag numeric;
36783
+ blok text[]; waar text[]; mist text[]; hmist text[]; naam text; bron text;
36784
+ c_url constant text := '/logistiek-engine/beheer#gegevens-voor-karindeling';
36785
+ BEGIN
36786
+ PERFORM public.logistics_acc_guard('ingelogd', 'logistics_product_oordeel_many');
36787
+ IF coalesce(cardinality(p_product_ids), 0) = 0 THEN RETURN; END IF;
36788
+ IF cardinality(p_product_ids) > 1000 THEN
36789
+ RAISE EXCEPTION 'Maximaal 1000 producten per aanroep (ontvangen: %).', cardinality(p_product_ids) USING ERRCODE = '22023';
36790
+ END IF;
36791
+ SELECT nullif(btrim(coalesce(s.settings->'karrenindeling'->'fields'->>'default_carrier_type', s.settings->'karrenindeling'->>'default_carrier_type')), '')
36792
+ INTO v_carrier FROM public.logistics_rule_configs s WHERE s.is_active ORDER BY s.updated_at DESC LIMIT 1;
36793
+ SELECT t.code, t.name, t.layer_length_cm, t.layer_width_cm, t.max_height_cm, t.max_crates_per_layer, t.layers_per_carrier INTO cd
36794
+ FROM public.load_carrier_types t
36795
+ WHERE t.is_active AND (t.code = v_carrier OR (v_carrier IS NULL AND t.is_default))
36796
+ ORDER BY (t.code = v_carrier) DESC NULLS LAST LIMIT 1;
36797
+
36798
+ FOR r IN SELECT * FROM public.logistics_data_gereedheid(p_product_ids) LOOP
36799
+ blok := '{}'; waar := '{}'; hmist := '{}';
36800
+ mist := array_remove(coalesce(r.ontbrekend, '{}'), 'weight');
36801
+ hv := public.logistics_product_hoofdverpakking(r.product_id);
36802
+ v_code := NULL; v_lfout := NULL;
36803
+ IF hv IS NOT NULL AND NOT coalesce((hv->>'aanwezig')::boolean, false) THEN
36804
+ mist := mist || 'belading niet ingesteld'::text;
36805
+ END IF;
36806
+ IF hv IS NOT NULL AND coalesce((hv->>'aanwezig')::boolean, false) AND nullif(hv->>'ladingdrager', '') IS NOT NULL THEN
36807
+ rs := public.logistics_ladingdrager_resolve(hv->>'ladingdrager');
36808
+ v_code := rs->>'code'; v_lfout := rs->>'fout';
36809
+ END IF;
36810
+ SELECT NULL::text AS code, NULL::text AS name, NULL::int AS layer_length_cm, NULL::int AS layer_width_cm, NULL::int AS max_height_cm, NULL::int AS max_crates_per_layer, NULL::int AS layers_per_carrier INTO c;
36811
+ IF v_code IS NOT NULL THEN
36812
+ SELECT t.code, t.name, t.layer_length_cm, t.layer_width_cm, t.max_height_cm, t.max_crates_per_layer, t.layers_per_carrier INTO c
36813
+ FROM public.load_carrier_types t WHERE t.is_active AND t.code = v_code LIMIT 1;
36814
+ ELSIF v_lfout IS NULL THEN
36815
+ SELECT cd.code, cd.name, cd.layer_length_cm, cd.layer_width_cm, cd.max_height_cm, cd.max_crates_per_layer, cd.layers_per_carrier INTO c;
36816
+ ELSE
36817
+ mist := mist || v_lfout;
36818
+ END IF;
36819
+ naam := coalesce(c.name, c.code, 'de ladingdrager');
36820
+ bron := coalesce(r.velden->'crates_per_layer'->>'bron', 'product');
36821
+ cpl := coalesce(nullif(r.velden->'crates_per_layer'->>'waarde', '')::numeric, 0);
36822
+ lpc := coalesce(nullif(r.velden->'layers_per_cart'->>'waarde', '')::numeric, 0);
36823
+ fh := nullif(r.velden->'fust_height_mm'->>'waarde', '')::numeric / 10.0;
36824
+ ph := nullif(r.velden->'plant_height_cm'->>'waarde', '')::numeric;
36825
+ ls := public.logistics_max_fusten_per_laag(c.layer_length_cm, c.layer_width_cm,
36826
+ nullif(r.velden->'fust_length_mm'->>'waarde', '')::numeric / 10.0, nullif(r.velden->'fust_width_mm'->>'waarde', '')::numeric / 10.0);
36827
+ IF coalesce(fh,0) > 0 AND coalesce(ph,0) > 0 AND coalesce(c.max_height_cm,0) > 0 THEN
36828
+ laag := greatest(fh, ph);
36829
+ IF lpc * laag > c.max_height_cm + 1e-9 AND ph >= fh AND coalesce(r.velden->'plant_height_cm'->>'bron', '') ILIKE 'floricode%' THEN
36830
+ waar := waar || format('planthoogte volgens Floricode; controleer: %s lagen × %s cm = %s cm, hoger dan %s (%s cm)',
36831
+ lpc, trim_scale(round(laag,1)), trim_scale(round(lpc*laag,1)), naam, trim_scale(round(c.max_height_cm::numeric,1)));
36832
+ ELSIF lpc * laag > c.max_height_cm + 1e-9 THEN
36833
+ blok := blok || format('%s lagen × %s cm = %s cm, terwijl %s %s cm hoog is; er passen maximaal %s lagen',
36834
+ lpc, trim_scale(round(laag,1)), trim_scale(round(lpc*laag,1)), naam, trim_scale(round(c.max_height_cm::numeric,1)), floor(c.max_height_cm / laag));
36835
+ END IF;
36836
+ ELSE
36837
+ IF coalesce(fh,0) <= 0 THEN hmist := hmist || 'fusthoogte'::text; END IF;
36838
+ IF coalesce(ph,0) <= 0 THEN hmist := hmist || 'planthoogte'::text; END IF;
36839
+ IF coalesce(c.max_height_cm,0) <= 0 THEN hmist := hmist || ('hoogte van ' || naam); END IF;
36840
+ END IF;
36841
+ IF public.logistics_laag_tolerantie_tekst(c.layer_length_cm, c.layer_width_cm,
36842
+ nullif(r.velden->'fust_length_mm'->>'waarde', '')::numeric / 10.0, nullif(r.velden->'fust_width_mm'->>'waarde', '')::numeric / 10.0, cpl) IS NOT NULL THEN
36843
+ waar := waar || public.logistics_laag_tolerantie_tekst(c.layer_length_cm, c.layer_width_cm,
36844
+ nullif(r.velden->'fust_length_mm'->>'waarde', '')::numeric / 10.0, nullif(r.velden->'fust_width_mm'->>'waarde', '')::numeric / 10.0, cpl);
36845
+ END IF;
36846
+ IF ls IS NOT NULL AND cpl > ls THEN
36847
+ blok := blok || format('%s fust per laag (bron: %s), maar op een laag van %s passen er maximaal %s', cpl, bron, naam, ls);
36848
+ END IF;
36849
+ IF coalesce(c.max_crates_per_layer,0) > 0 AND cpl > c.max_crates_per_layer THEN
36850
+ blok := blok || format('%s fust per laag (bron: %s), maar %s staat maximaal %s fust per laag toe', cpl, bron, naam, c.max_crates_per_layer);
36851
+ END IF;
36852
+ IF cardinality(blok) = 0 AND cardinality(hmist) > 0 AND coalesce(c.layers_per_carrier,0) > 0 AND lpc > c.layers_per_carrier THEN
36853
+ waar := waar || format('%s lagen opgegeven, standaard %s; niet te controleren omdat de %s ontbreekt', lpc, c.layers_per_carrier, array_to_string(hmist, ', '));
36854
+ END IF;
36855
+ IF 'weight' = ANY(coalesce(r.ontbrekend, '{}')) THEN waar := waar || 'gewicht per fust ontbreekt'::text; END IF;
36856
+
36857
+ product_id := r.product_id; "actieUrl" := c_url;
36858
+ IF cardinality(blok) > 0 THEN
36859
+ oordeel := 'onmogelijk'; tekst := format('%s past niet op %s: %s.', r.product, naam, array_to_string(blok, '; '));
36860
+ ELSIF cardinality(mist) > 0 THEN
36861
+ oordeel := 'mist_gegevens';
36862
+ tekst := CASE WHEN 'belading niet ingesteld' = ANY(mist) THEN format('%s: belading niet ingesteld.', r.product)
36863
+ WHEN v_lfout IS NOT NULL AND v_lfout = ANY(mist) THEN format('%s: %s.', r.product, v_lfout)
36864
+ ELSE r.uitleg END;
36865
+ ELSIF cardinality(waar) > 0 THEN
36866
+ oordeel := 'waarschuwing'; tekst := format('%s: %s.', r.product, array_to_string(waar, '; '));
36867
+ ELSE
36868
+ oordeel := 'compleet'; tekst := format('%s: gegevens voor karindeling compleet.', r.product);
36869
+ END IF;
36870
+ RETURN NEXT;
36871
+ END LOOP;
36872
+ END $function$;
36873
+ -- 1.0.213 punt 6/2: verouderde kar ook bij aantalafwijking of zonder geldige regels.
36874
+ CREATE OR REPLACE FUNCTION public.logistics_cart_aantal_afwijking_core(p_cart_id uuid)
36875
+ RETURNS text[] LANGUAGE sql STABLE SET search_path TO 'public' AS $$
36876
+ -- Regels op deze kar waarvan het totaal aantal fust (over alle actieve karren) niet past bij de order:
36877
+ -- fust moet gelijk zijn aan het orderaantal (eenheid fust) of aan ceil(aantal ÷ planten per fust) (eenheid planten).
36878
+ -- Groepsartikelen (meerdere producten onder één orderregel) worden per lid afgerond en tellen hier niet mee.
36879
+ WITH r AS (
36880
+ SELECT DISTINCT ci.order_item_id FROM public.order_cart_items ci WHERE ci.cart_id = p_cart_id AND ci.order_item_id IS NOT NULL
36881
+ ), s AS (
36882
+ SELECT oi.id, coalesce(oi.product_name, 'regel') naam, oi.quantity q, sum(ci.crates) fust,
36883
+ max(ci.plants_per_crate) ppc, count(DISTINCT ci.product_id) nprod,
36884
+ bool_or(ci.components IS NOT NULL AND ci.components::text NOT IN ('null','[]','{}')) comp
36885
+ FROM r JOIN public.order_items oi ON oi.id = r.order_item_id
36886
+ JOIN public.order_cart_items ci ON ci.order_item_id = oi.id
36887
+ JOIN public.order_carts c ON c.id = ci.cart_id AND c.archived_at IS NULL
36888
+ WHERE oi.quarantined_at IS NULL
36889
+ GROUP BY oi.id, oi.product_name, oi.quantity)
36890
+ SELECT array_agg(naam ORDER BY naam) FROM s
36891
+ WHERE NOT comp AND nprod <= 1 AND q IS NOT NULL AND q > 0
36892
+ AND fust <> q AND fust <> ceil(q::numeric / greatest(coalesce(ppc, 1), 1))
36893
+ $$;
36894
+
36895
+ CREATE OR REPLACE FUNCTION public.logistics_cart_verouderd_core(p_cart_id uuid)
36896
+ RETURNS jsonb LANGUAGE plpgsql STABLE SET search_path TO 'public'
36897
+ AS $function$
36898
+ DECLARE v_nr text; v_n int; v_live int; v_afw text[];
36899
+ BEGIN
36900
+ IF p_cart_id IS NULL THEN RETURN NULL; END IF;
36901
+ SELECT cart_number::text INTO v_nr FROM public.order_carts WHERE id = p_cart_id;
36902
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'order_items' AND column_name = 'quarantined_at') THEN
36903
+ SELECT count(*) INTO v_n FROM public.order_cart_items ci JOIN public.order_items oi ON oi.id = ci.order_item_id
36904
+ WHERE ci.cart_id = p_cart_id AND oi.quarantined_at IS NOT NULL;
36905
+ IF coalesce(v_n, 0) > 0 THEN
36906
+ RETURN jsonb_build_object('verouderd', true, 'reden', 'quarantaine', 'regelsInQuarantaine', v_n,
36907
+ 'blokkade', jsonb_build_object('code', 'kar_verouderd',
36908
+ 'tekst', format('Kar %s heeft %s regel(s) die Verkoop in quarantaine heeft gezet. Deel de niet-geprinte karren opnieuw in.', coalesce(v_nr, '?'), v_n)));
36909
+ END IF;
36910
+ SELECT count(*) INTO v_live FROM public.order_cart_items ci LEFT JOIN public.order_items oi ON oi.id = ci.order_item_id
36911
+ WHERE ci.cart_id = p_cart_id AND coalesce(ci.crates, 0) > 0 AND oi.quarantined_at IS NULL;
36912
+ ELSE
36913
+ SELECT count(*) INTO v_live FROM public.order_cart_items ci WHERE ci.cart_id = p_cart_id AND coalesce(ci.crates, 0) > 0;
36914
+ END IF;
36915
+ IF coalesce(v_live, 0) = 0 THEN
36916
+ RETURN jsonb_build_object('verouderd', true, 'reden', 'leeg',
36917
+ 'blokkade', jsonb_build_object('code', 'kar_leeg',
36918
+ 'tekst', format('Kar %s heeft geen regels meer. Deel de niet-geprinte karren opnieuw in.', coalesce(v_nr, '?'))));
36919
+ END IF;
36920
+ v_afw := public.logistics_cart_aantal_afwijking_core(p_cart_id);
36921
+ IF coalesce(cardinality(v_afw), 0) > 0 THEN
36922
+ RETURN jsonb_build_object('verouderd', true, 'reden', 'aantal', 'producten', to_jsonb(v_afw),
36923
+ 'blokkade', jsonb_build_object('code', 'kar_verouderd',
36924
+ 'tekst', format('Kar %s: de aantallen wijken af van de order (%s). Deel de niet-geprinte karren opnieuw in.', coalesce(v_nr, '?'), array_to_string(v_afw, ', '))));
36925
+ END IF;
36926
+ RETURN NULL;
36927
+ END $function$;
36928
+
36929
+ -- "1 laag" i.p.v. "1 lagen".
36930
+ CREATE OR REPLACE FUNCTION public.logistics_kar_tekst(p_carrier_code text, p_lagen numeric, p_pct numeric)
36931
+ RETURNS text LANGUAGE sql STABLE SECURITY DEFINER SET search_path TO 'public' AS $$
36932
+ SELECT CASE WHEN p_lagen IS NULL THEN NULL ELSE
36933
+ replace(trim_scale(round(p_lagen, 1))::text, '.', ',') || CASE WHEN round(p_lagen, 1) = 1 THEN ' laag · ' ELSE ' lagen · ' END
36934
+ || coalesce((SELECT nullif(btrim(t.name), '') FROM public.load_carrier_types t WHERE t.code = p_carrier_code ORDER BY t.is_active DESC LIMIT 1), 'Ladingdrager')
36935
+ || CASE WHEN coalesce(public.logistics_extra_platen(p_lagen), 0) = 0 THEN ''
36936
+ WHEN public.logistics_extra_platen(p_lagen) = 1 THEN ' + 1 plaat'
36937
+ ELSE ' + ' || public.logistics_extra_platen(p_lagen) || ' platen' END
36938
+ || CASE WHEN p_pct IS NULL THEN '' ELSE ' · ' || round(p_pct)::int || ' %' END END
36939
+ $$;
36940
+ CREATE OR REPLACE FUNCTION public.logistics_install_check_213()
36941
+ RETURNS TABLE(punt text, ok boolean, uitleg text) LANGUAGE sql STABLE SECURITY DEFINER SET search_path TO 'public' AS $$
36942
+ SELECT 'buffer niet splitsen', position('beschermd' IN pg_get_functiondef('public.logistics_rebuffer_simulate_open()'::regprocedure)) > 0,
36943
+ 'Verzonden/afgemelde of vastliggende buffers blijven bij de voorbeeldberekening ongewijzigd'
36944
+ UNION ALL SELECT 'oordeel_many: hoofdverpakking eerst', position('logistics_product_hoofdverpakking' IN pg_get_functiondef('public.logistics_product_oordeel_many(uuid[])'::regprocedure)) > 0
36945
+ AND position('logistics_ladingdrager_resolve' IN pg_get_functiondef('public.logistics_product_oordeel_many(uuid[])'::regprocedure)) > 0,
36946
+ 'Ladingdrager: hoofdverpakking → standaard werkwijze'
36947
+ UNION ALL SELECT 'kar verouderd bij aantalafwijking', to_regprocedure('public.logistics_cart_aantal_afwijking_core(uuid)') IS NOT NULL
36948
+ AND position('aantal_afwijking' IN pg_get_functiondef('public.logistics_cart_verouderd_core(uuid)'::regprocedure)) > 0,
36949
+ 'Afwijkende aantallen of lege kar → verouderd: opnieuw indelen'
36950
+ UNION ALL SELECT 'karTekst enkelvoud', position(' laag · ' IN pg_get_functiondef('public.logistics_kar_tekst(text,numeric,numeric)'::regprocedure)) > 0,
36951
+ '"1 laag" in plaats van "1 lagen"'
36952
+ UNION ALL SELECT 'guard', to_regprocedure('public.logistics_acc_guard(text,text)') IS NOT NULL, 'Toegangscontrole aanwezig'
36953
+ $$;
36954
+ GRANT EXECUTE ON FUNCTION public.logistics_install_check_213() TO authenticated, service_role;
36955
+
36956
+ -- ===== 1.0.215 =====
36957
+
36958
+ CREATE OR REPLACE FUNCTION public.logistics_product_hoofdverpakking(p_product_id uuid)
36959
+ RETURNS jsonb LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public'
36960
+ AS $function$
36961
+ DECLARE v_lc text; v_found boolean := false; v_heeft_lc boolean; v_n int; v_sid uuid; v_lijst text;
36962
+ BEGIN
36963
+ PERFORM public.logistics_acc_guard('ingelogd', 'logistics_product_hoofdverpakking');
36964
+ IF to_regclass('public.product_packings') IS NULL THEN RETURN NULL; END IF;
36965
+ SELECT EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'public.product_packings'::regclass AND attname = 'load_carrier' AND NOT attisdropped) INTO v_heeft_lc;
36966
+ IF NOT v_heeft_lc THEN
36967
+ EXECUTE 'SELECT true FROM public.product_packings WHERE product_id = $1 AND is_primary LIMIT 1' INTO v_found USING p_product_id;
36968
+ RETURN jsonb_build_object('aanwezig', coalesce(v_found, false), 'ladingdrager', NULL, 'bron', CASE WHEN v_found THEN 'product' END);
36969
+ END IF;
36970
+ -- Stap 2: primaire verpakking van het product zelf.
36971
+ EXECUTE 'SELECT true, nullif(btrim(load_carrier::text), '''') FROM public.product_packings WHERE product_id = $1 AND is_primary
36972
+ ORDER BY sort_order NULLS LAST, created_at LIMIT 1' INTO v_found, v_lc USING p_product_id;
36973
+ IF coalesce(v_found, false) AND v_lc IS NOT NULL THEN
36974
+ RETURN jsonb_build_object('aanwezig', true, 'ladingdrager', v_lc, 'bron', 'product');
36975
+ END IF;
36976
+ -- Stap 3 (1.0.215): eigen artikel → voorraadartikel met products.floriday_trade_item_id = dit product-id.
36977
+ IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'public.products'::regclass AND attname = 'floriday_trade_item_id' AND NOT attisdropped) THEN
36978
+ EXECUTE $q$
36979
+ SELECT count(DISTINCT x.lc), min(x.lc), (array_agg(x.pid ORDER BY x.pid))[1], string_agg(DISTINCT x.lc, ', ')
36980
+ FROM (SELECT DISTINCT ON (s.id) s.id pid, nullif(btrim(pp.load_carrier::text), '') lc
36981
+ FROM public.products s JOIN public.product_packings pp ON pp.product_id = s.id AND pp.is_primary
36982
+ WHERE s.floriday_trade_item_id = $1::text AND s.id <> $1
36983
+ ORDER BY s.id, pp.sort_order NULLS LAST, pp.created_at) x
36984
+ WHERE x.lc IS NOT NULL$q$
36985
+ INTO v_n, v_lc, v_sid, v_lijst USING p_product_id;
36986
+ IF coalesce(v_n, 0) = 1 THEN
36987
+ RETURN jsonb_build_object('aanwezig', true, 'ladingdrager', v_lc, 'bron', 'voorraadartikel', 'voorraadartikelId', v_sid);
36988
+ ELSIF coalesce(v_n, 0) > 1 THEN
36989
+ RETURN jsonb_build_object('aanwezig', false, 'ladingdrager', NULL, 'bron', 'voorraadartikel',
36990
+ 'fout', format('gekoppelde voorraadartikelen hebben verschillende ladingdragers (%s)', v_lijst));
36991
+ END IF;
36992
+ END IF;
36993
+ RETURN jsonb_build_object('aanwezig', coalesce(v_found, false), 'ladingdrager', NULL, 'bron', CASE WHEN v_found THEN 'product' END);
36994
+ END $function$;
36995
+ GRANT EXECUTE ON FUNCTION public.logistics_product_hoofdverpakking(uuid) TO authenticated, service_role;
36996
+
36997
+ -- Gedeelde keuze per planregel: regel → product → voorraadartikel → Floriday-verpakking → werkwijze (waarschuwing).
36998
+ -- p_regels: [{ "key": text, "regel_code": text|null, "product_id": uuid|null }], max 2000.
36999
+ CREATE OR REPLACE FUNCTION public.logistics_ladingdrager_kies(p_regels jsonb)
37000
+ RETURNS jsonb LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public'
37001
+ AS $function$
37002
+ DECLARE e jsonb; uit jsonb := '{}'; rc jsonb; hv jsonb; v_pid uuid; v_key text; v_def text; v_ti text; v_lc text;
37003
+ v_cache boolean := to_regclass('public.floriday_trade_items_cache') IS NOT NULL;
37004
+ BEGIN
37005
+ PERFORM public.logistics_acc_guard('ingelogd', 'logistics_ladingdrager_kies');
37006
+ IF p_regels IS NULL OR jsonb_typeof(p_regels) <> 'array' THEN RETURN '{}'; END IF;
37007
+ IF jsonb_array_length(p_regels) > 2000 THEN
37008
+ RAISE EXCEPTION 'Maximaal 2000 regels per aanroep.' USING ERRCODE = '22023';
37009
+ END IF;
37010
+ SELECT nullif(btrim(coalesce(s.settings->'karrenindeling'->'fields'->>'default_carrier_type', s.settings->'karrenindeling'->>'default_carrier_type')), '')
37011
+ INTO v_def FROM public.logistics_rule_configs s WHERE s.is_active ORDER BY s.updated_at DESC LIMIT 1;
37012
+ IF v_def IS NULL OR NOT EXISTS (SELECT 1 FROM public.load_carrier_types t WHERE t.is_active AND t.code = v_def) THEN
37013
+ SELECT t.code INTO v_def FROM public.load_carrier_types t WHERE t.is_active AND t.is_default ORDER BY t.sort_order LIMIT 1;
37014
+ END IF;
37015
+ FOR e IN SELECT x FROM jsonb_array_elements(p_regels) x LOOP
37016
+ v_key := e->>'key';
37017
+ CONTINUE WHEN v_key IS NULL;
37018
+ v_pid := CASE WHEN nullif(btrim(e->>'product_id'), '') ~* '^[0-9a-f-]{36}$' THEN (e->>'product_id')::uuid END;
37019
+ -- 1. orderregel
37020
+ rc := public.logistics_ladingdrager_resolve(e->>'regel_code');
37021
+ IF rc->>'code' IS NOT NULL THEN
37022
+ uit := uit || jsonb_build_object(v_key, jsonb_build_object('code', rc->>'code', 'bron', 'orderregel', 'invoer', rc->>'invoer'));
37023
+ CONTINUE;
37024
+ END IF;
37025
+ -- 2/3. product, dan voorraadartikel
37026
+ IF v_pid IS NOT NULL THEN
37027
+ hv := public.logistics_product_hoofdverpakking(v_pid);
37028
+ IF nullif(hv->>'ladingdrager', '') IS NOT NULL THEN
37029
+ rc := public.logistics_ladingdrager_resolve(hv->>'ladingdrager');
37030
+ IF rc->>'code' IS NOT NULL THEN
37031
+ uit := uit || jsonb_build_object(v_key, jsonb_build_object('code', rc->>'code', 'bron', hv->>'bron', 'invoer', rc->>'invoer',
37032
+ 'voorraadartikelId', hv->'voorraadartikelId'));
37033
+ CONTINUE;
37034
+ ELSIF upper(hv->>'ladingdrager') = 'NONE' OR rc->>'fout' = 'ladingdrager ontbreekt' THEN
37035
+ uit := uit || jsonb_build_object(v_key, jsonb_build_object('code', NULL, 'bron', hv->>'bron', 'fout', 'ladingdrager ontbreekt (geen ladingdrager)'));
37036
+ CONTINUE;
37037
+ END IF;
37038
+ ELSIF hv->>'fout' IS NOT NULL THEN
37039
+ uit := uit || jsonb_build_object(v_key, jsonb_build_object('code', NULL, 'bron', hv->>'bron', 'fout', hv->>'fout'));
37040
+ CONTINUE;
37041
+ END IF;
37042
+ -- 3b. Floriday-verpakking van het artikel (bestaand gedrag sinds 1.0.190).
37043
+ IF v_cache THEN
37044
+ v_lc := NULL;
37045
+ SELECT p.floriday_trade_item_id INTO v_ti FROM public.products p WHERE p.id = v_pid;
37046
+ IF v_ti IS NOT NULL THEN
37047
+ EXECUTE $q$SELECT c->>'loadCarrier' FROM public.floriday_trade_items_cache t,
37048
+ jsonb_array_elements(coalesce(t.data->'packingConfigurations', '[]')) c
37049
+ WHERE t.floriday_id = $1 AND NOT coalesce(t.is_deleted, false) AND nullif(c->>'loadCarrier', '') IS NOT NULL
37050
+ ORDER BY coalesce((c->>'primary')::boolean, false) DESC LIMIT 1$q$ INTO v_lc USING v_ti;
37051
+ rc := public.logistics_ladingdrager_resolve(v_lc);
37052
+ IF rc->>'code' IS NOT NULL THEN
37053
+ uit := uit || jsonb_build_object(v_key, jsonb_build_object('code', rc->>'code', 'bron', 'floriday_verpakking', 'invoer', rc->>'invoer'));
37054
+ CONTINUE;
37055
+ END IF;
37056
+ END IF;
37057
+ END IF;
37058
+ END IF;
37059
+ -- 4. werkwijze, zichtbaar
37060
+ uit := uit || jsonb_build_object(v_key, jsonb_build_object('code', v_def, 'bron', CASE WHEN v_def IS NOT NULL THEN 'werkwijze' END,
37061
+ 'waarschuwing', CASE WHEN v_def IS NOT NULL THEN 'ladingdrager uit de werkwijze, niet uit het artikel' END,
37062
+ 'fout', CASE WHEN v_def IS NULL THEN 'ladingdrager ontbreekt' END));
37063
+ END LOOP;
37064
+ RETURN uit;
37065
+ END $function$;
37066
+ GRANT EXECUTE ON FUNCTION public.logistics_ladingdrager_kies(jsonb) TO authenticated, service_role;
37067
+
37068
+ -- Kar die volgens de huidige regels anders zou worden ingedeeld (geen blokkade).
37069
+ CREATE OR REPLACE FUNCTION public.logistics_cart_regels_verouderd_core(p_cart_id uuid)
37070
+ RETURNS jsonb LANGUAGE plpgsql STABLE SET search_path TO 'public'
37071
+ AS $function$
37072
+ DECLARE v_cart record; v_reg jsonb; k jsonb; v_codes text[]; v_naam text; v_max int; v_lagen numeric; v_lh numeric;
37073
+ BEGIN
37074
+ IF p_cart_id IS NULL THEN RETURN NULL; END IF;
37075
+ SELECT c.id, c.cart_number, c.carrier_type, c.total_layers_used INTO v_cart FROM public.order_carts c WHERE c.id = p_cart_id;
37076
+ IF v_cart.id IS NULL THEN RETURN NULL; END IF;
37077
+ IF nullif(v_cart.carrier_type, '') IS NULL OR v_cart.carrier_type IN ('loose', 'none', 'NONE') THEN
37078
+ RETURN jsonb_build_object('reden', 'geen_ladingdrager', 'tekst', 'Kar staat zonder ladingdrager; een product zonder ladingdrager kan niet op een kar.');
37079
+ END IF;
37080
+ SELECT coalesce(jsonb_agg(jsonb_build_object('key', ci.id::text, 'regel_code', oi.load_carrier_type, 'product_id', coalesce(ci.product_id, oi.product_id))), '[]')
37081
+ INTO v_reg
37082
+ FROM public.order_cart_items ci LEFT JOIN public.order_items oi ON oi.id = ci.order_item_id
37083
+ WHERE ci.cart_id = p_cart_id AND coalesce(ci.crates, 0) > 0;
37084
+ IF jsonb_array_length(v_reg) > 0 THEN
37085
+ k := public.logistics_ladingdrager_kies(v_reg);
37086
+ SELECT array_agg(DISTINCT x.value->>'code') FILTER (WHERE x.value->>'code' IS NOT NULL AND x.value->>'bron' <> 'werkwijze')
37087
+ INTO v_codes FROM jsonb_each(k) x;
37088
+ IF coalesce(cardinality(v_codes), 0) > 0 AND NOT (v_cart.carrier_type = ANY(v_codes)) THEN
37089
+ SELECT coalesce(t.name, t.code) INTO v_naam FROM public.load_carrier_types t WHERE t.code = v_codes[1] LIMIT 1;
37090
+ RETURN jsonb_build_object('reden', 'andere_ladingdrager', 'ladingdrager', v_codes[1],
37091
+ 'tekst', format('Volgens het artikel hoort deze kar op %s, niet op %s.', coalesce(v_naam, v_codes[1]),
37092
+ coalesce((SELECT coalesce(t.name, t.code) FROM public.load_carrier_types t WHERE t.code = v_cart.carrier_type LIMIT 1), v_cart.carrier_type)));
37093
+ END IF;
37094
+ END IF;
37095
+ -- Fysieke hoogte: lagen × laaghoogte (max(fusthoogte, planthoogte)) boven de hoogte van de ladingdrager.
37096
+ SELECT t.max_height_cm INTO v_max FROM public.load_carrier_types t WHERE t.code = v_cart.carrier_type ORDER BY t.is_active DESC LIMIT 1;
37097
+ IF v_max IS NOT NULL AND v_max > 0 THEN
37098
+ SELECT max(greatest(coalesce(m.fust_height_mm / 10.0, 0), coalesce(m.plant_height_cm, 0))) INTO v_lh
37099
+ FROM public.logistics_product_maten((SELECT array_agg(DISTINCT ci.product_id) FROM public.order_cart_items ci
37100
+ WHERE ci.cart_id = p_cart_id AND ci.product_id IS NOT NULL)) m
37101
+ WHERE m.fust_height_mm IS NOT NULL AND m.plant_height_cm IS NOT NULL;
37102
+ v_lagen := coalesce(v_cart.total_layers_used,
37103
+ (SELECT sum(ci.crates::numeric / nullif(ci.crates_per_layer, 0)) FROM public.order_cart_items ci WHERE ci.cart_id = p_cart_id));
37104
+ IF v_lh IS NOT NULL AND v_lh > 0 AND v_lagen IS NOT NULL
37105
+ AND ceil(v_lagen - 0.001) > floor(v_max * (1 + public.logistics_laag_tolerantie()) / v_lh) THEN
37106
+ RETURN jsonb_build_object('reden', 'te_veel_lagen', 'tekst',
37107
+ format('%s lagen van %s cm passen niet in %s cm; maximaal %s lagen.', trim_scale(round(v_lagen, 1)), trim_scale(round(v_lh, 1)), v_max,
37108
+ floor(v_max * (1 + public.logistics_laag_tolerantie()) / v_lh)));
37109
+ END IF;
37110
+ END IF;
37111
+ RETURN NULL;
37112
+ END $function$;
37113
+ GRANT EXECUTE ON FUNCTION public.logistics_cart_regels_verouderd_core(uuid) TO service_role;
37114
+
37115
+ -- ===== 1.0.215 =====
37116
+
37117
+ CREATE OR REPLACE FUNCTION public.logistics_cart_verouderd_core(p_cart_id uuid)
37118
+ RETURNS jsonb LANGUAGE plpgsql STABLE SET search_path TO 'public'
37119
+ AS $function$
37120
+ DECLARE v_nr text; v_n int; v_live int; v_afw text[]; v_r jsonb;
37121
+ BEGIN
37122
+ IF p_cart_id IS NULL THEN RETURN NULL; END IF;
37123
+ SELECT cart_number::text INTO v_nr FROM public.order_carts WHERE id = p_cart_id;
37124
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'order_items' AND column_name = 'quarantined_at') THEN
37125
+ SELECT count(*) INTO v_n FROM public.order_cart_items ci JOIN public.order_items oi ON oi.id = ci.order_item_id
37126
+ WHERE ci.cart_id = p_cart_id AND oi.quarantined_at IS NOT NULL;
37127
+ IF coalesce(v_n, 0) > 0 THEN
37128
+ RETURN jsonb_build_object('verouderd', true, 'reden', 'quarantaine', 'regelsInQuarantaine', v_n,
37129
+ 'tekst', format('Kar %s heeft %s regel(s) in quarantaine.', coalesce(v_nr, '?'), v_n),
37130
+ 'blokkade', jsonb_build_object('code', 'kar_verouderd',
37131
+ 'tekst', format('Kar %s heeft %s regel(s) die Verkoop in quarantaine heeft gezet. Deel de niet-geprinte karren opnieuw in.', coalesce(v_nr, '?'), v_n)));
37132
+ END IF;
37133
+ SELECT count(*) INTO v_live FROM public.order_cart_items ci LEFT JOIN public.order_items oi ON oi.id = ci.order_item_id
37134
+ WHERE ci.cart_id = p_cart_id AND coalesce(ci.crates, 0) > 0 AND oi.quarantined_at IS NULL;
37135
+ ELSE
37136
+ SELECT count(*) INTO v_live FROM public.order_cart_items ci WHERE ci.cart_id = p_cart_id AND coalesce(ci.crates, 0) > 0;
37137
+ END IF;
37138
+ IF coalesce(v_live, 0) = 0 THEN
37139
+ RETURN jsonb_build_object('verouderd', true, 'reden', 'leeg', 'tekst', format('Kar %s heeft geen regels meer.', coalesce(v_nr, '?')),
37140
+ 'blokkade', jsonb_build_object('code', 'kar_leeg',
37141
+ 'tekst', format('Kar %s heeft geen regels meer. Deel de niet-geprinte karren opnieuw in.', coalesce(v_nr, '?'))));
37142
+ END IF;
37143
+ v_afw := public.logistics_cart_aantal_afwijking_core(p_cart_id);
37144
+ IF coalesce(cardinality(v_afw), 0) > 0 THEN
37145
+ RETURN jsonb_build_object('verouderd', true, 'reden', 'aantal', 'producten', to_jsonb(v_afw),
37146
+ 'tekst', format('De aantallen wijken af van de order (%s).', array_to_string(v_afw, ', ')),
37147
+ 'blokkade', jsonb_build_object('code', 'kar_verouderd',
37148
+ 'tekst', format('Kar %s: de aantallen wijken af van de order (%s). Deel de niet-geprinte karren opnieuw in.', coalesce(v_nr, '?'), array_to_string(v_afw, ', '))));
37149
+ END IF;
37150
+ -- 1.0.215: huidige regels zouden de kar anders indelen → alleen melden, geen blokkade.
37151
+ v_r := public.logistics_cart_regels_verouderd_core(p_cart_id);
37152
+ IF v_r IS NOT NULL THEN
37153
+ RETURN jsonb_build_object('verouderd', true, 'reden', v_r->>'reden', 'tekst', v_r->>'tekst', 'blokkade', NULL);
37154
+ END IF;
37155
+ RETURN NULL;
37156
+ END $function$;
37157
+ GRANT EXECUTE ON FUNCTION public.logistics_cart_verouderd_core(uuid) TO service_role;
37158
+
37159
+ DO $do$
37160
+ DECLARE d text; n text;
37161
+ BEGIN
37162
+ d := pg_get_functiondef('public.logistics_cart_platen_verrijk(jsonb)'::regprocedure);
37163
+ IF position('verouderdReden' IN d) = 0 THEN
37164
+ n := replace(d,
37165
+ $a$THEN jsonb_build_object('teHoog', false, 'indelingVerouderd', true, 'blokkade', public.logistics_cart_verouderd_core(v_id)->'blokkade')$a$,
37166
+ $a$THEN jsonb_build_object('teHoog', false, 'indelingVerouderd', true, 'blokkade', public.logistics_cart_verouderd_core(v_id)->'blokkade',
37167
+ 'verouderdReden', public.logistics_cart_verouderd_core(v_id)->>'tekst')$a$);
37168
+ IF n = d THEN RAISE EXCEPTION 'patroon platen_verrijk niet gevonden'; END IF;
37169
+ EXECUTE n;
37170
+ END IF;
37171
+ END $do$;
37172
+
37173
+ CREATE OR REPLACE FUNCTION public.logistics_install_check_215()
37174
+ RETURNS TABLE(punt text, ok boolean, uitleg text) LANGUAGE sql STABLE SECURITY DEFINER SET search_path TO 'public' AS $$
37175
+ SELECT 'hoofdverpakking via voorraadartikel', position('floriday_trade_item_id' IN pg_get_functiondef('public.logistics_product_hoofdverpakking(uuid)'::regprocedure)) > 0,
37176
+ 'Eigen artikel: belading van het voorraadartikel (products.floriday_trade_item_id)'
37177
+ UNION ALL SELECT 'gedeelde ladingdragerkeuze', to_regprocedure('public.logistics_ladingdrager_kies(jsonb)') IS NOT NULL,
37178
+ 'Orderregel → artikel → voorraadartikel → Floriday-verpakking → werkwijze met waarschuwing'
37179
+ UNION ALL SELECT 'kar verouderd volgens regels', position('logistics_cart_regels_verouderd_core' IN pg_get_functiondef('public.logistics_cart_verouderd_core(uuid)'::regprocedure)) > 0,
37180
+ 'Geen/andere ladingdrager of te veel lagen → verouderd: opnieuw indelen (geen blokkade)'
37181
+ UNION ALL SELECT 'reden op kar', position('verouderdReden' IN pg_get_functiondef('public.logistics_cart_platen_verrijk(jsonb)'::regprocedure)) > 0, 'Reden zichtbaar per kar'
37182
+ UNION ALL SELECT 'guard', position('logistics_acc_guard' IN pg_get_functiondef('public.logistics_ladingdrager_kies(jsonb)'::regprocedure)) > 0, 'Keuze controleert de aanroeper'
37183
+ $$;
37184
+ GRANT EXECUTE ON FUNCTION public.logistics_install_check_215() TO authenticated, service_role;
@@ -1 +1 @@
1
- {"version":3,"file":"BeheerPage.d.ts","sourceRoot":"","sources":["../../src/components/BeheerPage.tsx"],"names":[],"mappings":"AA+JA,wBAAgB,UAAU,gCAoBzB"}
1
+ {"version":3,"file":"BeheerPage.d.ts","sourceRoot":"","sources":["../../src/components/BeheerPage.tsx"],"names":[],"mappings":"AAgKA,wBAAgB,UAAU,gCAwBzB"}
@@ -9,6 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
9
  import { useState } from "react";
10
10
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, } from "@flowselections/core";
11
11
  import { RotateCcw } from "lucide-react";
12
+ import { Link } from "@tanstack/react-router";
12
13
  import { supabase } from "../integrations/supabase/moduleClient.js";
13
14
  import { RolPoort } from "./RolPoort.js";
14
15
  import { OudeOrderregelsCard } from "./OudeOrderregelsCard.js";
@@ -81,5 +82,5 @@ function HerindelenBeheer() {
81
82
  : `${voor?.geprintOfGepickt ?? 0} order(s) die al geprint of gepickt zijn, blijven ongemoeid.` })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsxs(AlertDialogAction, { onClick: () => void voerUit(), disabled: (voor?.aantal ?? 0) === 0, children: ["Ja, ", voor?.aantal ?? 0, " order(s) opnieuw indelen"] })] })] }) })] }));
82
83
  }
83
84
  export function BeheerPage() {
84
- return (_jsx(RolPoort, { rollen: ENGINE_TOEGANG.beheer, children: _jsxs("div", { className: "space-y-6 p-6", children: [_jsx(MinuutrondeStartMelding, {}), _jsx(StartlijstCard, {}), _jsx(HerindelenBeheer, {}), _jsx(HerindelingMeldingCard, {}), _jsx(VervoerderBewakingCard, {}), _jsx(EtiketInstellenMelding, {}), _jsx(DataGereedheidCard, {}), _jsx(GeblokkeerdeStickersCard, {}), _jsx(OudeOrderregelsCard, {}), _jsx(BeladingVerstuurderCard, {}), _jsx(DocumentkeuzeCard, {}), _jsx(AutomatischVersturenCard, {}), _jsx(OpruimlijstKarrenCard, {})] }) }));
85
+ return (_jsx(RolPoort, { rollen: ENGINE_TOEGANG.beheer, children: _jsxs("div", { className: "space-y-6 p-6", children: [_jsx(MinuutrondeStartMelding, {}), _jsx(StartlijstCard, {}), _jsx(HerindelenBeheer, {}), _jsxs("p", { className: "text-sm", children: [_jsx(Link, { to: "/logistiek-engine/proefindeling", className: "underline", children: "Proefindeling" }), " ", "\u2014 reken de karindeling na zonder iets op te slaan."] }), _jsx(HerindelingMeldingCard, {}), _jsx(VervoerderBewakingCard, {}), _jsx(EtiketInstellenMelding, {}), _jsx(DataGereedheidCard, {}), _jsx(GeblokkeerdeStickersCard, {}), _jsx(OudeOrderregelsCard, {}), _jsx(BeladingVerstuurderCard, {}), _jsx(DocumentkeuzeCard, {}), _jsx(AutomatischVersturenCard, {}), _jsx(OpruimlijstKarrenCard, {})] }) }));
85
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DataGereedheidCard.d.ts","sourceRoot":"","sources":["../../src/components/DataGereedheidCard.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAU3E,KAAK,IAAI,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AACxG,KAAK,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAMzN,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,aAAa,GAAG;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAuB9H;AAED,KAAK,QAAQ,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACtE,KAAK,KAAK,GAAG;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAClI,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAS7C,CAAC;AAGF,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAQ7C,CAAC;AAGF,wBAAgB,kBAAkB,gCAoLjC"}
1
+ {"version":3,"file":"DataGereedheidCard.d.ts","sourceRoot":"","sources":["../../src/components/DataGereedheidCard.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAU3E,KAAK,IAAI,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AACxG,KAAK,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAMzN,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,aAAa,GAAG;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAuB9H;AAED,KAAK,QAAQ,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACtE,KAAK,KAAK,GAAG;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAClI,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAS7C,CAAC;AAGF,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAQ7C,CAAC;AAGF,wBAAgB,kBAAkB,gCAmMjC"}
@@ -78,6 +78,8 @@ export function DataGereedheidCard() {
78
78
  const [bezig, setBezig] = useState(null);
79
79
  const [fusten, setFusten] = useState(new Map());
80
80
  const [dragers, setDragers] = useState(new Map());
81
+ // 1.0.213: het oordeel per product komt van de server (zelfde regel als de planner).
82
+ const [oordelen, setOordelen] = useState(new Map());
81
83
  const floricodeNamen = useFloricodeLadingdragers();
82
84
  const laden = useCallback(async () => {
83
85
  setLoading(true);
@@ -88,6 +90,16 @@ export function DataGereedheidCard() {
88
90
  const lijst = !error && Array.isArray(data) ? data : [];
89
91
  if (!error)
90
92
  setRijen(lijst);
93
+ const pids = Array.from(new Set(lijst.map((r) => r.product_id).filter(Boolean))).slice(0, 1000);
94
+ if (pids.length > 0) {
95
+ const { data: o, error: oe } = await supabase.rpc("logistics_product_oordeel_many", { p_product_ids: pids });
96
+ if (oe)
97
+ setFout(oe.message);
98
+ const ol = !oe && Array.isArray(o) ? o : [];
99
+ setOordelen(new Map(ol.map((x) => [String(x.product_id), String(x.oordeel)])));
100
+ }
101
+ else
102
+ setOordelen(new Map());
91
103
  const fustIds = Array.from(new Set(lijst.map((r) => r.velden?.fust_type_id?.waarde).filter((x) => typeof x === "string")));
92
104
  const codes = Array.from(new Set(lijst.map((r) => r.ladingdrager).filter((x) => !!x)));
93
105
  if (fustIds.length > 0) {
@@ -123,19 +135,26 @@ export function DataGereedheidCard() {
123
135
  };
124
136
  const fysiek = (r) => fysiekVoorRegel(r, typeof r.velden?.fust_type_id?.waarde === "string" ? fusten.get(r.velden.fust_type_id.waarde) ?? null : null, r.ladingdrager ? dragers.get(r.ladingdrager) ?? null : null);
125
137
  // 1.0.201: onmogelijk bovenaan, dan ontbrekende gegevens, dan alleen een waarschuwing (gewicht).
138
+ // 1.0.213: status volgt het serveroordeel (logistics_product_oordeel_many); alleen
139
+ // "onmogelijk" als de server dat zegt, ontbrekende gegevens → "Gegevens ontbreken".
126
140
  const rang = (r) => {
127
- if (fysiek(r).status === "onmogelijk")
141
+ const o = oordelen.get(String(r.product_id));
142
+ if (o === "onmogelijk")
128
143
  return 0;
129
- const mist = (Array.isArray(r.ontbrekend) ? r.ontbrekend : []).filter((v) => v !== "weight");
130
- if (mist.length > 0)
144
+ if (o === "mist_gegevens")
131
145
  return 1;
146
+ if (o == null) {
147
+ const mist = (Array.isArray(r.ontbrekend) ? r.ontbrekend : []).filter((v) => v !== "weight");
148
+ if (mist.length > 0)
149
+ return 1;
150
+ }
132
151
  return 2;
133
152
  };
134
153
  const metGaten = rijen
135
- .filter((r) => (Array.isArray(r.ontbrekend) && r.ontbrekend.length > 0) || fysiek(r).status === "onmogelijk")
154
+ .filter((r) => (Array.isArray(r.ontbrekend) && r.ontbrekend.length > 0) || oordelen.get(String(r.product_id)) === "onmogelijk")
136
155
  .sort((a, b) => rang(a) - rang(b) || a.product.localeCompare(b.product));
137
156
  const dragerNaam = (code) => code ? ladingdragerNaam(code, floricodeNamen, dragers.get(code)?.name ?? undefined) : "";
138
- return (_jsxs(Card, { id: "gegevens-voor-karindeling", children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Gegevens voor karindeling" }), _jsx(CardDescription, { children: "Producten in open orders waarvan een waarde ontbreekt. Een voorstel (berekend of van vergelijkbare producten) wordt pas gebruikt als je het hier invult en opslaat." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void laden(), disabled: loading, children: [_jsx(RefreshCw, { className: "h-4 w-4" }), "Verversen"] })] }), _jsxs(CardContent, { className: "space-y-3", children: [fout && _jsx("p", { className: "text-sm text-destructive", children: fout }), loading ? (_jsx(Skeleton, { className: "h-16 w-full" })) : metGaten.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Alle producten in open orders hebben de benodigde gegevens." })) : (metGaten.map((r) => (_jsxs("div", { className: "rounded-md border p-3 space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [_jsx("span", { className: "font-medium", children: r.product }), _jsxs("span", { className: "text-xs text-muted-foreground", children: [rang(r) === 0 ? "Onmogelijk · " : rang(r) === 2 ? "Compleet, 1 waarschuwing · " : "", r.in_open_order ? "In open order" : "Niet in open order", " \u00B7 ", METHODE_LABEL[r.methode] ?? "Indeling", r.ladingdrager ? ` · ${dragerNaam(r.ladingdrager)}` : ""] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: r.uitleg }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Fust ", String(r.velden?.fust_type_id?.naam ?? "onbekend"), " (", bronTekst(r.velden?.fust_type_id?.bron), ") \u00B7 maten", " ", ["fust_length_mm", "fust_width_mm", "fust_height_mm"]
157
+ return (_jsxs(Card, { id: "gegevens-voor-karindeling", children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Gegevens voor karindeling" }), _jsx(CardDescription, { children: "Producten in open orders waarvan een waarde ontbreekt. Een voorstel (berekend of van vergelijkbare producten) wordt pas gebruikt als je het hier invult en opslaat." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void laden(), disabled: loading, children: [_jsx(RefreshCw, { className: "h-4 w-4" }), "Verversen"] })] }), _jsxs(CardContent, { className: "space-y-3", children: [fout && _jsx("p", { className: "text-sm text-destructive", children: fout }), loading ? (_jsx(Skeleton, { className: "h-16 w-full" })) : metGaten.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Alle producten in open orders hebben de benodigde gegevens." })) : (metGaten.map((r) => (_jsxs("div", { className: "rounded-md border p-3 space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [_jsx("span", { className: "font-medium", children: r.product }), _jsxs("span", { className: "text-xs text-muted-foreground", children: [rang(r) === 0 ? "Onmogelijk · " : rang(r) === 1 ? "Gegevens ontbreken · " : "Compleet, 1 waarschuwing · ", r.in_open_order ? "In open order" : "Niet in open order", " \u00B7 ", METHODE_LABEL[r.methode] ?? "Indeling", r.ladingdrager ? ` · ${dragerNaam(r.ladingdrager)}` : ""] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: r.uitleg }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Fust ", String(r.velden?.fust_type_id?.naam ?? "onbekend"), " (", bronTekst(r.velden?.fust_type_id?.bron), ") \u00B7 maten", " ", ["fust_length_mm", "fust_width_mm", "fust_height_mm"]
139
158
  .map((k) => `${r.velden?.[k]?.waarde ?? "?"} mm (${bronTekst(r.velden?.[k]?.bron)})`)
140
159
  .join(" × "), " ", "\u00B7 planthoogte inclusief pot ", r.velden?.plant_height_cm?.waarde != null ? `${r.velden.plant_height_cm.waarde} cm` : "onbekend", " (", bronTekst(r.velden?.plant_height_cm?.bron), ")"] }), r.ontbrekend.map((veld) => {
141
160
  const k = `${r.product_id}:${veld}`;
@@ -0,0 +1,4 @@
1
+ import type { PlanPreviewOrder } from "../lib/planPreview.js";
2
+ export declare function proefindelingCsv(orders: PlanPreviewOrder[]): string;
3
+ export declare function ProefindelingPage(): import("react").JSX.Element;
4
+ //# sourceMappingURL=ProefindelingPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProefindelingPage.d.ts","sourceRoot":"","sources":["../../src/components/ProefindelingPage.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAW3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAqBnE;AAED,wBAAgB,iBAAiB,gCA2HhC"}