@flowselections/logistiek-engine 1.0.216 → 1.0.219

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.
@@ -37182,3 +37182,469 @@ RETURNS TABLE(punt text, ok boolean, uitleg text) LANGUAGE sql STABLE SECURITY D
37182
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
37183
  $$;
37184
37184
  GRANT EXECUTE ON FUNCTION public.logistics_install_check_215() TO authenticated, service_role;
37185
+
37186
+ -- ===================== 1.0.217 =====================
37187
+
37188
+ -- Hulp: één ref → canoniek artikel (Voorraad), fouten als reden (geen stille keuze).
37189
+ CREATE OR REPLACE FUNCTION public.logistics_canon_ref(p_ref text)
37190
+ RETURNS TABLE(pid uuid, tkey text, reden text)
37191
+ LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public' AS $function$
37192
+ BEGIN
37193
+ PERFORM public.logistics_acc_guard('ingelogd', 'logistics_canon_ref');
37194
+ pid := NULL; tkey := NULL; reden := NULL;
37195
+ IF p_ref IS NULL OR btrim(p_ref) = '' THEN reden := 'artikel onbekend'; RETURN NEXT; RETURN; END IF;
37196
+ BEGIN
37197
+ SELECT c.product_id, c.trade_item_key INTO pid, tkey FROM public.voorraad_canoniek_artikel(p_ref::text) c LIMIT 1;
37198
+ EXCEPTION WHEN SQLSTATE 'VR010' THEN reden := SQLERRM;
37199
+ WHEN others THEN reden := 'artikel niet te herleiden: ' || SQLERRM;
37200
+ END;
37201
+ IF reden IS NULL AND tkey IS NULL THEN reden := 'artikel onbekend'; END IF;
37202
+ RETURN NEXT;
37203
+ END $function$;
37204
+ GRANT EXECUTE ON FUNCTION public.logistics_canon_ref(text) TO authenticated, service_role;
37205
+
37206
+ -- 1. Voorraadcontrole nieuwe order: canoniek artikel (Voorraad) + trade_item_stock (fysiek − gereserveerd).
37207
+ CREATE OR REPLACE FUNCTION public.logistics_check_order_allowed(p_lines jsonb, p_strategy_id uuid DEFAULT NULL::uuid)
37208
+ RETURNS jsonb
37209
+ LANGUAGE plpgsql
37210
+ STABLE SECURITY DEFINER
37211
+ SET search_path TO 'public'
37212
+ AS $function$
37213
+ DECLARE
37214
+ v_allow boolean := public.logistics_backorder_allowed(p_strategy_id);
37215
+ v_canon boolean := to_regprocedure('public.voorraad_canoniek_artikel(text)') IS NOT NULL;
37216
+ v_shortages jsonb := '[]'::jsonb;
37217
+ r record;
37218
+ BEGIN
37219
+ PERFORM public.logistics_acc_guard('ingelogd', 'logistics_check_order_allowed');
37220
+ IF p_lines IS NULL OR jsonb_typeof(p_lines) <> 'array' THEN
37221
+ RETURN jsonb_build_object('allowed', true, 'allowBackorder', v_allow, 'shortages', '[]'::jsonb);
37222
+ END IF;
37223
+
37224
+ IF NOT v_canon THEN
37225
+ -- Terugval zonder Voorraad-module: oude regel, bron zichtbaar.
37226
+ SELECT COALESCE(jsonb_agg(s ORDER BY s->>'product'), '[]'::jsonb) INTO v_shortages
37227
+ FROM (
37228
+ SELECT jsonb_build_object('productId', p.id, 'product', COALESCE(p.product, l2.ref),
37229
+ 'wanted', l2.wanted, 'available', COALESCE(p.quantity, 0),
37230
+ 'missing', l2.wanted - COALESCE(p.quantity, 0), 'bron', 'products.quantity') AS s
37231
+ FROM (SELECT COALESCE(e->>'productId', e->>'product_id', e->>'productRef', e->>'ref') AS ref,
37232
+ SUM(COALESCE((e->>'quantity')::numeric, 0)) AS wanted
37233
+ FROM jsonb_array_elements(p_lines) e GROUP BY 1) l2
37234
+ LEFT JOIN LATERAL (SELECT * FROM public.products p
37235
+ WHERE p.id = public.logistics_try_uuid(l2.ref) OR p.article_number = l2.ref
37236
+ OR p.barcode = l2.ref OR p.floriday_trade_item_id = l2.ref OR p.trade_item_key = l2.ref
37237
+ ORDER BY (p.id = public.logistics_try_uuid(l2.ref)) DESC NULLS LAST LIMIT 1) p ON true
37238
+ WHERE l2.wanted > COALESCE(p.quantity, 0)) q;
37239
+ ELSE
37240
+ FOR r IN
37241
+ SELECT min(a.ref) AS ref, (array_agg(a.pid))[1] AS pid, a.tkey, min(a.reden) AS reden, SUM(a.wanted) AS wanted,
37242
+ (SELECT GREATEST(COALESCE(SUM(s.physical_stock), 0) - COALESCE(SUM(s.reserved_stock), 0), 0)
37243
+ FROM public.trade_item_stock s WHERE s.trade_item_key = a.tkey AND s.archived_at IS NULL) AS available
37244
+ FROM (SELECT l2.ref, c.pid, c.tkey, c.reden, l2.wanted
37245
+ FROM (SELECT COALESCE(e->>'productId', e->>'product_id', e->>'productRef', e->>'ref') AS ref,
37246
+ SUM(COALESCE((e->>'quantity')::numeric, 0)) AS wanted
37247
+ FROM jsonb_array_elements(p_lines) e GROUP BY 1) l2,
37248
+ LATERAL public.logistics_canon_ref(l2.ref) c) a GROUP BY a.tkey, CASE WHEN a.tkey IS NULL THEN a.ref END
37249
+ LOOP
37250
+ IF r.reden IS NOT NULL OR r.wanted > COALESCE(r.available, 0) THEN
37251
+ v_shortages := v_shortages || jsonb_build_object(
37252
+ 'productId', r.pid,
37253
+ 'product', COALESCE((SELECT p.product FROM public.products p WHERE p.id = r.pid), r.ref),
37254
+ 'wanted', r.wanted,
37255
+ 'available', CASE WHEN r.reden IS NULL THEN COALESCE(r.available, 0) ELSE 0 END,
37256
+ 'missing', r.wanted - CASE WHEN r.reden IS NULL THEN COALESCE(r.available, 0) ELSE 0 END,
37257
+ 'tradeItemKey', r.tkey,
37258
+ 'bron', 'trade_item_stock (physical_stock − reserved_stock), canoniek artikel',
37259
+ 'reden', r.reden);
37260
+ END IF;
37261
+ END LOOP;
37262
+ END IF;
37263
+
37264
+ RETURN jsonb_build_object(
37265
+ 'allowed', v_allow OR jsonb_array_length(v_shortages) = 0,
37266
+ 'allowBackorder', v_allow,
37267
+ 'shortages', v_shortages,
37268
+ 'bron', CASE WHEN v_canon THEN 'voorraad_canoniek_artikel' ELSE 'products.quantity (Voorraad-module ontbreekt)' END,
37269
+ 'reason', CASE WHEN NOT v_allow AND jsonb_array_length(v_shortages) > 0 THEN 'insufficient_stock' ELSE NULL END);
37270
+ END;
37271
+ $function$;
37272
+
37273
+ -- 2. Reserveringssleutel: zelfde regel als Voorraad — voorraad_canoniek_artikel(ref::text).trade_item_key.
37274
+ CREATE OR REPLACE FUNCTION public.logistics_reserved_key(p_key text)
37275
+ RETURNS text
37276
+ LANGUAGE plpgsql
37277
+ SECURITY DEFINER
37278
+ SET search_path TO 'public'
37279
+ AS $function$
37280
+ DECLARE
37281
+ v_raw text;
37282
+ v_key text;
37283
+ v_pid uuid;
37284
+ BEGIN
37285
+ PERFORM public.logistics_acc_guard('server', 'logistics_reserved_key');
37286
+ IF p_key IS NULL OR btrim(p_key) = '' THEN RETURN NULL; END IF;
37287
+ v_raw := btrim(p_key);
37288
+
37289
+ IF to_regprocedure('public.voorraad_canoniek_artikel(text)') IS NOT NULL THEN
37290
+ BEGIN
37291
+ SELECT c.trade_item_key INTO v_key FROM public.voorraad_canoniek_artikel(v_raw::text) c LIMIT 1;
37292
+ EXCEPTION
37293
+ WHEN SQLSTATE 'VR010' THEN RETURN NULL; -- meerdere artikelen: niets kiezen (fail-closed)
37294
+ WHEN others THEN v_key := NULL;
37295
+ END;
37296
+ IF v_key IS NOT NULL AND EXISTS (SELECT 1 FROM public.trade_item_stock s
37297
+ WHERE s.trade_item_key = v_key AND s.archived_at IS NULL) THEN
37298
+ RETURN v_key;
37299
+ END IF;
37300
+ v_key := NULL;
37301
+ END IF;
37302
+
37303
+ -- Terugval: regel van vóór 1.0.217.
37304
+ v_key := public.ensure_trade_item_stock_row_key(v_raw);
37305
+ IF v_key IS NOT NULL THEN RETURN v_key; END IF;
37306
+
37307
+ BEGIN
37308
+ v_pid := CASE WHEN v_raw LIKE 'product:%' THEN substring(v_raw from 9)::uuid ELSE v_raw::uuid END;
37309
+ EXCEPTION WHEN others THEN
37310
+ v_pid := NULL;
37311
+ END;
37312
+
37313
+ IF v_pid IS NOT NULL THEN
37314
+ SELECT COALESCE(NULLIF(btrim(COALESCE(p.floriday_trade_item_id, '')), ''),
37315
+ NULLIF(btrim(COALESCE(p.trade_item_key, '')), ''), p.id::text)
37316
+ INTO v_key FROM public.products p WHERE p.id = v_pid LIMIT 1;
37317
+ ELSE
37318
+ SELECT COALESCE(NULLIF(btrim(COALESCE(p.floriday_trade_item_id, '')), ''),
37319
+ NULLIF(btrim(COALESCE(p.trade_item_key, '')), ''), p.id::text)
37320
+ INTO v_key FROM public.products p
37321
+ WHERE p.floriday_trade_item_id = v_raw OR p.trade_item_key = v_raw LIMIT 1;
37322
+ END IF;
37323
+ v_key := COALESCE(v_key, v_raw);
37324
+
37325
+ IF EXISTS (SELECT 1 FROM public.trade_item_stock s WHERE s.trade_item_key = v_key) THEN
37326
+ RETURN v_key;
37327
+ END IF;
37328
+ RETURN NULL;
37329
+ END
37330
+ $function$;
37331
+
37332
+ -- 3. Dagtelling: unieke fysieke karren per dag; orders zonder kar apart.
37333
+ CREATE OR REPLACE FUNCTION public.logistics_capacity_day_load(p_from date, p_to date, p_user uuid, p_settings jsonb)
37334
+ RETURNS TABLE(d date, carts integer, required numeric, zonder_kar integer)
37335
+ LANGUAGE sql STABLE SECURITY DEFINER SET search_path TO 'public' AS $$
37336
+ WITH t AS (
37337
+ SELECT DISTINCT ON (t.order_ref) t.order_ref, (t.planned_start AT TIME ZONE public.logistics_tz())::date AS d
37338
+ FROM public.logistics_tasks t
37339
+ WHERE t.task_type = 'pick.execute' AND t.status NOT IN ('done','cancelled') AND NOT t.shadow
37340
+ AND t.planned_start IS NOT NULL AND (p_user IS NULL OR t.user_id = p_user)
37341
+ AND (t.planned_start AT TIME ZONE public.logistics_tz())::date BETWEEN p_from AND p_to
37342
+ ORDER BY t.order_ref, t.planned_start
37343
+ ),
37344
+ o AS (SELECT t.d, t.order_ref, o.id FROM t JOIN public.orders o ON o.order_number = t.order_ref AND o.deleted_at IS NULL),
37345
+ k AS (
37346
+ SELECT DISTINCT ON (u.cart_id) u.cart_id, o.d
37347
+ FROM o, LATERAL public.logistics_order_unit_carts_core(ARRAY[o.id]) u
37348
+ ORDER BY u.cart_id, o.d
37349
+ ),
37350
+ kc AS (
37351
+ SELECT k.d, count(*)::int AS carts,
37352
+ SUM(public.logistics_carrier_minutes(COALESCE(c.carrier_type, 'trolley'), p_settings)) AS mins
37353
+ FROM k JOIN public.order_carts c ON c.id = k.cart_id GROUP BY k.d
37354
+ ),
37355
+ zk AS (
37356
+ SELECT t.d, count(*)::int AS n FROM t
37357
+ WHERE NOT EXISTS (SELECT 1 FROM o JOIN LATERAL public.logistics_order_unit_carts_core(ARRAY[o.id]) u ON true
37358
+ WHERE o.order_ref = t.order_ref)
37359
+ GROUP BY t.d
37360
+ ),
37361
+ dd AS (SELECT DISTINCT t.d FROM t)
37362
+ SELECT dd.d, COALESCE(kc.carts, 0), COALESCE(NULLIF(kc.mins, 0), COALESCE(kc.carts, 0) * 15)::numeric, COALESCE(zk.n, 0)
37363
+ FROM dd LEFT JOIN kc ON kc.d = dd.d LEFT JOIN zk ON zk.d = dd.d
37364
+ ORDER BY dd.d;
37365
+ $$;
37366
+ GRANT EXECUTE ON FUNCTION public.logistics_capacity_day_load(date, date, uuid, jsonb) TO authenticated, service_role;
37367
+
37368
+ DO $do$
37369
+ DECLARE d text; n text;
37370
+ BEGIN
37371
+ d := pg_get_functiondef('public.logistics_capacity_day_check(date,date)'::regprocedure);
37372
+ IF position('logistics_capacity_day_load' IN d) = 0 THEN
37373
+ n := regexp_replace(d, 'FOR v_day IN\s+SELECT d,.*?ORDER BY d\s+LOOP',
37374
+ 'FOR v_day IN SELECT * FROM public.logistics_capacity_day_load(p_from, v_to, v_user, v_settings) LOOP', 's');
37375
+ IF n = d THEN RAISE EXCEPTION 'patroon capacity_day_check (lus) niet gevonden'; END IF;
37376
+ d := n;
37377
+ n := replace(d, $a$'carts', v_day.carts,$a$, $a$'carts', v_day.carts,
37378
+ 'ordersZonderKar', v_day.zonder_kar,$a$);
37379
+ IF n = d THEN RAISE EXCEPTION 'patroon capacity_day_check (carts) niet gevonden'; END IF;
37380
+ EXECUTE n;
37381
+ END IF;
37382
+ END $do$;
37383
+
37384
+ CREATE OR REPLACE FUNCTION public.logistics_install_check_217()
37385
+ RETURNS TABLE(punt text, ok boolean, uitleg text) LANGUAGE sql STABLE SECURITY DEFINER SET search_path TO 'public' AS $$
37386
+ SELECT 'Voorraad canoniek artikel aanwezig', to_regprocedure('public.voorraad_canoniek_artikel(text)') IS NOT NULL,
37387
+ 'Zonder deze functie valt de Engine terug op de oude regel (bron zichtbaar in het antwoord)'
37388
+ UNION ALL SELECT 'voorraadcontrole canoniek', position('voorraad_canoniek_artikel' IN pg_get_functiondef('public.logistics_check_order_allowed(jsonb,uuid)'::regprocedure)) > 0,
37389
+ 'Beschikbaar = physical_stock − reserved_stock (archived_at IS NULL) van de canonieke sleutel'
37390
+ UNION ALL SELECT 'reserveringssleutel canoniek', position('voorraad_canoniek_artikel' IN pg_get_functiondef('public.logistics_reserved_key(text)'::regprocedure)) > 0,
37391
+ 'voorraad_canoniek_artikel(ref::text).trade_item_key, anders oude regel'
37392
+ UNION ALL SELECT 'dagtelling unieke karren', position('logistics_capacity_day_load' IN pg_get_functiondef('public.logistics_capacity_day_check(date,date)'::regprocedure)) > 0,
37393
+ 'Gedeelde kar telt één keer; orders zonder kar apart (ordersZonderKar)'
37394
+ UNION ALL SELECT 'guards', position('logistics_acc_guard' IN pg_get_functiondef('public.logistics_check_order_allowed(jsonb,uuid)'::regprocedure)) > 0
37395
+ AND position('logistics_acc_guard' IN pg_get_functiondef('public.logistics_reserved_key(text)'::regprocedure)) > 0,
37396
+ 'Aanroeper wordt intern gecontroleerd'
37397
+ $$;
37398
+ GRANT EXECUTE ON FUNCTION public.logistics_install_check_217() TO authenticated, service_role;
37399
+
37400
+
37401
+ -- ===== 1.0.218 =====
37402
+ -- ===== 1.0.218 eigen vervoer / klant haalt op =====
37403
+ DO $fs$ BEGIN
37404
+ IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'orders_carrier_source_check')
37405
+ AND position('eigen_vervoer' IN (SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'orders_carrier_source_check' LIMIT 1)) = 0 THEN
37406
+ ALTER TABLE public.orders DROP CONSTRAINT orders_carrier_source_check;
37407
+ END IF;
37408
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'orders_carrier_source_check') THEN
37409
+ ALTER TABLE public.orders ADD CONSTRAINT orders_carrier_source_check CHECK (carrier_source IS NULL OR carrier_source IN
37410
+ ('handmatig','afleverlocatie','afleverlocatie_gln','klant','werkgebied','favoriet','geen','oud','eigen_vervoer')) NOT VALID;
37411
+ END IF;
37412
+ END $fs$;
37413
+
37414
+ CREATE OR REPLACE FUNCTION public.logistics_order_own_transport(p_custom_fields jsonb)
37415
+ RETURNS boolean LANGUAGE sql IMMUTABLE SET search_path TO 'public'
37416
+ AS $function$
37417
+ SELECT CASE jsonb_typeof(p_custom_fields->'own_transport')
37418
+ WHEN 'object' THEN p_custom_fields->'own_transport' <> '{}'::jsonb
37419
+ WHEN 'boolean' THEN (p_custom_fields->>'own_transport')::boolean
37420
+ WHEN 'string' THEN coalesce(nullif(btrim(lower(p_custom_fields->>'own_transport')), ''), 'false') NOT IN ('false','0','nee','no')
37421
+ WHEN 'number' THEN (p_custom_fields->>'own_transport')::numeric <> 0
37422
+ WHEN 'array' THEN jsonb_array_length(p_custom_fields->'own_transport') > 0
37423
+ ELSE false END
37424
+ $function$;
37425
+ GRANT EXECUTE ON FUNCTION public.logistics_order_own_transport(jsonb) TO authenticated, service_role;
37426
+
37427
+ -- Soort vertrek van een order (orders-rij als jsonb): extern | eigen_vervoer | klant_haalt_op.
37428
+ CREATE OR REPLACE FUNCTION public.logistics_order_transport_group(p_order jsonb)
37429
+ RETURNS text LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public'
37430
+ AS $function$
37431
+ DECLARE cf jsonb := CASE WHEN jsonb_typeof(p_order->'custom_fields') = 'object' THEN p_order->'custom_fields' ELSE '{}'::jsonb END;
37432
+ v_id uuid; v_kind text;
37433
+ BEGIN
37434
+ PERFORM public.logistics_acc_guard('ingelogd', 'logistics_order_transport_group');
37435
+ IF public.logistics_order_own_transport(cf) THEN RETURN 'eigen_vervoer'; END IF;
37436
+ v_id := public.logistics_try_uuid(p_order->>'carrier_id');
37437
+ IF v_id IS NOT NULL THEN
37438
+ SELECT to_jsonb(c)->>'carrier_kind' INTO v_kind FROM public.carriers c WHERE c.id = v_id;
37439
+ IF v_kind IN ('eigen_vervoer', 'klant_haalt_op') THEN RETURN v_kind; END IF;
37440
+ RETURN 'extern';
37441
+ END IF;
37442
+ -- Zonder vervoerder maar met een bron (afleverlocatie self_pickup) = klant haalt op.
37443
+ IF coalesce(p_order->>'carrier_source', 'geen') NOT IN ('geen', 'eigen_vervoer') THEN RETURN 'klant_haalt_op'; END IF;
37444
+ RETURN 'extern';
37445
+ END $function$;
37446
+ GRANT EXECUTE ON FUNCTION public.logistics_order_transport_group(jsonb) TO authenticated, service_role;
37447
+
37448
+ -- Resolverstap eigen vervoer: NULL = niet van toepassing.
37449
+ CREATE OR REPLACE FUNCTION public.logistics_own_transport_carrier(p_order jsonb, p_skip_manual boolean)
37450
+ RETURNS jsonb LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public'
37451
+ AS $function$
37452
+ DECLARE cf jsonb := CASE WHEN jsonb_typeof(p_order->'custom_fields') = 'object' THEN p_order->'custom_fields' ELSE '{}'::jsonb END;
37453
+ v_id uuid; v_kind text; v_skips text[] := '{}'::text[]; v_ids uuid[]; v_reden text;
37454
+ BEGIN
37455
+ PERFORM public.logistics_acc_guard('server', 'logistics_own_transport_carrier');
37456
+ IF NOT public.logistics_order_own_transport(cf) THEN RETURN NULL; END IF;
37457
+ v_id := public.logistics_try_uuid(p_order->>'carrier_id');
37458
+ IF v_id IS NOT NULL AND NOT p_skip_manual AND p_order->>'carrier_source' = 'handmatig' THEN
37459
+ SELECT to_jsonb(c)->>'carrier_kind' INTO v_kind FROM public.carriers c WHERE c.id = v_id;
37460
+ IF v_kind = 'eigen_vervoer' AND public.logistics_carrier_skip_reason(v_id) IS NULL THEN
37461
+ RETURN public.logistics_carrier_result(v_id, 'handmatig', 'Handmatig gekozen: eigen vervoer.', v_skips);
37462
+ END IF;
37463
+ v_skips := v_skips || 'handmatige vervoerder is geen eigen vervoer'::text;
37464
+ END IF;
37465
+ SELECT array_agg(c.id ORDER BY c.id) INTO v_ids FROM public.carriers c
37466
+ WHERE to_jsonb(c)->>'carrier_kind' = 'eigen_vervoer' AND nullif(to_jsonb(c)->>'archived_at', '') IS NULL;
37467
+ IF coalesce(cardinality(v_ids), 0) = 1 THEN
37468
+ RETURN public.logistics_carrier_result(v_ids[1], 'eigen_vervoer', 'Eigen vervoer op de order; vervoerder met soort eigen vervoer.', v_skips);
37469
+ END IF;
37470
+ v_reden := CASE WHEN coalesce(cardinality(v_ids), 0) = 0
37471
+ THEN 'Eigen vervoer op de order, maar er is geen vervoerder met soort eigen vervoer. Er is geen andere vervoerder gekozen.'
37472
+ ELSE 'Eigen vervoer op de order, maar er zijn ' || cardinality(v_ids) || ' vervoerders met soort eigen vervoer. Er is geen vervoerder gekozen.' END;
37473
+ RETURN jsonb_build_object('carrier_id', NULL, 'carrier_name', 'Eigen vervoer', 'carrier_kind', 'eigen_vervoer',
37474
+ 'bron', 'geen', 'reden', v_reden, 'waarschuwing', v_reden, 'overgeslagen', to_jsonb(v_skips));
37475
+ END $function$;
37476
+ REVOKE ALL ON FUNCTION public.logistics_own_transport_carrier(jsonb, boolean) FROM PUBLIC, anon;
37477
+ GRANT EXECUTE ON FUNCTION public.logistics_own_transport_carrier(jsonb, boolean) TO authenticated, service_role;
37478
+
37479
+ -- Bestaande functies gericht aanvullen (luid falen als het patroon ontbreekt).
37480
+ DO $do$
37481
+ DECLARE d text; n text; f record; v_hit int := 0;
37482
+ BEGIN
37483
+ -- resolver
37484
+ d := pg_get_functiondef('public.logistics_resolve_carrier_core(jsonb,boolean)'::regprocedure);
37485
+ IF position('logistics_own_transport_carrier' IN d) = 0 THEN
37486
+ n := replace(d, $p$PERFORM public.logistics_acc_guard('server', 'logistics_resolve_carrier_core');$p$,
37487
+ $p$PERFORM public.logistics_acc_guard('server', 'logistics_resolve_carrier_core');
37488
+ IF public.logistics_own_transport_carrier(p_order, p_skip_manual) IS NOT NULL THEN
37489
+ RETURN public.logistics_own_transport_carrier(p_order, p_skip_manual);
37490
+ END IF;$p$);
37491
+ IF n = d THEN RAISE EXCEPTION '1.0.218: patroon resolver niet gevonden'; END IF;
37492
+ EXECUTE n;
37493
+ END IF;
37494
+ -- set_order_carrier: externe vervoerder bij eigen vervoer weigeren
37495
+ d := pg_get_functiondef('public.logistics_set_order_carrier(uuid,uuid,uuid)'::regprocedure);
37496
+ IF position('logistics_order_own_transport' IN d) = 0 THEN
37497
+ n := replace(d, $p$ IF v_skip IS NOT NULL THEN RAISE EXCEPTION 'Deze vervoerder kan niet gekozen worden: %.', v_skip; END IF;$p$,
37498
+ $p$ IF v_skip IS NOT NULL THEN RAISE EXCEPTION 'Deze vervoerder kan niet gekozen worden: %.', v_skip; END IF;
37499
+ IF public.logistics_order_own_transport(o->'custom_fields')
37500
+ AND coalesce((SELECT to_jsonb(ca)->>'carrier_kind' FROM public.carriers ca WHERE ca.id = p_carrier_id), '') <> 'eigen_vervoer' THEN
37501
+ RAISE EXCEPTION 'Deze order heeft eigen vervoer; kies een vervoerder met soort eigen vervoer.' USING ERRCODE = '22023';
37502
+ END IF;$p$);
37503
+ IF n = d THEN RAISE EXCEPTION '1.0.218: patroon set_order_carrier niet gevonden'; END IF;
37504
+ EXECUTE n;
37505
+ END IF;
37506
+ -- trigger: own_transport-wijziging herberekent, ook na handmatige externe keuze
37507
+ d := pg_get_functiondef('public.logistics_orders_carrier_fill()'::regprocedure);
37508
+ IF position('own_transport' IN d) = 0 THEN
37509
+ n := replace(d, $p$ IF OLD.carrier_source = 'handmatig' THEN RETURN NEW; END IF;$p$,
37510
+ $p$ IF OLD.carrier_source = 'handmatig'
37511
+ AND (NEW.custom_fields->'own_transport') IS NOT DISTINCT FROM (OLD.custom_fields->'own_transport') THEN RETURN NEW; END IF;$p$);
37512
+ n := replace(n, $p$ AND (NEW.custom_fields->>'carrier') IS NOT DISTINCT FROM (OLD.custom_fields->>'carrier') THEN$p$,
37513
+ $p$ AND (NEW.custom_fields->>'carrier') IS NOT DISTINCT FROM (OLD.custom_fields->>'carrier')
37514
+ AND (NEW.custom_fields->'own_transport') IS NOT DISTINCT FROM (OLD.custom_fields->'own_transport') THEN$p$);
37515
+ IF position('own_transport' IN n) = 0 THEN RAISE EXCEPTION '1.0.218: patroon carrier_fill niet gevonden'; END IF;
37516
+ EXECUTE n;
37517
+ END IF;
37518
+ -- bufferpayload: soort vertrek meegeven
37519
+ d := pg_get_functiondef('public.logistics_buffer_payload(uuid)'::regprocedure);
37520
+ IF position('transportGroup' IN d) = 0 THEN
37521
+ n := replace(d, $p$'isReservation', COALESCE(o.is_reservation, false)$p$,
37522
+ $p$'isReservation', COALESCE(o.is_reservation, false),
37523
+ 'transportGroup', public.logistics_order_transport_group(to_jsonb(o))$p$);
37524
+ IF n = d THEN RAISE EXCEPTION '1.0.218: patroon buffer_payload niet gevonden'; END IF;
37525
+ EXECUTE n;
37526
+ END IF;
37527
+ -- buffersleutel: niet-extern altijd apart (extern ongewijzigd = bestaande sleutels blijven gelijk)
37528
+ FOR f IN SELECT p.oid FROM pg_proc p JOIN pg_namespace s ON s.oid = p.pronamespace
37529
+ WHERE s.nspname = 'public' AND p.proname LIKE 'logistics_buffer_decision%' LOOP
37530
+ d := pg_get_functiondef(f.oid);
37531
+ IF position('v_parts := ARRAY(SELECT DISTINCT ON (p) p FROM unnest(v_parts)' IN d) > 0 THEN
37532
+ v_hit := v_hit + 1;
37533
+ IF position('transport_group' IN d) = 0 THEN
37534
+ n := replace(d, $p$ v_parts := ARRAY(SELECT DISTINCT ON (p) p FROM unnest(v_parts)$p$,
37535
+ $p$ IF coalesce(p_order ->> 'transportGroup', 'extern') IN ('eigen_vervoer', 'klant_haalt_op') THEN
37536
+ v_parts := v_parts || ('vervoer=' || (p_order ->> 'transportGroup'));
37537
+ v_trace := v_trace || jsonb_build_array(jsonb_build_object('rule', 'transport_group', 'value', p_order ->> 'transportGroup', 'result', true,
37538
+ 'message', CASE p_order ->> 'transportGroup' WHEN 'eigen_vervoer' THEN 'Eigen vervoer wordt alleen samen gebufferd met andere orders met eigen vervoer.'
37539
+ ELSE 'Klant haalt op wordt alleen samen gebufferd met andere orders die de klant ophaalt.' END));
37540
+ END IF;
37541
+ v_parts := ARRAY(SELECT DISTINCT ON (p) p FROM unnest(v_parts)$p$);
37542
+ EXECUTE n;
37543
+ END IF;
37544
+ END IF;
37545
+ END LOOP;
37546
+ IF v_hit = 0 THEN RAISE EXCEPTION '1.0.218: buffersleutel niet gevonden'; END IF;
37547
+ -- verouderd: gemengde soorten op één kar (alleen melding)
37548
+ d := pg_get_functiondef('public.logistics_cart_verouderd_core(uuid)'::regprocedure);
37549
+ IF position('logistics_cart_vervoer_gemengd' IN d) = 0 THEN
37550
+ n := replace(d, $p$ v_r := public.logistics_cart_regels_verouderd_core(p_cart_id);$p$,
37551
+ $p$ v_r := public.logistics_cart_vervoer_gemengd(p_cart_id);
37552
+ IF v_r IS NOT NULL THEN
37553
+ RETURN jsonb_build_object('verouderd', true, 'reden', 'vervoer_gemengd', 'tekst', v_r->>'tekst', 'blokkade', NULL);
37554
+ END IF;
37555
+ v_r := public.logistics_cart_regels_verouderd_core(p_cart_id);$p$);
37556
+ IF n = d THEN RAISE EXCEPTION '1.0.218: patroon cart_verouderd_core niet gevonden'; END IF;
37557
+ -- hulpfunctie eerst
37558
+ EXECUTE $h$CREATE OR REPLACE FUNCTION public.logistics_cart_vervoer_gemengd(p_cart_id uuid)
37559
+ RETURNS jsonb LANGUAGE sql STABLE SET search_path TO 'public'
37560
+ AS $f$
37561
+ WITH k AS (SELECT oc.order_id, oc.buffer_id FROM public.order_carts oc WHERE oc.id = p_cart_id),
37562
+ o AS (SELECT DISTINCT x.id, x.order_number, public.logistics_order_transport_group(to_jsonb(x)) g
37563
+ FROM public.orders x, k
37564
+ WHERE x.deleted_at IS NULL AND (x.id = k.order_id OR (k.buffer_id IS NOT NULL AND x.buffer_id = k.buffer_id)))
37565
+ SELECT CASE WHEN count(DISTINCT g) > 1 THEN jsonb_build_object('soorten', jsonb_agg(DISTINCT g),
37566
+ 'tekst', 'Op deze kar staan verschillende soorten vertrek (' ||
37567
+ string_agg(order_number || ': ' || CASE g WHEN 'eigen_vervoer' THEN 'eigen vervoer' WHEN 'klant_haalt_op' THEN 'klant haalt op' ELSE 'vervoerder' END, ', ' ORDER BY order_number)
37568
+ || '); die worden niet samen gebufferd.') END
37569
+ FROM o
37570
+ $f$$h$;
37571
+ EXECUTE n;
37572
+ END IF;
37573
+ END $do$;
37574
+ GRANT EXECUTE ON FUNCTION public.logistics_cart_vervoer_gemengd(uuid) TO service_role;
37575
+
37576
+ -- Voorbeeldberekening: reden bij soort vertrek.
37577
+ CREATE OR REPLACE FUNCTION public.logistics_rebuffer_simulate_open()
37578
+ RETURNS TABLE(id uuid, order_number text, delivery_date text, buffer_id uuid, cur_label text, new_label text, changed boolean, reason text)
37579
+ LANGUAGE plpgsql
37580
+ STABLE SECURITY DEFINER
37581
+ SET search_path TO 'public'
37582
+ AS $function$
37583
+ BEGIN
37584
+ PERFORM public.logistics_acc_guard('server', 'logistics_rebuffer_simulate_open');
37585
+ RETURN QUERY
37586
+ WITH c AS MATERIALIZED (
37587
+ SELECT o.id, o.order_number, o.delivery_date, o.buffer_id, public.logistics_buffer_target(o.id) AS t,
37588
+ public.logistics_order_afgemeld(o.id) AS afgemeld,
37589
+ (o.buffer_id IS NOT NULL AND coalesce(public.logistics_buffer_frozen(o.buffer_id), false)) AS buf_vast,
37590
+ public.logistics_order_transport_group(to_jsonb(o)) AS grp
37591
+ FROM public.orders o JOIN public.logistics_rebuffer_candidates() k ON k.order_id = o.id
37592
+ ), m AS (
37593
+ SELECT c.*,
37594
+ (c.afgemeld OR c.buf_vast) AS beschermd,
37595
+ (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,
37596
+ (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,
37597
+ EXISTS (SELECT 1 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
37598
+ AND public.logistics_order_transport_group(to_jsonb(x)) <> c.grp) AS gemengd
37599
+ FROM c
37600
+ ), l AS (
37601
+ SELECT m.*,
37602
+ CASE WHEN m.buffer_id IS NULL THEN 'Geen buffer (eigen kar)'
37603
+ WHEN m.cur_mates IS NULL THEN 'Eigen buffer'
37604
+ 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,
37605
+ CASE m.grp WHEN 'eigen_vervoer' THEN 'eigen vervoer' WHEN 'klant_haalt_op' THEN 'klant haalt op' ELSE 'vervoerder' END AS grp_t
37606
+ FROM m
37607
+ )
37608
+ SELECT l.id, l.order_number, l.delivery_date, l.buffer_id, l.cur_l,
37609
+ CASE WHEN l.beschermd THEN l.cur_l
37610
+ WHEN l.t->>'soort' IN ('solo','uitgesloten') THEN 'Geen buffer (eigen kar)'
37611
+ WHEN l.new_mates IS NULL THEN 'Eigen buffer'
37612
+ 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,
37613
+ CASE WHEN l.beschermd THEN false
37614
+ ELSE coalesce(l.cur_mates, '{}') IS DISTINCT FROM coalesce(l.new_mates, '{}')
37615
+ OR ((l.t->>'soort') IN ('solo','uitgesloten') AND l.buffer_id IS NOT NULL AND l.cur_mates IS NOT NULL) END,
37616
+ CASE WHEN l.afgemeld THEN 'Ongewijzigd: de order is al verzonden of afgemeld; een bestaande buffer wordt niet gesplitst.'
37617
+ WHEN l.buf_vast THEN 'Ongewijzigd: de buffer ligt al vast (geprint, gescand of verstuurd) en wordt niet gesplitst.'
37618
+ || CASE WHEN l.gemengd THEN ' Let op: in deze buffer staan verschillende soorten vertrek (deze order: ' || l.grp_t || ').' ELSE '' END
37619
+ WHEN jsonb_array_length(coalesce(l.t->'overgeslagen','[]'::jsonb)) > 0
37620
+ THEN 'Niet samengevoegd: buffer van ' || (SELECT string_agg(v, '; ') FROM jsonb_array_elements_text(l.t->'overgeslagen') v)
37621
+ || ' ligt vast (geprint, gescand of verstuurd).'
37622
+ WHEN l.t->>'soort' = 'uitgesloten' THEN 'Deze order is uitgesloten van combineren (handmatig uitgesloten van indeling).'
37623
+ WHEN l.gemengd THEN 'Aparte kar: ' || l.grp_t || ' wordt niet samen gebufferd met orders met een ander soort vertrek (extern, eigen vervoer, klant haalt op).'
37624
+ WHEN l.t->>'soort' = 'solo' THEN 'Volgens de huidige combineerregels krijgt deze order een eigen kar (' || coalesce(l.t->>'reason','onbekend') || ').'
37625
+ WHEN l.grp <> 'extern' THEN 'Volgens de huidige combineerregels, alleen met orders met ' || l.grp_t || ' (' || coalesce(l.t->>'reason','onbekend') || ').'
37626
+ ELSE 'Volgens de huidige combineerregels (' || coalesce(l.t->>'reason','onbekend') || ').' END
37627
+ FROM l;
37628
+ END $function$;
37629
+
37630
+ CREATE OR REPLACE FUNCTION public.logistics_install_check_218()
37631
+ RETURNS TABLE(punt text, ok boolean, uitleg text) LANGUAGE sql STABLE SECURITY DEFINER SET search_path TO 'public' AS $$
37632
+ SELECT 'resolver eigen vervoer', position('logistics_own_transport_carrier' IN pg_get_functiondef('public.logistics_resolve_carrier_core(jsonb,boolean)'::regprocedure)) > 0,
37633
+ 'own_transport: vervoerder met soort eigen vervoer of geen; nooit favoriet/standaard'
37634
+ UNION ALL SELECT 'bron eigen_vervoer toegestaan', position('eigen_vervoer' IN coalesce((SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'orders_carrier_source_check' LIMIT 1), 'eigen_vervoer')) > 0,
37635
+ 'carrier_source mag eigen_vervoer zijn'
37636
+ UNION ALL SELECT 'externe keuze geweigerd', position('logistics_order_own_transport' IN pg_get_functiondef('public.logistics_set_order_carrier(uuid,uuid,uuid)'::regprocedure)) > 0,
37637
+ 'logistics_set_order_carrier weigert externe vervoerder bij eigen vervoer'
37638
+ UNION ALL SELECT 'trigger own_transport', position('own_transport' IN pg_get_functiondef('public.logistics_orders_carrier_fill()'::regprocedure)) > 0,
37639
+ 'Wijziging van own_transport herberekent de vervoerder'
37640
+ UNION ALL SELECT 'buffersleutel soort vertrek', position('transportGroup' IN pg_get_functiondef('public.logistics_buffer_payload(uuid)'::regprocedure)) > 0
37641
+ AND EXISTS (SELECT 1 FROM pg_proc p JOIN pg_namespace s ON s.oid = p.pronamespace WHERE s.nspname = 'public'
37642
+ AND p.proname LIKE 'logistics_buffer_decision%' AND position('transport_group' IN pg_get_functiondef(p.oid)) > 0),
37643
+ 'Extern, eigen vervoer en klant haalt op nooit samen gebufferd'
37644
+ UNION ALL SELECT 'verouderd bij gemengd vervoer', position('logistics_cart_vervoer_gemengd' IN pg_get_functiondef('public.logistics_cart_verouderd_core(uuid)'::regprocedure)) > 0,
37645
+ 'Kar met verschillende soorten vertrek: verouderd: opnieuw indelen'
37646
+ UNION ALL SELECT 'guards', position('logistics_acc_guard' IN pg_get_functiondef('public.logistics_own_transport_carrier(jsonb,boolean)'::regprocedure)) > 0
37647
+ AND position('logistics_acc_guard' IN pg_get_functiondef('public.logistics_order_transport_group(jsonb)'::regprocedure)) > 0,
37648
+ 'Aanroeper wordt intern gecontroleerd'
37649
+ $$;
37650
+ GRANT EXECUTE ON FUNCTION public.logistics_install_check_218() TO authenticated, service_role;
@@ -35639,6 +35639,16 @@ export type Database = {
35639
35639
  };
35640
35640
  Returns: number;
35641
35641
  };
35642
+ logistics_canon_ref: {
35643
+ Args: {
35644
+ p_ref: string;
35645
+ };
35646
+ Returns: {
35647
+ pid: string;
35648
+ reden: string;
35649
+ tkey: string;
35650
+ }[];
35651
+ };
35642
35652
  logistics_capacity_day_check: {
35643
35653
  Args: {
35644
35654
  p_from?: string;
@@ -35646,6 +35656,20 @@ export type Database = {
35646
35656
  };
35647
35657
  Returns: Json;
35648
35658
  };
35659
+ logistics_capacity_day_load: {
35660
+ Args: {
35661
+ p_from: string;
35662
+ p_settings: Json;
35663
+ p_to: string;
35664
+ p_user: string;
35665
+ };
35666
+ Returns: {
35667
+ carts: number;
35668
+ d: string;
35669
+ required: number;
35670
+ zonder_kar: number;
35671
+ }[];
35672
+ };
35649
35673
  logistics_capacity_plan_day: {
35650
35674
  Args: {
35651
35675
  p_date: string;
@@ -36102,6 +36126,12 @@ export type Database = {
36102
36126
  };
36103
36127
  Returns: Json;
36104
36128
  };
36129
+ logistics_cart_vervoer_gemengd: {
36130
+ Args: {
36131
+ p_cart_id: string;
36132
+ };
36133
+ Returns: Json;
36134
+ };
36105
36135
  logistics_cart_vulgraad_core: {
36106
36136
  Args: {
36107
36137
  p_cart_id: string;
@@ -37094,6 +37124,22 @@ export type Database = {
37094
37124
  uitleg: string;
37095
37125
  }[];
37096
37126
  };
37127
+ logistics_install_check_217: {
37128
+ Args: never;
37129
+ Returns: {
37130
+ ok: boolean;
37131
+ punt: string;
37132
+ uitleg: string;
37133
+ }[];
37134
+ };
37135
+ logistics_install_check_218: {
37136
+ Args: never;
37137
+ Returns: {
37138
+ ok: boolean;
37139
+ punt: string;
37140
+ uitleg: string;
37141
+ }[];
37142
+ };
37097
37143
  logistics_is_beheer: {
37098
37144
  Args: never;
37099
37145
  Returns: boolean;
@@ -37713,6 +37759,12 @@ export type Database = {
37713
37759
  status: string;
37714
37760
  }[];
37715
37761
  };
37762
+ logistics_order_own_transport: {
37763
+ Args: {
37764
+ p_custom_fields: Json;
37765
+ };
37766
+ Returns: boolean;
37767
+ };
37716
37768
  logistics_order_pick_day: {
37717
37769
  Args: {
37718
37770
  p_order_ref: string;
@@ -37821,6 +37873,12 @@ export type Database = {
37821
37873
  order_id: string;
37822
37874
  }[];
37823
37875
  };
37876
+ logistics_order_transport_group: {
37877
+ Args: {
37878
+ p_order: Json;
37879
+ };
37880
+ Returns: string;
37881
+ };
37824
37882
  logistics_order_unit_carts: {
37825
37883
  Args: {
37826
37884
  p_order_ids: string[];
@@ -37878,6 +37936,13 @@ export type Database = {
37878
37936
  };
37879
37937
  Returns: Json;
37880
37938
  };
37939
+ logistics_own_transport_carrier: {
37940
+ Args: {
37941
+ p_order: Json;
37942
+ p_skip_manual: boolean;
37943
+ };
37944
+ Returns: Json;
37945
+ };
37881
37946
  logistics_own_transport_departure: {
37882
37947
  Args: {
37883
37948
  p_delivery_date: string;
@@ -44963,6 +45028,12 @@ export type Database = {
44963
45028
  };
44964
45029
  Returns: boolean;
44965
45030
  };
45031
+ voorraad_orderregel_sleutel: {
45032
+ Args: {
45033
+ p_ref: string;
45034
+ };
45035
+ Returns: string;
45036
+ };
44966
45037
  voorraad_packing_from_floriday: {
44967
45038
  Args: {
44968
45039
  p_product_id: string;