@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.
- package/db/schema/logistiek-engine.sql +463 -0
- package/dist-lib/components/BeheerPage.d.ts.map +1 -1
- package/dist-lib/components/BeheerPage.js +2 -1
- package/dist-lib/components/DataGereedheidCard.d.ts.map +1 -1
- package/dist-lib/components/DataGereedheidCard.js +24 -5
- package/dist-lib/components/ProefindelingPage.d.ts +4 -0
- package/dist-lib/components/ProefindelingPage.d.ts.map +1 -0
- package/dist-lib/components/ProefindelingPage.js +102 -0
- package/dist-lib/index.d.ts +4 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +4 -0
- package/dist-lib/integrations/supabase/types.d.ts +892 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanner.d.ts +13 -0
- package/dist-lib/lib/loadCarrierPlanner.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanner.js +25 -7
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.server.js +69 -83
- package/dist-lib/lib/moduleVersion.d.ts +1 -1
- package/dist-lib/lib/moduleVersion.js +1 -1
- package/dist-lib/lib/planPreview.d.ts +39 -0
- package/dist-lib/lib/planPreview.d.ts.map +1 -0
- package/dist-lib/lib/planPreview.functions.d.ts +13 -0
- package/dist-lib/lib/planPreview.functions.d.ts.map +1 -0
- package/dist-lib/lib/planPreview.functions.js +34 -0
- package/dist-lib/lib/planPreview.js +87 -0
- package/package.json +7 -1
|
@@ -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":"
|
|
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,
|
|
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
|
-
|
|
141
|
+
const o = oordelen.get(String(r.product_id));
|
|
142
|
+
if (o === "onmogelijk")
|
|
128
143
|
return 0;
|
|
129
|
-
|
|
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) ||
|
|
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) ===
|
|
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 @@
|
|
|
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"}
|