@flowselections/logistiek-engine 1.0.185 → 1.0.187

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.
@@ -4548,169 +4548,7 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
4548
4548
  RAISE NOTICE 'logistiek-engine: functie public.logistics_allowed_load_carriers overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
4549
4549
  END $fsdo$;
4550
4550
 
4551
- DO $fsdo$ BEGIN
4552
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_apply_buffer_decision(p_order_id uuid)
4553
- RETURNS uuid
4554
- LANGUAGE plpgsql
4555
- SECURITY DEFINER
4556
- SET search_path TO 'public'
4557
- AS $function$
4558
- DECLARE
4559
- o public.orders%ROWTYPE;
4560
- cf jsonb; d jsonb; v_ref text; v_close timestamptz; v_key text;
4561
- v_buffer uuid; v_closed boolean; v_try int := 0; v_on_cart boolean;
4562
- BEGIN
4563
- SELECT * INTO o FROM public.orders WHERE id = p_order_id;
4564
- IF NOT FOUND OR o.deleted_at IS NOT NULL THEN RETURN NULL; END IF;
4565
- cf := COALESCE(o.custom_fields, '{}'::jsonb);
4566
-
4567
- IF public.logistics_order_buffer_excluded(o.id) THEN
4568
- IF o.buffer_id IS NOT NULL THEN
4569
- SELECT EXISTS (SELECT 1 FROM public.order_items oi
4570
- JOIN logistics_active.order_cart_items ci ON ci.order_item_id = oi.id
4571
- JOIN logistics_active.order_carts oc ON oc.id = ci.cart_id
4572
- WHERE oi.order_id = o.id AND oc.buffer_id = o.buffer_id)
4573
- INTO v_on_cart;
4574
- IF NOT v_on_cart THEN
4575
- PERFORM set_config('logistics.buffer_writer', 'engine', true);
4576
- UPDATE public.orders SET buffer_id = NULL WHERE id = o.id;
4577
- BEGIN
4578
- INSERT INTO public.logistics_planner_trace(order_id, message)
4579
- VALUES (o.id, 'afgemeld; uit buffer gehaald');
4580
- EXCEPTION WHEN others THEN NULL; END;
4581
- END IF;
4582
- END IF;
4583
- RETURN NULL;
4584
- END IF;
4585
-
4586
- d := public.logistics_buffer_decision(public.logistics_buffer_payload(o.id), NULL);
4587
-
4588
- IF COALESCE((d ->> 'buffer')::boolean, false) IS NOT TRUE OR (d ->> 'bufferRef') IS NULL THEN
4589
- IF o.buffer_id IS NOT NULL THEN
4590
- UPDATE public.order_carts SET buffer_id = NULL WHERE order_id = o.id;
4591
- PERFORM set_config('logistics.buffer_writer', 'engine', true);
4592
- UPDATE public.orders SET buffer_id = NULL WHERE id = o.id;
4593
- END IF;
4594
- DELETE FROM public.order_buffers b
4595
- WHERE NOT EXISTS (SELECT 1 FROM public.orders x WHERE x.buffer_id = b.id)
4596
- AND NOT EXISTS (SELECT 1 FROM public.order_carts oc WHERE oc.buffer_id = b.id);
4597
- RETURN NULL;
4598
- END IF;
4599
-
4600
- v_ref := d ->> 'bufferRef';
4601
- BEGIN v_close := NULLIF(d ->> 'bufferCloseAt', '')::timestamptz;
4602
- EXCEPTION WHEN others THEN v_close := NULL; END;
4603
-
4604
- LOOP
4605
- v_key := CASE WHEN v_try = 0 THEN v_ref ELSE v_ref || '#' || v_try END;
4606
- SELECT id INTO v_buffer FROM public.order_buffers WHERE buffer_key = v_key;
4607
- IF v_buffer IS NULL THEN
4608
- INSERT INTO public.order_buffers (buffer_key, grouping_mode, pickup_date, pickup_location, customer_id, close_at)
4609
- VALUES (v_key, 'engine',
4610
- COALESCE(cf ->> 'pickup_moment', cf ->> 'pickup_date', o.delivery_date::text),
4611
- COALESCE(cf ->> 'delivery_location_gln', cf ->> 'delivery_location_id', cf ->> 'delivery_location'),
4612
- o.customer_id, v_close)
4613
- ON CONFLICT (buffer_key) DO UPDATE SET grouping_mode = EXCLUDED.grouping_mode
4614
- RETURNING id INTO v_buffer;
4615
- EXIT;
4616
- END IF;
4617
- IF o.buffer_id = v_buffer THEN EXIT; END IF;
4618
-
4619
- -- 1.0.164: ook een buffer met een geprinte, gescande of verstuurde kar/order is dicht voor nieuwe orders.
4620
- v_closed := public.logistics_buffer_closed_for_join(v_buffer);
4621
-
4622
- IF NOT COALESCE(v_closed, false) THEN
4623
- UPDATE public.order_buffers
4624
- SET close_at = LEAST(COALESCE(close_at, v_close), COALESCE(v_close, close_at)), updated_at = now()
4625
- WHERE id = v_buffer;
4626
- EXIT;
4627
- END IF;
4628
- UPDATE public.order_buffers SET closed_at = COALESCE(closed_at, now()), updated_at = now() WHERE id = v_buffer;
4629
- v_try := v_try + 1; v_buffer := NULL;
4630
- IF v_try > 50 THEN RAISE EXCEPTION 'Kan geen open orderbuffer vinden voor sleutel %', v_ref; END IF;
4631
- END LOOP;
4632
-
4633
- IF o.buffer_id IS DISTINCT FROM v_buffer THEN
4634
- PERFORM set_config('logistics.buffer_writer', 'engine', true);
4635
- UPDATE public.orders SET buffer_id = v_buffer WHERE id = o.id;
4636
- DELETE FROM public.order_buffers b
4637
- WHERE NOT EXISTS (SELECT 1 FROM public.orders x WHERE x.buffer_id = b.id)
4638
- AND NOT EXISTS (SELECT 1 FROM public.order_carts oc WHERE oc.buffer_id = b.id);
4639
- END IF;
4640
- RETURN v_buffer;
4641
- END;
4642
- $function$
4643
- $fsdef$;
4644
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
4645
- RAISE NOTICE 'logistiek-engine: functie public.logistics_apply_buffer_decision overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
4646
- END $fsdo$;
4647
-
4648
- DO $fsdo$ BEGIN
4649
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_apply_cart_swap(p_delete_ids uuid[], p_new_carts jsonb)
4650
- RETURNS jsonb
4651
- LANGUAGE plpgsql
4652
- SECURITY DEFINER
4653
- SET search_path TO 'public'
4654
- AS $function$
4655
- DECLARE
4656
- v_del uuid[] := COALESCE(p_delete_ids, ARRAY[]::uuid[]);
4657
- v_cart jsonb; v_row jsonb; v_id uuid; v_map jsonb := '[]'::jsonb;
4658
- v_jobs int := 0; v_carts int := 0; v_items int := 0;
4659
- BEGIN
4660
- IF jsonb_typeof(COALESCE(p_new_carts, '[]'::jsonb)) <> 'array' THEN
4661
- RAISE EXCEPTION 'Nieuwe karren ontbreken of zijn ongeldig.';
4662
- END IF;
4663
-
4664
- IF array_length(v_del, 1) IS NOT NULL THEN
4665
- UPDATE public.print_jobs
4666
- SET status = 'geannuleerd'::print_job_status,
4667
- foutmelding = 'Vervallen: kar verwijderd bij herindeling',
4668
- updated_at = now()
4669
- WHERE status IN ('wachtend'::print_job_status, 'bezig'::print_job_status)
4670
- AND (payload->>'cart_id') = ANY(SELECT id::text FROM unnest(v_del) AS id);
4671
- GET DIAGNOSTICS v_jobs = ROW_COUNT;
4672
- DELETE FROM public.pakbon_barcode_inhoud WHERE cart_id = ANY(SELECT id::text FROM unnest(v_del) AS id);
4673
- DELETE FROM public.order_cart_layer_positions WHERE cart_id = ANY(v_del);
4674
- DELETE FROM public.order_cart_items WHERE cart_id = ANY(v_del);
4675
- DELETE FROM public.order_carts
4676
- WHERE id = ANY(v_del)
4677
- AND COALESCE(pickup_locked, false) IS FALSE;
4678
- GET DIAGNOSTICS v_carts = ROW_COUNT;
4679
- END IF;
4680
-
4681
- FOR v_cart IN SELECT value FROM jsonb_array_elements(COALESCE(p_new_carts, '[]'::jsonb)) LOOP
4682
- v_row := v_cart->'row';
4683
- v_id := gen_random_uuid();
4684
- INSERT INTO public.order_carts (id, order_id, buffer_id, cart_number, total_carts, is_auto, is_complete, source,
4685
- occupancy_pct, total_layers_used, carrier_type, carrier_type_id, max_layers, weight_kg, algorithm_version, layout_method)
4686
- SELECT v_id, r.order_id, r.buffer_id, r.cart_number, r.total_carts, COALESCE(r.is_auto, true), COALESCE(r.is_complete, false),
4687
- COALESCE(r.source, 'auto'), COALESCE(r.occupancy_pct, 0), COALESCE(r.total_layers_used, 0),
4688
- COALESCE(r.carrier_type, 'trolley'), r.carrier_type_id, r.max_layers, r.weight_kg, COALESCE(r.algorithm_version, 1), r.layout_method
4689
- FROM jsonb_to_record(v_row) AS r(order_id uuid, buffer_id uuid, cart_number integer, total_carts integer, is_auto boolean,
4690
- is_complete boolean, source text, occupancy_pct numeric, total_layers_used numeric, carrier_type text, carrier_type_id uuid,
4691
- max_layers integer, weight_kg numeric, algorithm_version integer, layout_method text);
4692
-
4693
- INSERT INTO public.order_cart_items (cart_id, order_item_id, product_id, product_name, crates, boxes, weight_kg,
4694
- plants_per_crate, crates_per_layer, layers_per_cart, layer_fraction, layer_share_pct, layer_index, crates_in_layer, components)
4695
- SELECT v_id, i.order_item_id, i.product_id, i.product_name, i.crates, i.boxes, i.weight_kg,
4696
- COALESCE(i.plants_per_crate, 1), COALESCE(i.crates_per_layer, 1), COALESCE(i.layers_per_cart, 1),
4697
- COALESCE(i.layer_fraction, 0), COALESCE(i.layer_share_pct, 0), i.layer_index, i.crates_in_layer,
4698
- CASE WHEN jsonb_typeof(i.components) = 'array' THEN i.components END
4699
- FROM jsonb_to_recordset(COALESCE(v_cart->'items', '[]'::jsonb)) AS i(order_item_id uuid, product_id uuid, product_name text,
4700
- crates integer, boxes integer, weight_kg numeric, plants_per_crate integer, crates_per_layer integer, layers_per_cart integer,
4701
- layer_fraction numeric, layer_share_pct numeric, layer_index integer, crates_in_layer integer, components jsonb);
4702
- v_items := v_items + jsonb_array_length(COALESCE(v_cart->'items', '[]'::jsonb));
4703
4551
 
4704
- v_map := v_map || jsonb_build_object('planIndex', (v_cart->>'planIndex')::integer, 'id', v_id);
4705
- END LOOP;
4706
-
4707
- RETURN jsonb_build_object('karren', v_map, 'verwijderd', v_carts, 'printopdrachtenGeannuleerd', v_jobs, 'regels', v_items);
4708
- END;
4709
- $function$
4710
- $fsdef$;
4711
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
4712
- RAISE NOTICE 'logistiek-engine: functie public.logistics_apply_cart_swap overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
4713
- END $fsdo$;
4714
4552
 
4715
4553
  DO $fsdo$ BEGIN
4716
4554
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_apply_document_layouts()
@@ -5600,80 +5438,6 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
5600
5438
  RAISE NOTICE 'logistiek-engine: functie public.logistics_blocked_overview overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
5601
5439
  END $fsdo$;
5602
5440
 
5603
- DO $fsdo$ BEGIN
5604
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_brief_transport(p_order_id uuid, p_cart_id uuid DEFAULT NULL::uuid)
5605
- RETURNS jsonb
5606
- LANGUAGE plpgsql
5607
- STABLE SECURITY DEFINER
5608
- SET search_path TO 'public'
5609
- AS $function$
5610
- DECLARE
5611
- o record; v_carrier_id uuid; v_bron text; ca public.carriers%ROWTYPE;
5612
- v_naam text; v_code text; v_date date; v_time time; n int;
5613
- v_wens text; onbekend text[] := '{}'::text[]; uit jsonb := '{}'::jsonb;
5614
- BEGIN
5615
- SELECT id, custom_fields, floriday_sales_order_id INTO o FROM public.orders WHERE id = p_order_id AND deleted_at IS NULL;
5616
- IF o.id IS NULL THEN
5617
- RETURN jsonb_build_object('onbekend', jsonb_build_array('order'));
5618
- END IF;
5619
-
5620
- IF coalesce(o.custom_fields->>'carrier','') ~* '^[0-9a-f-]{36}$' THEN
5621
- v_carrier_id := (o.custom_fields->>'carrier')::uuid; v_bron := 'order';
5622
- ELSIF coalesce(o.custom_fields->>'delivery_location','') ~* '^[0-9a-f-]{36}$' THEN
5623
- SELECT carrier_id INTO v_carrier_id FROM public.customer_delivery_locations
5624
- WHERE id = (o.custom_fields->>'delivery_location')::uuid;
5625
- v_bron := 'afleverlocatie';
5626
- END IF;
5627
- IF v_carrier_id IS NOT NULL THEN
5628
- SELECT * INTO ca FROM public.carriers WHERE id = v_carrier_id;
5629
- END IF;
5630
-
5631
- IF ca.id IS NOT NULL AND nullif(trim(ca.name),'') IS NOT NULL THEN
5632
- uit := uit || jsonb_build_object('vervoerder', jsonb_build_object('naam', ca.name), 'vervoerder_bron', v_bron);
5633
- ELSE
5634
- SELECT count(*), min(fc.name), min(nullif(fc.gln_code,'')) INTO n, v_naam, v_code
5635
- FROM public.floriday_delivery_orders_cache d
5636
- JOIN public.floriday_carriers_cache fc ON fc.organization_id::text = d.data->>'carrierOrganizationId'
5637
- WHERE o.floriday_sales_order_id IS NOT NULL
5638
- AND d.data::text LIKE '%' || o.floriday_sales_order_id::text || '%'
5639
- AND coalesce(d.is_deleted,false) = false;
5640
- IF n = 1 AND nullif(trim(v_naam),'') IS NOT NULL THEN
5641
- uit := uit || jsonb_build_object('vervoerder', jsonb_build_object('naam', v_naam) ||
5642
- CASE WHEN v_code IS NOT NULL THEN jsonb_build_object('code', v_code) ELSE '{}'::jsonb END,
5643
- 'vervoerder_bron', 'floriday');
5644
- ELSE
5645
- onbekend := array_append(onbekend, CASE WHEN n > 1 THEN 'vervoerder_niet_eenduidig' ELSE 'vervoerder' END);
5646
- END IF;
5647
- END IF;
5648
-
5649
- SELECT pick_date INTO v_date FROM public.logistics_order_pick_day WHERE order_id = p_order_id;
5650
- IF v_date IS NULL OR ca.id IS NULL OR jsonb_typeof(ca.pickup_moments) IS DISTINCT FROM 'array' THEN
5651
- onbekend := array_append(onbekend, 'ophaalmoment');
5652
- ELSIF ca.pickup_weekdays IS NOT NULL AND array_length(ca.pickup_weekdays,1) > 0
5653
- AND NOT (extract(isodow FROM v_date)::int = ANY (ca.pickup_weekdays)) THEN
5654
- onbekend := array_append(onbekend, 'ophaalmoment');
5655
- ELSE
5656
- v_wens := nullif(o.custom_fields->>'pickup_moment','');
5657
- SELECT count(*), min(nullif(m->>'pickup_time','')::time) INTO n, v_time
5658
- FROM jsonb_array_elements(ca.pickup_moments) m
5659
- WHERE nullif(m->>'pickup_time','') IS NOT NULL
5660
- AND coalesce(nullif(m->>'pickup_days',''),'0') = '0'
5661
- AND (v_wens IS NULL OR left(m->>'pickup_time',5) = left(v_wens,5));
5662
- IF n = 1 THEN
5663
- uit := uit || jsonb_build_object('ophaalmoment',
5664
- to_char(((v_date + v_time) AT TIME ZONE public.logistics_tz()) AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"'));
5665
- ELSE
5666
- onbekend := array_append(onbekend, CASE WHEN n > 1 THEN 'ophaalmoment_niet_eenduidig' ELSE 'ophaalmoment' END);
5667
- END IF;
5668
- END IF;
5669
-
5670
- RETURN uit || jsonb_build_object('onbekend', to_jsonb(onbekend));
5671
- END;
5672
- $function$
5673
- $fsdef$;
5674
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
5675
- RAISE NOTICE 'logistiek-engine: functie public.logistics_brief_transport overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
5676
- END $fsdo$;
5677
5441
 
5678
5442
  DO $fsdo$ BEGIN
5679
5443
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_buffer_closed_for_join(p_buffer uuid)
@@ -6240,36 +6004,6 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
6240
6004
  RAISE NOTICE 'logistiek-engine: functie public.logistics_buffer_open_key overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
6241
6005
  END $fsdo$;
6242
6006
 
6243
- DO $fsdo$ BEGIN
6244
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_buffer_payload(p_order_id uuid)
6245
- RETURNS jsonb
6246
- LANGUAGE sql
6247
- STABLE SECURITY DEFINER
6248
- SET search_path TO 'public'
6249
- AS $function$
6250
- SELECT jsonb_build_object(
6251
- 'orderRef', COALESCE(o.order_number, o.id::text),
6252
- 'checkinAt', COALESCE(cf ->> 'checkin_at', cf ->> 'checkin_moment', cf ->> 'pickup_moment', cf ->> 'pickup_datetime'),
6253
- 'pickupAt', COALESCE(cf ->> 'pickup_datetime', cf ->> 'pickup_moment', cf ->> 'checkin_at'),
6254
- 'customerRef', o.customer_id::text,
6255
- 'floridayCustomerRef', COALESCE(cf ->> 'floriday_customer_id', cf ->> 'floriday_customer_ref'),
6256
- 'floridaySalesOrderRef', COALESCE(o.floriday_sales_order_id::text, cf ->> 'floriday_sales_order_id'),
6257
- 'bvoCustomerRef', COALESCE(cf ->> 'bvo_customer_ref', cf ->> 'bvo_customer_id'),
6258
- 'deliveryLocationRef', COALESCE(cf ->> 'delivery_location_gln', cf ->> 'delivery_location_id', cf ->> 'delivery_location'),
6259
- 'deliveryDate', o.delivery_date::text,
6260
- 'carrierRef', COALESCE(cf ->> 'carrier_id', cf ->> 'carrier'),
6261
- 'loadCarrierType', COALESCE(cf ->> 'load_carrier_type', 'trolley'),
6262
- 'orderType', COALESCE(cf ->> 'order_type', o.order_source, 'standaard'),
6263
- 'isReservation', COALESCE(o.is_reservation, false)
6264
- )
6265
- FROM public.orders o
6266
- CROSS JOIN LATERAL (SELECT COALESCE(o.custom_fields, '{}'::jsonb) AS cf) x
6267
- WHERE o.id = p_order_id;
6268
- $function$
6269
- $fsdef$;
6270
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
6271
- RAISE NOTICE 'logistiek-engine: functie public.logistics_buffer_payload overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
6272
- END $fsdo$;
6273
6007
 
6274
6008
  DO $fsdo$ BEGIN
6275
6009
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_buffer_simulate(p_orders jsonb DEFAULT '[]'::jsonb, p_strategy_id uuid DEFAULT NULL::uuid)
@@ -6756,45 +6490,6 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
6756
6490
  RAISE NOTICE 'logistiek-engine: functie public.logistics_capacity_plan_day overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
6757
6491
  END $fsdo$;
6758
6492
 
6759
- DO $fsdo$ BEGIN
6760
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_carrier_for_location(p_location text, p_customer_id uuid)
6761
- RETURNS uuid
6762
- LANGUAGE plpgsql
6763
- STABLE SECURITY DEFINER
6764
- SET search_path TO 'public'
6765
- AS $function$
6766
- DECLARE v_raw text := nullif(btrim(coalesce(p_location,'')),''); v_lid uuid := public.logistics_try_uuid(v_raw);
6767
- dl_country text; dl_province text; dl_city text; c_country text; c_city text; v_id uuid; v_country text; v_city text; v_prov text;
6768
- BEGIN
6769
- IF v_raw IS NOT NULL THEN
6770
- SELECT l.country, l.province, l.city INTO dl_country, dl_province, dl_city FROM public.customer_delivery_locations l
6771
- WHERE (v_lid IS NOT NULL AND l.id = v_lid) OR l.gln = v_raw
6772
- ORDER BY (v_lid IS NOT NULL AND l.id = v_lid) DESC
6773
- LIMIT 1;
6774
- END IF;
6775
- IF p_customer_id IS NOT NULL THEN
6776
- SELECT cu.country, cu.city INTO c_country, c_city FROM public.customers cu WHERE cu.id = p_customer_id;
6777
- END IF;
6778
- v_country := public.afh_normalize_country(coalesce(dl_country, c_country));
6779
- v_city := public.afh_city_code(v_country, coalesce(dl_city, c_city));
6780
- v_prov := public.afh_normalize_nl_province(dl_province);
6781
- SELECT ca.id INTO v_id FROM public.carriers ca
6782
- WHERE nullif(trim(ca.email),'') IS NOT NULL
6783
- AND ((v_country = ANY(ca.service_countries))
6784
- OR (v_prov = ANY(ca.service_provinces))
6785
- OR (v_city = ANY(ca.service_cities))
6786
- OR ca.is_default)
6787
- ORDER BY CASE WHEN ca.is_default AND NOT (
6788
- (v_country = ANY(ca.service_countries))
6789
- OR (v_prov = ANY(ca.service_provinces))
6790
- OR (v_city = ANY(ca.service_cities))) THEN 1 ELSE 0 END, ca.sort_order, ca.id
6791
- LIMIT 1;
6792
- RETURN v_id;
6793
- END $function$
6794
- $fsdef$;
6795
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
6796
- RAISE NOTICE 'logistiek-engine: functie public.logistics_carrier_for_location overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
6797
- END $fsdo$;
6798
6493
 
6799
6494
  DO $fsdo$ BEGIN
6800
6495
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_carrier_minutes(p_carrier text, p_settings jsonb)
@@ -7873,49 +7568,11 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
7873
7568
  RAISE NOTICE 'logistiek-engine: functie public.logistics_cart_scanned overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
7874
7569
  END $fsdo$;
7875
7570
 
7571
+
7876
7572
  DO $fsdo$ BEGIN
7877
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_cart_seq(p_order_uuid uuid DEFAULT NULL::uuid)
7878
- RETURNS TABLE(order_uuid uuid, cart_id uuid, cart_index integer, cart_seq integer, cart_number text, cart_total integer)
7879
- LANGUAGE sql
7880
- STABLE SECURITY DEFINER
7881
- SET search_path TO 'public'
7882
- AS $function$
7883
- WITH deelname_ruw AS (
7884
- SELECT oc.id AS cart_id, oc.cart_number AS cart_index, oc.order_id AS order_uuid, oc.created_at
7885
- FROM public.order_carts oc
7886
- WHERE oc.order_id IS NOT NULL AND (p_order_uuid IS NULL OR oc.order_id = p_order_uuid)
7887
- UNION
7888
- SELECT oc.id, oc.cart_number, oi.order_id, oc.created_at
7889
- FROM public.order_carts oc
7890
- JOIN public.order_cart_items oci ON oci.cart_id = oc.id
7891
- JOIN logistics_active.order_items oi ON oi.id = oci.order_item_id
7892
- WHERE oc.order_id IS NULL AND oi.order_id IS NOT NULL AND (p_order_uuid IS NULL OR oi.order_id = p_order_uuid)
7893
- ),
7894
- deelname AS (
7895
- SELECT DISTINCT ON (d.order_uuid, d.cart_index) d.*
7896
- FROM deelname_ruw d
7897
- ORDER BY d.order_uuid, d.cart_index, d.created_at DESC, d.cart_id
7898
- ),
7899
- genummerd AS (
7900
- SELECT d.order_uuid, d.cart_id, d.cart_index,
7901
- dense_rank() OVER (PARTITION BY d.order_uuid ORDER BY d.cart_index)::int AS cart_seq,
7902
- count(*) OVER (PARTITION BY d.order_uuid)::int AS cart_total
7903
- FROM deelname d
7904
- )
7905
- SELECT g.order_uuid, g.cart_id, g.cart_index, g.cart_seq,
7906
- COALESCE(cn.cart_number, g.cart_seq::text) AS cart_number, g.cart_total
7907
- FROM genummerd g
7908
- LEFT JOIN public.cart_numbers cn ON cn.order_id = g.order_uuid::text AND cn.cart_index = g.cart_index;
7909
- $function$
7910
- $fsdef$;
7911
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
7912
- RAISE NOTICE 'logistiek-engine: functie public.logistics_cart_seq overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
7913
- END $fsdo$;
7914
-
7915
- DO $fsdo$ BEGIN
7916
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_cart_slip_plan(p_order_id uuid)
7917
- RETURNS jsonb
7918
- LANGUAGE plpgsql
7573
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_cart_slip_plan(p_order_id uuid)
7574
+ RETURNS jsonb
7575
+ LANGUAGE plpgsql
7919
7576
  STABLE SECURITY DEFINER
7920
7577
  SET search_path TO 'public'
7921
7578
  AS $function$
@@ -9684,111 +9341,6 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
9684
9341
  RAISE NOTICE 'logistiek-engine: functie public.logistics_document_layout overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
9685
9342
  END $fsdo$;
9686
9343
 
9687
- DO $fsdo$ BEGIN
9688
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_document_layout_v2(_document_code text, _is_partial boolean DEFAULT false)
9689
- RETURNS jsonb
9690
- LANGUAGE plpgsql
9691
- STABLE SECURITY DEFINER
9692
- SET search_path TO 'public'
9693
- AS $function$
9694
- DECLARE
9695
- v_types public.doc_layout_type[];
9696
- v_code text;
9697
- v_keys text[];
9698
- v_key text;
9699
- v_fields jsonb;
9700
- v_chosen uuid;
9701
- v_used_key text;
9702
- v_layout public.document_layouts%ROWTYPE;
9703
- BEGIN
9704
- v_code := lower(btrim(coalesce(_document_code, '')));
9705
- v_types := public.logistics_document_type_map(v_code);
9706
-
9707
- -- Geordende sleutellijst: eigen instelling eerst, daarna de terugval.
9708
- -- Pakbon kent twee varianten: een volle kar en een deelorder (geen volle kar).
9709
- v_keys := CASE
9710
- WHEN v_code LIKE '%pakbon%' AND COALESCE(_is_partial, false)
9711
- THEN ARRAY['packing_slip_partial_layout', 'packing_slip_layout']
9712
- WHEN v_code LIKE '%pakbon%' THEN ARRAY['packing_slip_layout']
9713
- WHEN v_code LIKE '%sticker%' OR v_code LIKE '%etiket%' THEN ARRAY['sticker_layout']
9714
- WHEN v_code LIKE '%afleverbrief%' OR v_code LIKE '%afleverbon%'
9715
- THEN ARRAY['delivery_note_layout', 'shipping_note_layout']
9716
- WHEN v_code LIKE '%veilingbrief%' OR v_code LIKE '%veilingbon%'
9717
- THEN ARRAY['auction_note_layout', 'shipping_note_layout']
9718
- WHEN v_code LIKE '%verzendbrief%' OR v_code LIKE '%transportbrief%'
9719
- OR v_code LIKE '%verzendlabel%' THEN ARRAY['shipping_note_layout']
9720
- WHEN v_code LIKE '%productiebon%' OR v_code LIKE '%productielijst%'
9721
- THEN ARRAY['production_note_layout']
9722
- ELSE NULL
9723
- END;
9724
-
9725
- IF v_keys IS NOT NULL THEN
9726
- FOREACH v_key IN ARRAY v_keys LOOP
9727
- SELECT settings->'pickstations'->'fields'
9728
- INTO v_fields
9729
- FROM public.logistics_rule_configs
9730
- WHERE nullif(settings->'pickstations'->'fields'->>v_key, '') IS NOT NULL
9731
- ORDER BY is_active DESC, (status = 'actief') DESC, updated_at DESC
9732
- LIMIT 1;
9733
-
9734
- BEGIN
9735
- v_chosen := nullif(v_fields->>v_key, '')::uuid;
9736
- EXCEPTION WHEN invalid_text_representation THEN
9737
- v_chosen := NULL;
9738
- END;
9739
-
9740
- IF v_chosen IS NOT NULL THEN
9741
- v_used_key := v_key;
9742
- EXIT;
9743
- END IF;
9744
- END LOOP;
9745
- END IF;
9746
-
9747
- IF v_chosen IS NOT NULL THEN
9748
- SELECT * INTO v_layout
9749
- FROM public.document_layouts
9750
- WHERE id = v_chosen
9751
- AND actief IS TRUE;
9752
- IF v_layout.id IS NULL THEN
9753
- v_used_key := NULL;
9754
- END IF;
9755
- END IF;
9756
-
9757
- IF v_layout.id IS NULL AND array_length(v_types, 1) IS NOT NULL THEN
9758
- SELECT * INTO v_layout
9759
- FROM public.document_layouts
9760
- WHERE actief IS TRUE
9761
- AND documenttype = ANY (v_types)
9762
- ORDER BY array_position(v_types, documenttype), is_standaard DESC, updated_at DESC
9763
- LIMIT 1;
9764
- END IF;
9765
-
9766
- IF v_layout.id IS NULL THEN
9767
- RETURN NULL;
9768
- END IF;
9769
-
9770
- RETURN jsonb_build_object(
9771
- 'layoutId', v_layout.id,
9772
- 'layoutName', v_layout.naam,
9773
- 'layoutVersion', v_layout.huidige_versie,
9774
- 'layoutDocumentType', v_layout.documenttype::text,
9775
- 'layoutSettingKey', v_used_key,
9776
- 'layoutVariant', CASE WHEN COALESCE(_is_partial, false) THEN 'partial_order' ELSE 'full_cart' END,
9777
- 'layoutFallbackUsed', (v_used_key IS NOT NULL AND v_keys IS NOT NULL AND v_used_key <> v_keys[1]),
9778
- 'layout_id', v_layout.id,
9779
- 'layout_name', v_layout.naam,
9780
- 'layout_version', v_layout.huidige_versie,
9781
- 'layout_document_type', v_layout.documenttype::text,
9782
- 'layout_setting_key', v_used_key,
9783
- 'layout_variant', CASE WHEN COALESCE(_is_partial, false) THEN 'partial_order' ELSE 'full_cart' END,
9784
- 'layout_fallback_used', (v_used_key IS NOT NULL AND v_keys IS NOT NULL AND v_used_key <> v_keys[1])
9785
- );
9786
- END;
9787
- $function$
9788
- $fsdef$;
9789
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
9790
- RAISE NOTICE 'logistiek-engine: functie public.logistics_document_layout_v2 overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
9791
- END $fsdo$;
9792
9344
 
9793
9345
  DO $fsdo$ BEGIN
9794
9346
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_document_station(_document_code text)
@@ -12310,30 +11862,6 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
12310
11862
  RAISE NOTICE 'logistiek-engine: functie public.logistics_order_buffer_excluded overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
12311
11863
  END $fsdo$;
12312
11864
 
12313
- DO $fsdo$ BEGIN
12314
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_order_carrier_id(p_order_id uuid)
12315
- RETURNS uuid
12316
- LANGUAGE plpgsql
12317
- STABLE SECURITY DEFINER
12318
- SET search_path TO 'public'
12319
- AS $function$
12320
- DECLARE v_own uuid; v_id uuid; o record;
12321
- BEGIN
12322
- SELECT ord.custom_fields cf, ord.customer_id INTO o FROM public.orders ord WHERE ord.id = p_order_id;
12323
- IF NOT FOUND THEN RETURN NULL; END IF;
12324
- v_own := public.logistics_try_uuid(o.cf->>'carrier');
12325
- IF v_own IS NOT NULL THEN
12326
- SELECT id INTO v_id FROM public.carriers WHERE id = v_own;
12327
- IF v_id IS NOT NULL THEN RETURN v_id; END IF;
12328
- END IF;
12329
- RETURN public.logistics_carrier_for_location(
12330
- coalesce(nullif(o.cf->>'delivery_location',''), nullif(o.cf->>'delivery_location_id',''), nullif(o.cf->>'delivery_location_gln','')),
12331
- o.customer_id);
12332
- END $function$
12333
- $fsdef$;
12334
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
12335
- RAISE NOTICE 'logistiek-engine: functie public.logistics_order_carrier_id overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
12336
- END $fsdo$;
12337
11865
 
12338
11866
  DO $fsdo$ BEGIN
12339
11867
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_order_cart_approval(_order_ref text)
@@ -12548,48 +12076,6 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
12548
12076
  RAISE NOTICE 'logistiek-engine: functie public.logistics_order_definitively_blocked overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
12549
12077
  END $fsdo$;
12550
12078
 
12551
- DO $fsdo$ BEGIN
12552
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_order_departure(p_order_id uuid)
12553
- RETURNS jsonb
12554
- LANGUAGE plpgsql
12555
- STABLE SECURITY DEFINER
12556
- SET search_path TO 'public'
12557
- AS $function$
12558
- DECLARE o record; v jsonb; v_own jsonb; v_txt text; v_at timestamptz;
12559
- BEGIN
12560
- IF public.logistics_tz() IS NULL THEN
12561
- RETURN jsonb_build_object('ok', false, 'reason', 'De tijdzone van de vestiging ontbreekt. Stel die in bij Werktijden.');
12562
- END IF;
12563
- SELECT id, created_at, delivery_date, custom_fields INTO o FROM public.orders WHERE id = p_order_id;
12564
- IF NOT FOUND OR coalesce(o.delivery_date,'') !~ '^\d{4}-\d{2}-\d{2}' THEN
12565
- RETURN jsonb_build_object('ok', false, 'reason', 'De leverdatum van de order is onbekend.');
12566
- END IF;
12567
-
12568
- v_own := CASE WHEN jsonb_typeof(o.custom_fields->'own_transport') = 'object' THEN o.custom_fields->'own_transport' END;
12569
- IF v_own IS NOT NULL THEN
12570
- v_txt := nullif(btrim(coalesce(v_own->>'pickup_at', '')), '');
12571
- IF v_txt IS NOT NULL THEN
12572
- IF v_txt !~ '(Z|[+-]\d{2}(:?\d{2})?)$' THEN
12573
- RETURN jsonb_build_object('ok', false, 'ownTransport', true,
12574
- 'reason', 'De ophaaltijd bij eigen vervoer mist een tijdzone.');
12575
- END IF;
12576
- BEGIN
12577
- v_at := v_txt::timestamptz;
12578
- EXCEPTION WHEN OTHERS THEN
12579
- RETURN jsonb_build_object('ok', false, 'ownTransport', true,
12580
- 'reason', 'De ophaaltijd bij eigen vervoer is geen geldige tijd.');
12581
- END;
12582
- END IF;
12583
- RETURN public.logistics_own_transport_departure(left(o.delivery_date,10)::date, v_at);
12584
- END IF;
12585
-
12586
- v := public.logistics_departure_calc(public.logistics_order_carrier_id(p_order_id), left(o.delivery_date,10)::date, o.created_at, NULL);
12587
- RETURN v - 'moments';
12588
- END $function$
12589
- $fsdef$;
12590
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
12591
- RAISE NOTICE 'logistiek-engine: functie public.logistics_order_departure overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
12592
- END $fsdo$;
12593
12079
 
12594
12080
  DO $fsdo$ BEGIN
12595
12081
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_order_departure_list(p_order_ids uuid[])
@@ -13866,70 +13352,6 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
13866
13352
  RAISE NOTICE 'logistiek-engine: functie public.logistics_own_transport_departure overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
13867
13353
  END $fsdo$;
13868
13354
 
13869
- DO $fsdo$ BEGIN
13870
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_packing_autofix(p_limit integer DEFAULT 100)
13871
- RETURNS integer
13872
- LANGUAGE plpgsql
13873
- SECURITY DEFINER
13874
- SET search_path TO 'public'
13875
- AS $function$
13876
- DECLARE
13877
- r record;
13878
- v_reden text;
13879
- v_aantal integer := 0;
13880
- BEGIN
13881
- FOR r IN
13882
- SELECT d.*
13883
- FROM public.logistics_packing_deviations d
13884
- WHERE d.resolved_at IS NULL
13885
- AND d.scope = 'order_line'
13886
- AND d.needs_human = false
13887
- AND d.order_id IS NOT NULL
13888
- AND d.order_item_id IS NOT NULL
13889
- ORDER BY d.first_seen_at
13890
- LIMIT GREATEST(1, LEAST(p_limit, 500))
13891
- LOOP
13892
- v_reden := public.logistics_packing_block_reason(r.order_id, r.order_item_id);
13893
-
13894
- IF v_reden IS NOT NULL THEN
13895
- UPDATE public.logistics_packing_deviations
13896
- SET needs_human = true, human_reason = v_reden
13897
- WHERE id = r.id;
13898
- CONTINUE;
13899
- END IF;
13900
-
13901
- UPDATE public.order_items oi
13902
- SET plants_per_crate = COALESCE(
13903
- (r.floriday_packing->>'pieces_per_package')::int, oi.plants_per_crate),
13904
- crates_per_layer = COALESCE(
13905
- (r.floriday_packing->>'packages_per_layer')::int, oi.crates_per_layer),
13906
- layers_per_cart = COALESCE(
13907
- (r.floriday_packing->>'layers_per_load_carrier')::int, oi.layers_per_cart)
13908
- WHERE oi.id = r.order_item_id
13909
- AND oi.packing_locked_at IS NULL;
13910
-
13911
- IF FOUND THEN
13912
- v_aantal := v_aantal + 1;
13913
- UPDATE public.logistics_packing_deviations
13914
- SET auto_fixed_at = now(),
13915
- resolved_at = now(),
13916
- human_reason = 'automatisch bijgewerkt naar de Floriday-stand'
13917
- WHERE id = r.id;
13918
-
13919
- PERFORM public.logistics_trace_event(
13920
- r.order_id, 'stap:belading_synchronisatie', 'ok',
13921
- 'belading bijgewerkt naar de Floriday-stand'
13922
- );
13923
- END IF;
13924
- END LOOP;
13925
-
13926
- RETURN v_aantal;
13927
- END;
13928
- $function$
13929
- $fsdef$;
13930
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
13931
- RAISE NOTICE 'logistiek-engine: functie public.logistics_packing_autofix overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
13932
- END $fsdo$;
13933
13355
 
13934
13356
  DO $fsdo$ BEGIN
13935
13357
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_packing_block_reason(p_order_id uuid, p_order_item_id uuid)
@@ -17241,346 +16663,41 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
17241
16663
  RAISE NOTICE 'logistiek-engine: functie public.logistics_recalc_tick_kick overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
17242
16664
  END $fsdo$;
17243
16665
 
16666
+
16667
+
17244
16668
  DO $fsdo$ BEGIN
17245
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_recompute_reserved_targeted(p_product_id uuid)
17246
- RETURNS jsonb
16669
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_refresh_pick_queue_status(p_ref text)
16670
+ RETURNS integer
17247
16671
  LANGUAGE plpgsql
17248
16672
  SECURITY DEFINER
17249
16673
  SET search_path TO 'public'
17250
16674
  AS $function$
17251
- DECLARE
17252
- v_t0 timestamptz := clock_timestamp();
17253
- v_max_lines constant integer := 500;
17254
- v_max_keys constant integer := 200;
17255
- v_trace_ms constant numeric := 500;
17256
- v_nkeys integer; v_nkeys_prev integer := -1; v_nlines integer := 0; v_changed integer := 0; v_ms numeric;
17257
- oi_rec record; part record; member_rec record;
17258
- v_key text; v_last_key text; v_remaining numeric; v_available numeric; v_take numeric; v_err text;
17259
- v_skipped jsonb := '[]'::jsonb; v_skipped_qty numeric := 0;
17260
- v_struct jsonb := '[]'::jsonb; v_struct_qty numeric := 0;
17261
- v_freeze boolean := public.logistics_plan_setting('verdeling_bevriezen_actief', 0) = 1;
17262
- v_frozen_ids uuid[]; v_line_ids uuid[];
17263
- BEGIN
17264
- IF p_product_id IS NULL THEN RETURN jsonb_build_object('status', 'geen_product'); END IF;
17265
- IF current_setting('app.recomputing_reserved', true) = 'on' THEN RETURN jsonb_build_object('status', 'al_bezig'); END IF;
17266
- PERFORM set_config('app.recomputing_reserved', 'on', true);
17267
-
17268
- DROP TABLE IF EXISTS _rt_open;
17269
- DROP TABLE IF EXISTS _rt_pexp;
17270
- DROP TABLE IF EXISTS _rt_gm;
17271
- DROP TABLE IF EXISTS _rt_raw;
17272
- DROP TABLE IF EXISTS _rt_pkeys;
17273
- DROP TABLE IF EXISTS _rt_keys;
17274
- DROP TABLE IF EXISTS _rt_lines;
17275
- DROP TABLE IF EXISTS _rt_demand;
17276
- DROP TABLE IF EXISTS _rt_alloc;
17277
- DROP TABLE IF EXISTS _rt_frozen;
17278
- CREATE TEMP TABLE _rt_open (order_item_id uuid PRIMARY KEY, product_id uuid, qty numeric, created_at timestamptz, order_id uuid) ON COMMIT DROP;
17279
- CREATE TEMP TABLE _rt_pexp (product_id uuid, ord bigint, kind text, ref text, qty numeric, note text) ON COMMIT DROP;
17280
- CREATE TEMP TABLE _rt_gm (group_id uuid, raw_key text, canon_key text, ord bigint) ON COMMIT DROP;
17281
- CREATE TEMP TABLE _rt_raw (raw_key text PRIMARY KEY, canon_key text) ON COMMIT DROP;
17282
- CREATE TEMP TABLE _rt_pkeys (product_id uuid, canon_key text) ON COMMIT DROP;
17283
- CREATE TEMP TABLE _rt_keys (canon_key text PRIMARY KEY) ON COMMIT DROP;
17284
- CREATE TEMP TABLE _rt_lines (order_item_id uuid PRIMARY KEY) ON COMMIT DROP;
17285
- CREATE TEMP TABLE _rt_demand (trade_item_key text PRIMARY KEY, qty numeric NOT NULL DEFAULT 0) ON COMMIT DROP;
17286
- CREATE TEMP TABLE _rt_alloc (order_item_id uuid, trade_item_key text, qty numeric, source text, group_id uuid) ON COMMIT DROP;
17287
- CREATE TEMP TABLE _rt_frozen (order_item_id uuid PRIMARY KEY) ON COMMIT DROP;
17288
-
17289
- INSERT INTO _rt_open
17290
- SELECT oi.id, oi.product_id, COALESCE(oi.quantity, 0)::numeric, o.created_at, oi.order_id
17291
- FROM logistics_active.order_items oi
17292
- JOIN public.orders o ON o.id = oi.order_id
17293
- WHERE o.status = 'Nog te picken' AND NOT public.logistics_reserved_order_excluded(o.id)
17294
- AND COALESCE(oi.quantity, 0) > 0
17295
- AND oi.product_id IS NOT NULL
17296
- AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log sml WHERE sml.order_item_id = oi.id AND sml.mutation_type = 'consumption');
17297
- -- Groepsregel naast eigen ledenregels: nooit reserveren.
17298
- DELETE FROM _rt_open o WHERE o.order_item_id IN (SELECT d.order_item_id FROM public.logistics_double_group_lines(NULL) d);
17299
-
17300
- INSERT INTO _rt_pexp (product_id, ord, kind, ref, qty, note)
17301
- SELECT d.pid, e.ord, e.kind, e.ref, e.qty, e.note
17302
- FROM (SELECT DISTINCT product_id AS pid FROM _rt_open UNION SELECT p_product_id) d,
17303
- LATERAL public.logistics_expand_line(d.pid, 1) WITH ORDINALITY AS e(kind, ref, qty, note, ord);
17304
-
17305
- INSERT INTO _rt_gm (group_id, raw_key, ord)
17306
- SELECT g.gid, m.raw_key, m.ord
17307
- FROM (SELECT DISTINCT ref::uuid AS gid FROM _rt_pexp WHERE kind = 'group') g,
17308
- LATERAL (
17309
- SELECT public.resolve_group_member_stock_key(pgi.trade_item_id) AS raw_key,
17310
- row_number() OVER (ORDER BY pgi.sort_order ASC NULLS LAST, pgi.created_at ASC, pgi.id ASC) AS ord
17311
- FROM public.product_group_items pgi
17312
- WHERE pgi.product_group_id = g.gid) m
17313
- WHERE m.raw_key IS NOT NULL
17314
- AND m.raw_key NOT LIKE 'group:%';
17315
-
17316
- INSERT INTO _rt_raw (raw_key, canon_key)
17317
- SELECT x.k, public.logistics_reserved_key(x.k)
17318
- FROM (SELECT ref AS k FROM _rt_pexp WHERE kind = 'item' UNION SELECT raw_key FROM _rt_gm) x;
17319
- UPDATE _rt_gm g SET canon_key = r.canon_key FROM _rt_raw r WHERE r.raw_key = g.raw_key;
17320
-
17321
- INSERT INTO _rt_pkeys (product_id, canon_key)
17322
- SELECT DISTINCT e.product_id, r.canon_key
17323
- FROM _rt_pexp e JOIN _rt_raw r ON r.raw_key = e.ref
17324
- WHERE e.kind = 'item' AND r.canon_key IS NOT NULL
17325
- UNION
17326
- SELECT DISTINCT e.product_id, g.canon_key
17327
- FROM _rt_pexp e JOIN _rt_gm g ON g.group_id = e.ref::uuid
17328
- WHERE e.kind = 'group' AND g.canon_key IS NOT NULL;
17329
-
17330
- INSERT INTO _rt_keys SELECT DISTINCT canon_key FROM _rt_pkeys WHERE product_id = p_product_id ON CONFLICT DO NOTHING;
17331
- LOOP
17332
- SELECT count(*) INTO v_nkeys FROM _rt_keys;
17333
- EXIT WHEN v_nkeys = v_nkeys_prev;
17334
- v_nkeys_prev := v_nkeys;
17335
- INSERT INTO _rt_lines
17336
- SELECT o.order_item_id FROM _rt_open o
17337
- WHERE EXISTS (SELECT 1 FROM _rt_pkeys pk JOIN _rt_keys k ON k.canon_key = pk.canon_key
17338
- WHERE pk.product_id = o.product_id)
17339
- ON CONFLICT DO NOTHING;
17340
- INSERT INTO _rt_keys
17341
- SELECT DISTINCT pk.canon_key FROM _rt_pkeys pk
17342
- JOIN _rt_open o ON o.product_id = pk.product_id
17343
- JOIN _rt_lines l ON l.order_item_id = o.order_item_id
17344
- ON CONFLICT DO NOTHING;
17345
- SELECT count(*) INTO v_nlines FROM _rt_lines;
17346
- IF v_nlines > v_max_lines OR (SELECT count(*) FROM _rt_keys) > v_max_keys THEN
17347
- PERFORM set_config('app.recomputing_reserved', 'off', true);
17348
- PERFORM public.recompute_all_reserved_stock();
17349
- v_ms := round(extract(epoch FROM clock_timestamp() - v_t0) * 1000, 1);
17350
- PERFORM public.logistics_trace(jsonb_build_object(
17351
- 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
17352
- 'actor', 'logistics_recompute_reserved_targeted', 'errorClass', 'omvang',
17353
- 'reason', 'Gerichte herberekening te groot; volledige herberekening uitgevoerd.',
17354
- 'values', jsonb_build_object('product_id', p_product_id, 'regels', v_nlines,
17355
- 'sleutels', (SELECT count(*) FROM _rt_keys), 'duur_ms', v_ms)));
17356
- RETURN jsonb_build_object('status', 'volledig', 'regels', v_nlines, 'duur_ms', v_ms);
17357
- END IF;
17358
- END LOOP;
17359
-
17360
- -- Bevroren regels eerst, naar rato van het huidige aantal.
17361
- IF v_freeze THEN
17362
- INSERT INTO _rt_frozen
17363
- SELECT o.order_item_id FROM _rt_open o JOIN _rt_lines l ON l.order_item_id = o.order_item_id
17364
- WHERE EXISTS (SELECT 1 FROM public.logistics_reservation_allocations a WHERE a.order_item_id = o.order_item_id AND a.source <> 'error')
17365
- AND public.logistics_allocation_frozen(o.order_id)
17366
- ON CONFLICT DO NOTHING;
17367
- INSERT INTO _rt_alloc
17368
- SELECT f.order_item_id, s.trade_item_key, s.qty, s.source, s.group_id
17369
- FROM _rt_frozen f
17370
- JOIN _rt_open o ON o.order_item_id = f.order_item_id
17371
- CROSS JOIN LATERAL public.logistics_frozen_allocation_scaled(f.order_item_id, o.qty) s;
17372
- INSERT INTO _rt_demand AS d
17373
- SELECT trade_item_key, SUM(qty) FROM _rt_alloc GROUP BY trade_item_key
17374
- ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
17375
- INSERT INTO _rt_keys SELECT DISTINCT trade_item_key FROM _rt_alloc ON CONFLICT DO NOTHING;
17376
- END IF;
17377
-
17378
- FOR oi_rec IN
17379
- SELECT o.order_item_id AS id, o.product_id, o.qty
17380
- FROM _rt_open o JOIN _rt_lines l ON l.order_item_id = o.order_item_id
17381
- WHERE NOT EXISTS (SELECT 1 FROM _rt_frozen f WHERE f.order_item_id = o.order_item_id)
17382
- ORDER BY o.created_at, o.order_item_id
17383
- LOOP
17384
- SELECT string_agg(e.note, '; ') INTO v_err FROM _rt_pexp e WHERE e.product_id = oi_rec.product_id AND e.kind = 'error';
17385
- IF v_err IS NOT NULL THEN
17386
- v_struct := v_struct || jsonb_build_object('order_item_id', oi_rec.id, 'product_id', oi_rec.product_id, 'reden', v_err);
17387
- v_struct_qty := v_struct_qty + oi_rec.qty;
17388
- CONTINUE;
17389
- END IF;
17390
-
17391
- FOR part IN
17392
- SELECT e.kind, e.ref, e.qty * oi_rec.qty AS qty
17393
- FROM _rt_pexp e WHERE e.product_id = oi_rec.product_id ORDER BY e.ord
17394
- LOOP
17395
- IF part.kind = 'item' THEN
17396
- SELECT canon_key INTO v_key FROM _rt_raw WHERE raw_key = part.ref;
17397
- IF v_key IS NULL THEN
17398
- v_skipped := v_skipped || to_jsonb(part.ref);
17399
- v_skipped_qty := v_skipped_qty + part.qty;
17400
- ELSE
17401
- INSERT INTO _rt_demand AS d VALUES (v_key, part.qty)
17402
- ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
17403
- INSERT INTO _rt_alloc VALUES (oi_rec.id, v_key, part.qty, 'item', NULL);
17404
- END IF;
17405
- ELSE
17406
- v_remaining := part.qty;
17407
- v_last_key := NULL;
17408
- FOR member_rec IN
17409
- SELECT g.canon_key FROM _rt_gm g WHERE g.group_id = part.ref::uuid ORDER BY g.ord
17410
- LOOP
17411
- v_key := member_rec.canon_key;
17412
- IF v_key IS NULL THEN CONTINUE; END IF;
17413
- v_last_key := v_key;
17414
- IF v_remaining <= 0 THEN CONTINUE; END IF;
17415
-
17416
- SELECT GREATEST(COALESCE(MAX(s.physical_stock), 0)
17417
- - COALESCE((SELECT d.qty FROM _rt_demand d WHERE d.trade_item_key = v_key), 0), 0)
17418
- INTO v_available
17419
- FROM public.trade_item_stock s
17420
- WHERE s.trade_item_key = v_key;
17421
-
17422
- IF COALESCE(v_available, 0) <= 0 THEN CONTINUE; END IF;
17423
- v_take := LEAST(v_available, v_remaining);
17424
- INSERT INTO _rt_demand AS d VALUES (v_key, v_take)
17425
- ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
17426
- INSERT INTO _rt_alloc VALUES (oi_rec.id, v_key, v_take, 'group', part.ref::uuid);
17427
- v_remaining := v_remaining - v_take;
17428
- END LOOP;
17429
-
17430
- IF v_remaining > 0 THEN
17431
- IF v_last_key IS NULL THEN
17432
- v_skipped := v_skipped || to_jsonb('group:' || part.ref);
17433
- v_skipped_qty := v_skipped_qty + v_remaining;
17434
- ELSE
17435
- INSERT INTO _rt_demand AS d VALUES (v_last_key, v_remaining)
17436
- ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
17437
- INSERT INTO _rt_alloc VALUES (oi_rec.id, v_last_key, v_remaining, 'group', part.ref::uuid);
17438
- END IF;
17439
- END IF;
17440
- END IF;
17441
- END LOOP;
17442
- END LOOP;
17443
-
17444
- DELETE FROM public.logistics_reservation_allocations a
17445
- WHERE a.order_item_id IN (SELECT l.order_item_id FROM _rt_lines l);
17446
- INSERT INTO public.logistics_reservation_allocations (order_item_id, trade_item_key, qty, source, group_id, line_qty)
17447
- SELECT r.order_item_id, r.trade_item_key, SUM(r.qty), r.source, r.group_id, MAX(o.qty)
17448
- FROM _rt_alloc r
17449
- LEFT JOIN _rt_open o ON o.order_item_id = r.order_item_id
17450
- WHERE r.qty > 0
17451
- GROUP BY r.order_item_id, r.trade_item_key, r.source, r.group_id;
17452
-
17453
- PERFORM set_config('stock.change_source', 'RESERVATION_RECOMPUTE', true);
17454
- UPDATE public.trade_item_stock s
17455
- SET reserved_stock = CEIL(COALESCE(d.qty, 0))::integer,
17456
- change_source = 'RESERVATION_RECOMPUTE',
17457
- updated_at = now()
17458
- FROM _rt_keys k
17459
- LEFT JOIN _rt_demand d ON d.trade_item_key = k.canon_key
17460
- WHERE s.trade_item_key = k.canon_key
17461
- AND s.reserved_stock IS DISTINCT FROM CEIL(COALESCE(d.qty, 0))::integer;
17462
- GET DIAGNOSTICS v_changed = ROW_COUNT;
17463
-
17464
- v_ms := round(extract(epoch FROM clock_timestamp() - v_t0) * 1000, 1);
17465
-
17466
- IF jsonb_array_length(v_skipped) > 0 THEN
17467
- PERFORM public.logistics_trace(jsonb_build_object(
17468
- 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
17469
- 'actor', 'logistics_recompute_reserved_targeted', 'errorClass', 'ontbrekende_gegevens',
17470
- 'reason', 'Geen voorraadrij en geen actieve Floriday-koppeling om die aan te maken; reservering voor deze artikelen niet berekend.',
17471
- 'values', jsonb_build_object('product_id', p_product_id,
17472
- 'sleutels', (SELECT jsonb_agg(DISTINCT x) FROM jsonb_array_elements_text(v_skipped) x),
17473
- 'gemiste_vraag_stuks', v_skipped_qty)));
17474
- END IF;
17475
-
17476
- IF jsonb_array_length(v_struct) > 0 THEN
17477
- PERFORM public.logistics_trace(jsonb_build_object(
17478
- 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
17479
- 'actor', 'logistics_recompute_reserved_targeted', 'errorClass', 'structuur',
17480
- 'reason', 'Samenstelling met een lus of te diepe nesting; voor deze bestelregels is niets gereserveerd.',
17481
- 'values', jsonb_build_object('regels', v_struct, 'gemiste_vraag_stuks', v_struct_qty)));
17482
- END IF;
17483
-
17484
- IF v_ms > v_trace_ms THEN
17485
- PERFORM public.logistics_trace(jsonb_build_object(
17486
- 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
17487
- 'actor', 'logistics_recompute_reserved_targeted', 'errorClass', 'traag',
17488
- 'reason', 'Gerichte herberekening duurde langer dan verwacht.',
17489
- 'values', jsonb_build_object('product_id', p_product_id, 'regels', v_nlines,
17490
- 'sleutels', (SELECT count(*) FROM _rt_keys), 'duur_ms', v_ms)));
17491
- END IF;
17492
-
17493
- IF v_freeze THEN
17494
- SELECT array_agg(order_item_id) INTO v_frozen_ids FROM _rt_frozen;
17495
- SELECT array_agg(order_item_id) INTO v_line_ids FROM _rt_lines;
17496
- PERFORM public.logistics_report_frozen_shortage(v_frozen_ids);
17497
- PERFORM public.logistics_allocation_check_changes(v_line_ids);
17498
- END IF;
17499
-
17500
- PERFORM set_config('app.recomputing_reserved', 'off', true);
17501
- RETURN jsonb_build_object('status', 'gericht', 'regels', v_nlines,
17502
- 'sleutels', (SELECT count(*) FROM _rt_keys), 'gewijzigd', v_changed, 'duur_ms', v_ms);
17503
- END
17504
- $function$
17505
- $fsdef$;
17506
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
17507
- RAISE NOTICE 'logistiek-engine: functie public.logistics_recompute_reserved_targeted overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
17508
- END $fsdo$;
17509
-
17510
- DO $fsdo$ BEGIN
17511
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_reconcile_missing_plan_queue()
17512
- RETURNS integer
17513
- LANGUAGE plpgsql
17514
- SECURITY DEFINER
17515
- SET search_path TO 'public'
17516
- AS $function$
17517
- DECLARE r record; n integer := 0;
17518
- BEGIN
17519
- FOR r IN
17520
- SELECT o.id FROM public.orders o
17521
- WHERE o.deleted_at IS NULL
17522
- AND COALESCE(o.is_reservation,false) = false
17523
- AND o.plan_locked_at IS NULL
17524
- AND o.order_number IS NOT NULL
17525
- AND (o.created_at >= now() - interval '14 days' OR o.updated_at >= now() - interval '14 days')
17526
- AND NOT EXISTS (SELECT 1 FROM public.logistics_tasks t WHERE t.order_ref = o.order_number)
17527
- AND NOT EXISTS (SELECT 1 FROM public.logistics_plan_queue q
17528
- WHERE q.order_id = o.id AND q.status IN ('pending','running'))
17529
- -- Geblokkeerd en sindsdien niets veranderd aan order, regels of product: niet opnieuw proberen.
17530
- AND NOT EXISTS (
17531
- SELECT 1 FROM public.logistics_plan_queue q
17532
- WHERE q.order_id = o.id AND q.status = 'geblokkeerd'
17533
- AND q.updated_at >= COALESCE(o.updated_at, o.created_at)
17534
- AND NOT EXISTS (SELECT 1 FROM logistics_active.order_items oi WHERE oi.order_id = o.id AND oi.created_at > q.updated_at)
17535
- AND NOT EXISTS (SELECT 1 FROM logistics_active.order_items oi JOIN public.products p ON p.id = oi.product_id
17536
- WHERE oi.order_id = o.id AND p.updated_at > q.updated_at))
17537
- ORDER BY o.created_at ASC
17538
- LIMIT 500
17539
- LOOP
17540
- PERFORM public.logistics_plan_enqueue(r.id, 'reconcile');
17541
- n := n + 1;
17542
- END LOOP;
17543
- RETURN n;
17544
- END;
17545
- $function$
17546
- $fsdef$;
17547
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
17548
- RAISE NOTICE 'logistiek-engine: functie public.logistics_reconcile_missing_plan_queue overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
17549
- END $fsdo$;
17550
-
17551
- DO $fsdo$ BEGIN
17552
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_refresh_pick_queue_status(p_ref text)
17553
- RETURNS integer
17554
- LANGUAGE plpgsql
17555
- SECURITY DEFINER
17556
- SET search_path TO 'public'
17557
- AS $function$
17558
- declare v_status jsonb; v_ref text; v_updated integer := 0;
17559
- begin
17560
- v_ref := btrim(coalesce(p_ref, ''));
17561
- if v_ref = '' then return 0; end if;
17562
- v_status := public.logistics_cart_plan_status(v_ref);
17563
- v_ref := coalesce(v_status->>'orderRef', v_ref);
17564
- update public.pick_queue q
17565
- set payload = coalesce(q.payload, '{}'::jsonb) || jsonb_build_object(
17566
- 'cartPlanReady', coalesce((v_status->>'ready')::boolean, false),
17567
- 'cartPlanReleased', coalesce((v_status->>'released')::boolean, false),
17568
- 'cartPlanReason', v_status->>'reason',
17569
- 'cartPlanState', v_status->>'state',
17570
- 'pickMode', v_status->>'pickMode',
17571
- 'totalCarts', coalesce((v_status->>'totalCarts')::integer, 0),
17572
- 'fullCarts', coalesce((v_status->>'fullCarts')::integer, 0),
17573
- 'partialCarts', coalesce((v_status->>'partialCarts')::integer, 0)),
17574
- updated_at = now()
17575
- where q.cancelled_at is null
17576
- and (q.order_number = v_ref or coalesce(q.payload->>'cartRowFor', '') = v_ref);
17577
- get diagnostics v_updated = row_count;
17578
- return v_updated;
17579
- end; $function$
17580
- $fsdef$;
17581
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
17582
- RAISE NOTICE 'logistiek-engine: functie public.logistics_refresh_pick_queue_status overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
17583
- END $fsdo$;
16675
+ declare v_status jsonb; v_ref text; v_updated integer := 0;
16676
+ begin
16677
+ v_ref := btrim(coalesce(p_ref, ''));
16678
+ if v_ref = '' then return 0; end if;
16679
+ v_status := public.logistics_cart_plan_status(v_ref);
16680
+ v_ref := coalesce(v_status->>'orderRef', v_ref);
16681
+ update public.pick_queue q
16682
+ set payload = coalesce(q.payload, '{}'::jsonb) || jsonb_build_object(
16683
+ 'cartPlanReady', coalesce((v_status->>'ready')::boolean, false),
16684
+ 'cartPlanReleased', coalesce((v_status->>'released')::boolean, false),
16685
+ 'cartPlanReason', v_status->>'reason',
16686
+ 'cartPlanState', v_status->>'state',
16687
+ 'pickMode', v_status->>'pickMode',
16688
+ 'totalCarts', coalesce((v_status->>'totalCarts')::integer, 0),
16689
+ 'fullCarts', coalesce((v_status->>'fullCarts')::integer, 0),
16690
+ 'partialCarts', coalesce((v_status->>'partialCarts')::integer, 0)),
16691
+ updated_at = now()
16692
+ where q.cancelled_at is null
16693
+ and (q.order_number = v_ref or coalesce(q.payload->>'cartRowFor', '') = v_ref);
16694
+ get diagnostics v_updated = row_count;
16695
+ return v_updated;
16696
+ end; $function$
16697
+ $fsdef$;
16698
+ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
16699
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_refresh_pick_queue_status overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
16700
+ END $fsdo$;
17584
16701
 
17585
16702
  DO $fsdo$ BEGIN
17586
16703
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_refresh_pickup_moments(p_weeks integer DEFAULT 4)
@@ -18947,265 +18064,78 @@ BEGIN
18947
18064
 
18948
18065
  BEGIN
18949
18066
  v_pid := CASE WHEN v_raw LIKE 'product:%' THEN substring(v_raw from 9)::uuid ELSE v_raw::uuid END;
18950
- EXCEPTION WHEN others THEN
18951
- v_pid := NULL;
18952
- END;
18953
-
18954
- IF v_pid IS NOT NULL THEN
18955
- SELECT COALESCE(NULLIF(btrim(COALESCE(p.floriday_trade_item_id, '')), ''),
18956
- NULLIF(btrim(COALESCE(p.trade_item_key, '')), ''), p.id::text)
18957
- INTO v_key FROM public.products p WHERE p.id = v_pid LIMIT 1;
18958
- ELSE
18959
- SELECT COALESCE(NULLIF(btrim(COALESCE(p.floriday_trade_item_id, '')), ''),
18960
- NULLIF(btrim(COALESCE(p.trade_item_key, '')), ''), p.id::text)
18961
- INTO v_key FROM public.products p
18962
- WHERE p.floriday_trade_item_id = v_raw OR p.trade_item_key = v_raw LIMIT 1;
18963
- END IF;
18964
- v_key := COALESCE(v_key, v_raw);
18965
-
18966
- IF EXISTS (SELECT 1 FROM public.trade_item_stock s WHERE s.trade_item_key = v_key) THEN
18967
- RETURN v_key;
18968
- END IF;
18969
- RETURN NULL;
18970
- END
18971
- $function$
18972
- $fsdef$;
18973
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
18974
- RAISE NOTICE 'logistiek-engine: functie public.logistics_reserved_key overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
18975
- END $fsdo$;
18976
-
18977
- DO $fsdo$ BEGIN
18978
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_reserved_order_excluded(p_order_id uuid)
18979
- RETURNS boolean
18980
- LANGUAGE plpgsql
18981
- STABLE SECURITY DEFINER
18982
- SET search_path TO 'public'
18983
- AS $function$
18984
- DECLARE r record; a jsonb;
18985
- BEGIN
18986
- SELECT o.deleted_at, o.cancelled_at, o.status INTO r FROM public.orders o WHERE o.id = p_order_id;
18987
- IF NOT FOUND THEN RETURN false; END IF;
18988
- IF r.deleted_at IS NOT NULL OR r.cancelled_at IS NOT NULL
18989
- OR lower(coalesce(r.status, '')) IN ('geannuleerd', 'cancelled') THEN
18990
- RETURN true;
18991
- END IF;
18992
- BEGIN
18993
- a := public.logistics_order_is_archived(p_order_id);
18994
- RETURN coalesce((a->>'gearchiveerd')::boolean, false);
18995
- EXCEPTION WHEN OTHERS THEN
18996
- RETURN false; -- leesfout: order blijft meetellen (fail closed)
18997
- END;
18998
- END $function$
18999
- $fsdef$;
19000
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
19001
- RAISE NOTICE 'logistiek-engine: functie public.logistics_reserved_order_excluded overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
19002
- END $fsdo$;
19003
-
19004
- DO $fsdo$ BEGIN
19005
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_reserved_stock_nightly()
19006
- RETURNS void
19007
- LANGUAGE plpgsql
19008
- SECURITY DEFINER
19009
- SET search_path TO 'public'
19010
- AS $function$
19011
- BEGIN
19012
- PERFORM set_config('statement_timeout', '600000', true);
19013
- PERFORM public.recompute_all_reserved_stock();
19014
- END
19015
- $function$
19016
- $fsdef$;
19017
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
19018
- RAISE NOTICE 'logistiek-engine: functie public.logistics_reserved_stock_nightly overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
19019
- END $fsdo$;
19020
-
19021
- DO $fsdo$ BEGIN
19022
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_resolve_pick_document(_queue_item_id uuid, _voorbeeld boolean, _session_id text, _is_herprint boolean)
19023
- RETURNS jsonb
19024
- LANGUAGE plpgsql
19025
- STABLE SECURITY DEFINER
19026
- SET search_path TO 'public'
19027
- AS $function$
19028
- DECLARE
19029
- item public.pick_queue;
19030
- dt public.pick_document_types;
19031
- doc_code text;
19032
- target_layout uuid;
19033
- target_plek uuid;
19034
- layout_meta jsonb;
19035
- v_types public.doc_layout_type[];
19036
- doc_type public.doc_layout_type := 'vrij';
19037
- layout_type public.doc_layout_type;
19038
- pdf_url text;
19039
- pdf_b64 text;
19040
- herkomst text;
19041
- v_variant text;
19042
- v_is_partial boolean := false;
19043
- v_cart_id uuid;
19044
- v_cart jsonb;
19045
- v_order_id uuid;
19046
- v_barcode text;
19047
- v_barcode_on boolean := public.logistics_pick_barcode_enabled();
19048
- BEGIN
19049
- SELECT * INTO item FROM public.pick_queue WHERE id = _queue_item_id;
19050
- IF NOT FOUND THEN
19051
- RAISE EXCEPTION 'Dit document bestaat niet meer.' USING ERRCODE = 'P0002';
19052
- END IF;
19053
-
19054
- SELECT * INTO dt FROM public.pick_document_types WHERE id = item.document_type_id;
19055
-
19056
- doc_code := lower(btrim(coalesce(
19057
- nullif(item.payload->>'documentType', ''),
19058
- nullif(dt.code, ''),
19059
- ''
19060
- )));
19061
-
19062
- v_types := public.logistics_document_type_map(doc_code);
19063
-
19064
- BEGIN
19065
- v_cart_id := nullif(coalesce(
19066
- item.payload->>'cartId', item.payload->>'cart_id',
19067
- item.payload->'data'->>'cartId', item.payload->'data'->>'cart_id'
19068
- ), '')::uuid;
19069
- EXCEPTION WHEN invalid_text_representation THEN
19070
- v_cart_id := NULL;
19071
- END;
19072
-
19073
- IF v_cart_id IS NOT NULL THEN
19074
- SELECT COALESCE(c.order_id, (
19075
- SELECT oi.order_id FROM logistics_active.order_cart_items ci
19076
- JOIN logistics_active.order_items oi ON oi.id = ci.order_item_id
19077
- WHERE ci.cart_id = c.id LIMIT 1))
19078
- INTO v_order_id
19079
- FROM logistics_active.order_carts c WHERE c.id = v_cart_id;
19080
-
19081
- IF v_order_id IS NOT NULL THEN
19082
- SELECT c INTO v_cart
19083
- FROM jsonb_array_elements(public.logistics_cart_slip_plan(v_order_id)->'carts') AS c
19084
- WHERE (c->>'cartId')::uuid = v_cart_id;
19085
- END IF;
19086
-
19087
- IF v_barcode_on THEN
19088
- v_barcode := COALESCE(
19089
- (SELECT b.barcode FROM public.logistics_cart_barcode b WHERE b.cart_id = v_cart_id),
19090
- public.logistics_cart_barcode_value(v_cart_id)
19091
- );
19092
- IF v_cart IS NOT NULL THEN
19093
- v_cart := v_cart || jsonb_build_object('barcode', v_barcode);
19094
- END IF;
19095
- END IF;
19096
- END IF;
19097
-
19098
- IF v_cart IS NOT NULL THEN
19099
- v_is_partial := NOT COALESCE((v_cart->>'isFullCart')::boolean, false);
19100
- ELSE
19101
- v_variant := nullif(coalesce(
19102
- item.payload->>'slipVariant',
19103
- item.payload->'data'->>'slipVariant'
19104
- ), '');
19105
- IF v_variant IS NOT NULL THEN
19106
- v_is_partial := (v_variant = 'partial_order');
19107
- ELSIF nullif(coalesce(item.payload->>'pickUnit', item.payload->'data'->>'pickUnit'), '') IS NOT NULL THEN
19108
- v_is_partial := (coalesce(item.payload->>'pickUnit', item.payload->'data'->>'pickUnit') = 'order');
19109
- ELSE
19110
- v_is_partial := (item.cart_count IS NOT NULL AND item.cart_count <= 1);
19111
- END IF;
19112
- END IF;
19113
-
19114
- BEGIN
19115
- target_layout := nullif(coalesce(
19116
- item.payload->'layout'->>'layout_id',
19117
- item.payload->>'layout_id'
19118
- ), '')::uuid;
19119
- EXCEPTION WHEN invalid_text_representation THEN
19120
- target_layout := NULL;
19121
- END;
19122
-
19123
- IF target_layout IS NULL AND doc_code <> '' THEN
19124
- layout_meta := public.logistics_document_layout_v2(doc_code, v_is_partial);
19125
- BEGIN
19126
- target_layout := nullif(layout_meta->>'layout_id', '')::uuid;
19127
- EXCEPTION WHEN invalid_text_representation THEN
19128
- target_layout := NULL;
19129
- END;
19130
- END IF;
19131
-
19132
- SELECT l.documenttype INTO layout_type
19133
- FROM public.document_layouts l
19134
- WHERE l.id = target_layout AND l.actief IS TRUE;
19135
-
19136
- IF layout_type IS NULL THEN target_layout := NULL; END IF;
18067
+ EXCEPTION WHEN others THEN
18068
+ v_pid := NULL;
18069
+ END;
19137
18070
 
19138
- doc_type := COALESCE(layout_type, v_types[1], 'vrij');
18071
+ IF v_pid IS NOT NULL THEN
18072
+ SELECT COALESCE(NULLIF(btrim(COALESCE(p.floriday_trade_item_id, '')), ''),
18073
+ NULLIF(btrim(COALESCE(p.trade_item_key, '')), ''), p.id::text)
18074
+ INTO v_key FROM public.products p WHERE p.id = v_pid LIMIT 1;
18075
+ ELSE
18076
+ SELECT COALESCE(NULLIF(btrim(COALESCE(p.floriday_trade_item_id, '')), ''),
18077
+ NULLIF(btrim(COALESCE(p.trade_item_key, '')), ''), p.id::text)
18078
+ INTO v_key FROM public.products p
18079
+ WHERE p.floriday_trade_item_id = v_raw OR p.trade_item_key = v_raw LIMIT 1;
18080
+ END IF;
18081
+ v_key := COALESCE(v_key, v_raw);
19139
18082
 
19140
- IF item.station_id IS NOT NULL THEN
19141
- SELECT k.print_plek_id INTO target_plek
19142
- FROM public.print_plek_koppelingen k
19143
- WHERE k.bron IN ('pickstation', 'pick_station')
19144
- AND k.bron_id = item.station_id::text
19145
- ORDER BY k.created_at
19146
- LIMIT 1;
18083
+ IF EXISTS (SELECT 1 FROM public.trade_item_stock s WHERE s.trade_item_key = v_key) THEN
18084
+ RETURN v_key;
19147
18085
  END IF;
18086
+ RETURN NULL;
18087
+ END
18088
+ $function$
18089
+ $fsdef$;
18090
+ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
18091
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_reserved_key overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
18092
+ END $fsdo$;
19148
18093
 
19149
- pdf_url := nullif(item.payload->>'pdf_url', '');
19150
- pdf_b64 := nullif(item.payload->>'pdf_base64', '');
19151
- herkomst := nullif(item.payload->>'herkomst', '');
18094
+ DO $fsdo$ BEGIN
18095
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_reserved_order_excluded(p_order_id uuid)
18096
+ RETURNS boolean
18097
+ LANGUAGE plpgsql
18098
+ STABLE SECURITY DEFINER
18099
+ SET search_path TO 'public'
18100
+ AS $function$
18101
+ DECLARE r record; a jsonb;
18102
+ BEGIN
18103
+ SELECT o.deleted_at, o.cancelled_at, o.status INTO r FROM public.orders o WHERE o.id = p_order_id;
18104
+ IF NOT FOUND THEN RETURN false; END IF;
18105
+ IF r.deleted_at IS NOT NULL OR r.cancelled_at IS NOT NULL
18106
+ OR lower(coalesce(r.status, '')) IN ('geannuleerd', 'cancelled') THEN
18107
+ RETURN true;
18108
+ END IF;
18109
+ BEGIN
18110
+ a := public.logistics_order_is_archived(p_order_id);
18111
+ RETURN coalesce((a->>'gearchiveerd')::boolean, false);
18112
+ EXCEPTION WHEN OTHERS THEN
18113
+ RETURN false; -- leesfout: order blijft meetellen (fail closed)
18114
+ END;
18115
+ END $function$
18116
+ $fsdef$;
18117
+ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
18118
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_reserved_order_excluded overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
18119
+ END $fsdo$;
19152
18120
 
19153
- RETURN jsonb_build_object(
19154
- 'queue_item_id', item.id,
19155
- 'station_id', item.station_id,
19156
- 'doc_code', doc_code,
19157
- 'layout_id', target_layout,
19158
- 'layout_variant', CASE WHEN v_is_partial THEN 'partial_order' ELSE 'full_cart' END,
19159
- 'cart_id', v_cart_id,
19160
- 'cart', v_cart,
19161
- 'barcode', v_barcode,
19162
- 'documenttype', doc_type,
19163
- 'documentnaam', COALESCE(item.document_number, 'Pickdocument'),
19164
- 'pdf_url', pdf_url,
19165
- 'pdf_base64', pdf_b64,
19166
- 'herkomst', herkomst,
19167
- 'render_html', nullif(item.payload->>'render_html', ''),
19168
- 'print_plek_id', target_plek,
19169
- 'foutmelding',
19170
- CASE WHEN target_layout IS NULL AND pdf_url IS NULL AND pdf_b64 IS NULL
19171
- THEN 'Geen actieve layout gevonden voor documentsoort "' || COALESCE(nullif(doc_code, ''), 'onbekend') || '". Maak een layout aan in de Layout maker of koppel er een in de Logistiek Engine.'
19172
- ELSE NULL END,
19173
- 'payload', jsonb_build_object(
19174
- 'source', 'pickstation',
19175
- 'voorbeeld', COALESCE(_voorbeeld, false),
19176
- 'queue_item_id', item.id,
19177
- 'station_id', item.station_id,
19178
- 'print_plek_id', target_plek,
19179
- 'session_id', _session_id,
19180
- 'is_herprint', COALESCE(_is_herprint, false),
19181
- 'document_number', item.document_number,
19182
- 'document_type_code', COALESCE(nullif(doc_code, ''), dt.code),
19183
- 'layout_variant', CASE WHEN v_is_partial THEN 'partial_order' ELSE 'full_cart' END,
19184
- 'layout_id', target_layout,
19185
- 'report_key', dt.report_key,
19186
- 'print_template', dt.print_template,
19187
- 'order_number', item.order_number,
19188
- 'customer_name', item.customer_name,
19189
- 'reference', item.reference,
19190
- 'barcode', CASE WHEN v_barcode_on THEN COALESCE(v_barcode, item.barcode) ELSE item.barcode END,
19191
- 'cart_id', v_cart_id,
19192
- 'cart_index', v_cart->>'cartIndex',
19193
- 'cart_total', v_cart->>'cartTotal',
19194
- 'cart_label', v_cart->>'cartLabel',
19195
- 'cart_items', COALESCE(v_cart->'items', '[]'::jsonb),
19196
- 'country_code', item.country_code,
19197
- 'data', COALESCE(item.payload, '{}'::jsonb)
19198
- || jsonb_build_object('print_plek_id', target_plek)
19199
- || COALESCE(jsonb_build_object('cart', v_cart), '{}'::jsonb)
19200
- )
19201
- );
19202
- END;
18121
+ DO $fsdo$ BEGIN
18122
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_reserved_stock_nightly()
18123
+ RETURNS void
18124
+ LANGUAGE plpgsql
18125
+ SECURITY DEFINER
18126
+ SET search_path TO 'public'
18127
+ AS $function$
18128
+ BEGIN
18129
+ PERFORM set_config('statement_timeout', '600000', true);
18130
+ PERFORM public.recompute_all_reserved_stock();
18131
+ END
19203
18132
  $function$
19204
18133
  $fsdef$;
19205
18134
  EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
19206
- RAISE NOTICE 'logistiek-engine: functie public.logistics_resolve_pick_document overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
18135
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_reserved_stock_nightly overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
19207
18136
  END $fsdo$;
19208
18137
 
18138
+
19209
18139
  DO $fsdo$ BEGIN
19210
18140
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_resolve_product_id(p_label text)
19211
18141
  RETURNS uuid
@@ -21350,193 +20280,27 @@ DO $fsdo$ BEGIN
21350
20280
  IMMUTABLE
21351
20281
  SET search_path TO 'public'
21352
20282
  AS $function$
21353
- DECLARE t text; m text[];
21354
- BEGIN
21355
- IF p IS NULL OR p = 'null'::jsonb THEN RETURN NULL; END IF;
21356
- IF jsonb_typeof(p) = 'object' THEN
21357
- IF (p->>'width') ~ '^\d+(\.\d+)?$' AND (p->>'height') ~ '^\d+(\.\d+)?$' THEN
21358
- RETURN ARRAY[(p->>'width')::numeric, (p->>'height')::numeric];
21359
- END IF;
21360
- t := p::text;
21361
- ELSE
21362
- t := p #>> '{}';
21363
- END IF;
21364
- m := regexp_match(coalesce(t,''), '(\d+(?:[.,]\d+)?)\s*[xX×*]\s*(\d+(?:[.,]\d+)?)');
21365
- IF m IS NULL THEN RETURN NULL; END IF;
21366
- RETURN ARRAY[replace(m[1],',','.')::numeric, replace(m[2],',','.')::numeric];
21367
- END; $function$
21368
- $fsdef$;
21369
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
21370
- RAISE NOTICE 'logistiek-engine: functie public.logistics_sticker_parse_size overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
21371
- END $fsdo$;
21372
-
21373
- DO $fsdo$ BEGIN
21374
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_sticker_sync(p_limit integer DEFAULT 200)
21375
- RETURNS jsonb
21376
- LANGUAGE plpgsql
21377
- SECURITY DEFINER
21378
- SET search_path TO 'public'
21379
- AS $function$
21380
- DECLARE
21381
- s jsonb; v_rel text; v_station text; v_tray text;
21382
- st record; o record; d jsonb; fi jsonb; v_n int; v_order uuid; v_onr text;
21383
- v_copies int; v_size numeric[]; v_type text; v_should text; v_keep uuid[]; v_del text; v_fo text; v_cart uuid;
21384
- v_crates_tot int; v_pieces_tot int; r record; v_rows int;
21385
- n_st int := 0; n_tray int := 0;
21386
- BEGIN
21387
- SELECT settings INTO s FROM public.logistics_rule_configs
21388
- ORDER BY is_active DESC, (status = 'actief') DESC, updated_at DESC LIMIT 1;
21389
- v_rel := public.logistics_rule_text(s, 'pickstations', 'sticker_release', 'with_pick_release');
21390
- v_station := CASE WHEN v_rel = 'with_packing_slip'
21391
- THEN public.logistics_rule_text(s, 'pickstations', 'packing_slip_station', '')
21392
- ELSE public.logistics_rule_text(s, 'pickstations', 'sticker_station', '') END;
21393
- v_tray := public.logistics_rule_text(s, 'pickstations', 'traysticker_release', 'uit');
21394
-
21395
- FOR st IN SELECT * FROM public.floriday_customer_stickers_cache ORDER BY updated_at DESC LIMIT greatest(p_limit,1) LOOP
21396
- n_st := n_st + 1;
21397
- d := coalesce(st.data, '{}'::jsonb);
21398
- fi := coalesce(d->'stickerFileInformation', '{}'::jsonb);
21399
- v_size := CASE WHEN st.sticker_width_mm IS NOT NULL AND st.sticker_height_mm IS NOT NULL
21400
- THEN ARRAY[st.sticker_width_mm::numeric, st.sticker_height_mm::numeric]
21401
- ELSE public.logistics_sticker_parse_size(d->'stickerSize') END;
21402
- v_type := upper(nullif(btrim(coalesce(st.sticker_type, d->>'stickerType', '')), ''));
21403
- v_should := lower(coalesce(st.should_be_printed::text, fi->>'shouldBePrinted', ''));
21404
-
21405
- -- Koppeling volgens Suppliers API 2026v1: alleen via salesOrderId.
21406
- SELECT count(*), (array_agg(id))[1], (array_agg(order_number))[1] INTO v_n, v_order, v_onr
21407
- FROM public.orders WHERE deleted_at IS NULL
21408
- AND nullif(btrim(coalesce(st.sales_order_id, d->>'salesOrderId', '')), '') IS NOT NULL
21409
- AND floriday_sales_order_id = coalesce(st.sales_order_id, d->>'salesOrderId');
21410
-
21411
- IF coalesce(st.is_deleted,false) OR lower(coalesce(d->>'isDeleted','')) = 'true' THEN
21412
- UPDATE public.logistics_sticker_prints SET status = 'niet_printen', reden = 'De koper heeft deze sticker verwijderd.'
21413
- WHERE soort = 'sticker' AND bron_id = st.floriday_id AND status NOT IN ('in_printwachtrij','afgemeld');
21414
- CONTINUE;
21415
- END IF;
21416
- IF lower(coalesce(d->>'isHandled','')) = 'true' THEN
21417
- UPDATE public.logistics_sticker_prints SET status = 'afgemeld', reden = 'Al afgemeld in Floriday.', handled_at = coalesce(handled_at, now())
21418
- WHERE soort = 'sticker' AND bron_id = st.floriday_id AND status <> 'afgemeld';
21419
- CONTINUE;
21420
- END IF;
21421
- IF v_n = 1 AND public.logistics_sales_order_external_fulfilled(v_order, coalesce(st.sales_order_id, d->>'salesOrderId')) THEN
21422
- UPDATE public.logistics_sticker_prints SET status = 'niet_printen', reden = 'Afgehandeld in Floriday; door ons niet geprint en niet afgemeld.'
21423
- WHERE soort = 'sticker' AND bron_id = st.floriday_id AND status NOT IN ('in_printwachtrij','afgemeld');
21424
- PERFORM public.logistics_sticker_upsert('sticker', st.floriday_id, v_order, v_onr, NULL, 0, v_size, NULL, NULL, 'niet_printen', 'Afgehandeld in Floriday; door ons niet geprint en niet afgemeld.');
21425
- CONTINUE;
21426
- END IF;
21427
- IF v_n <> 1 THEN
21428
- PERFORM public.logistics_sticker_upsert('sticker', st.floriday_id, NULL, NULL, NULL, 0, v_size, NULL, NULL, 'geblokkeerd',
21429
- format('Geen eenduidige order bij Floriday-verkooporder %s (%s gevonden).', coalesce(st.sales_order_id, d->>'salesOrderId', 'onbekend'), v_n));
21430
- UPDATE public.logistics_sticker_prints SET sticker_type = v_type WHERE soort='sticker' AND bron_id = st.floriday_id;
21431
- CONTINUE;
21432
- END IF;
21433
- IF v_should = 'false' THEN
21434
- -- Geen printopdracht; zichtbaar als 'niet_printen'. Wordt ook niet afgemeld in Floriday.
21435
- UPDATE public.logistics_sticker_prints SET status = 'niet_printen',
21436
- reden = 'Floriday: deze sticker hoeft niet geprint te worden (shouldBePrinted = false).'
21437
- WHERE soort = 'sticker' AND bron_id = st.floriday_id AND cart_id IS NOT NULL AND status NOT IN ('in_printwachtrij','afgemeld');
21438
- PERFORM public.logistics_sticker_upsert('sticker', st.floriday_id, v_order, v_onr, NULL, 0, v_size, NULL, NULL, 'niet_printen',
21439
- 'Floriday: deze sticker hoeft niet geprint te worden (shouldBePrinted = false).');
21440
- UPDATE public.logistics_sticker_prints SET sticker_type = v_type WHERE soort='sticker' AND bron_id = st.floriday_id;
21441
- CONTINUE;
21442
- END IF;
21443
- IF lower(coalesce(fi->>'deliverSeparately','')) = 'true' THEN
21444
- PERFORM public.logistics_sticker_upsert('sticker', st.floriday_id, v_order, v_onr, NULL, 0, v_size, NULL, NULL, 'geblokkeerd',
21445
- 'Sticker moet apart worden geleverd (deliverSeparately); kies handmatig hoe die mee gaat.');
21446
- UPDATE public.logistics_sticker_prints SET sticker_type = v_type WHERE soort='sticker' AND bron_id = st.floriday_id;
21447
- CONTINUE;
21448
- END IF;
21449
- IF nullif(btrim(coalesce(d->>'salesOrderCorrectionRequestId','')),'') IS NOT NULL THEN
21450
- PERFORM public.logistics_sticker_upsert('sticker', st.floriday_id, v_order, v_onr, NULL, 0, v_size, NULL, NULL, 'wacht_op_moment',
21451
- 'Wacht tot de correctie op de verkooporder is verwerkt.');
21452
- CONTINUE;
21453
- END IF;
21454
- v_copies := CASE WHEN (fi->>'numberOfCopies') ~ '^\d+$' THEN (fi->>'numberOfCopies')::int ELSE NULL END;
21455
- IF coalesce(v_copies,0) <= 0 THEN
21456
- PERFORM public.logistics_sticker_upsert('sticker', st.floriday_id, v_order, v_onr, NULL, 0, v_size, NULL, NULL, 'geblokkeerd',
21457
- 'Floriday gaf geen aantal kopieën (numberOfCopies) mee; niet geprint.');
21458
- UPDATE public.logistics_sticker_prints SET sticker_type = v_type WHERE soort='sticker' AND bron_id = st.floriday_id;
21459
- CONTINUE;
21460
- END IF;
21461
- IF v_type IS NULL OR v_type NOT IN ('POT','PRODUCT','SLEEVE','PACKAGE','LAYER','LOAD_CARRIER') THEN
21462
- PERFORM public.logistics_sticker_upsert('sticker', st.floriday_id, v_order, v_onr, NULL, v_copies, v_size, NULL, NULL, 'geblokkeerd',
21463
- format('Onbekende of niet-ondersteunde stickersoort (%s); kies handmatig hoe deze sticker mee gaat.', coalesce(v_type,'leeg')));
21464
- UPDATE public.logistics_sticker_prints SET sticker_type = v_type WHERE soort='sticker' AND bron_id = st.floriday_id;
21465
- CONTINUE;
21466
- END IF;
21467
-
21468
- v_keep := ARRAY[]::uuid[]; v_rows := 0; v_crates_tot := 0; v_pieces_tot := 0;
21469
- FOR r IN SELECT * FROM public.logistics_sticker_distribution(v_order, v_type, v_copies, nullif(btrim(coalesce(st.trade_item_id,'')),'')) LOOP
21470
- v_rows := v_rows + 1; v_keep := v_keep || r.cart_id;
21471
- v_crates_tot := v_crates_tot + r.crates; v_pieces_tot := v_pieces_tot + r.pieces;
21472
- PERFORM public.logistics_sticker_upsert('sticker', st.floriday_id, v_order, v_onr, r.cart_id, r.kopieen, v_size,
21473
- public.logistics_sticker_cart_plek(r.cart_id, v_station), NULL,
21474
- CASE WHEN public.logistics_cart_slip_queued(r.cart_id) THEN 'klaar' ELSE 'wacht_op_moment' END,
21475
- CASE WHEN public.logistics_cart_slip_queued(r.cart_id) THEN NULL ELSE 'Wacht op de pakbon van deze kar.' END);
21476
- END LOOP;
21477
- IF v_rows = 0 THEN
21478
- PERFORM public.logistics_sticker_upsert('sticker', st.floriday_id, v_order, v_onr, NULL, v_copies, v_size, NULL, NULL, 'geblokkeerd',
21479
- 'De verdeling over karren sluit niet: de order staat (nog) niet op een kar, of de karren hebben geen fusten/stuks van dit artikel.');
21480
- UPDATE public.logistics_sticker_prints SET sticker_type = v_type WHERE soort='sticker' AND bron_id = st.floriday_id;
21481
- CONTINUE;
21482
- END IF;
21483
- UPDATE public.logistics_sticker_prints SET sticker_type = v_type WHERE soort='sticker' AND bron_id = st.floriday_id;
21484
- IF (v_type IN ('PACKAGE','LAYER') AND v_crates_tot <> v_copies) OR (v_type IN ('POT','PRODUCT','SLEEVE') AND v_pieces_tot <> v_copies) THEN
21485
- BEGIN
21486
- INSERT INTO public.logistics_trace_events(phase, step, status, reason, order_ref)
21487
- SELECT 'stickers', 'Aantal klantstickers wijkt af', 'waarschuwing',
21488
- format('Floriday vraagt %s (%s); op de karren: %s fusten / %s stuks. Het aantal van Floriday wordt geprint.',
21489
- v_copies, v_type, v_crates_tot, v_pieces_tot), v_onr
21490
- WHERE NOT EXISTS (SELECT 1 FROM public.logistics_trace_events e WHERE e.order_ref = v_onr AND e.step = 'Aantal klantstickers wijkt af'
21491
- AND e.created_at > now() - interval '1 day');
21492
- EXCEPTION WHEN OTHERS THEN NULL; END;
20283
+ DECLARE t text; m text[];
20284
+ BEGIN
20285
+ IF p IS NULL OR p = 'null'::jsonb THEN RETURN NULL; END IF;
20286
+ IF jsonb_typeof(p) = 'object' THEN
20287
+ IF (p->>'width') ~ '^\d+(\.\d+)?$' AND (p->>'height') ~ '^\d+(\.\d+)?$' THEN
20288
+ RETURN ARRAY[(p->>'width')::numeric, (p->>'height')::numeric];
21493
20289
  END IF;
21494
- UPDATE public.logistics_sticker_prints SET status = 'niet_printen',
21495
- reden = CASE WHEN cart_id IS NULL THEN 'Vervangen door regels per kar.' ELSE 'Kar hoort niet meer bij deze sticker.' END
21496
- WHERE soort = 'sticker' AND bron_id = st.floriday_id
21497
- AND (cart_id IS NULL OR NOT (cart_id = ANY (v_keep)))
21498
- AND status NOT IN ('in_printwachtrij','afgemeld','niet_printen');
21499
- END LOOP;
21500
-
21501
- IF v_tray <> 'uit' THEN
21502
- FOR o IN
21503
- SELECT o.id, o.order_number, nullif(btrim(o.floriday_sales_order_id),'') AS sales
21504
- FROM public.orders o
21505
- WHERE o.deleted_at IS NULL AND nullif(btrim(o.floriday_sales_order_id),'') IS NOT NULL
21506
- AND o.delivery_date >= current_date - 2
21507
- AND EXISTS (SELECT 1 FROM logistics_active.order_carts oc WHERE oc.order_id = o.id)
21508
- ORDER BY o.delivery_date LIMIT greatest(p_limit,1)
21509
- LOOP
21510
- SELECT d2.floriday_id INTO v_del FROM public.floriday_delivery_orders_cache d2
21511
- WHERE d2.data @> jsonb_build_object('fulfillmentRequests', jsonb_build_array(jsonb_build_object('salesOrderId', o.sales)))
21512
- ORDER BY d2.fetched_at DESC LIMIT 1;
21513
- IF v_del IS NULL THEN CONTINUE; END IF;
21514
- IF EXISTS (SELECT 1 FROM public.logistics_sticker_prints WHERE soort = 'traysticker' AND bron_id = v_del) THEN
21515
- v_fo := public.logistics_afhandeling_fulfillment_order(o.id)->>'fulfillmentOrderId';
21516
- IF v_fo IS NOT NULL THEN
21517
- UPDATE public.logistics_sticker_prints SET fulfillment_order_id = v_fo
21518
- WHERE soort = 'traysticker' AND bron_id = v_del AND fulfillment_order_id IS DISTINCT FROM v_fo
21519
- AND status NOT IN ('in_printwachtrij','afgemeld');
21520
- END IF;
21521
- CONTINUE;
21522
- END IF;
21523
- SELECT oc.id INTO v_cart FROM logistics_active.order_carts oc WHERE oc.order_id = o.id ORDER BY oc.cart_number, oc.created_at LIMIT 1;
21524
- v_fo := public.logistics_afhandeling_fulfillment_order(o.id)->>'fulfillmentOrderId';
21525
- PERFORM public.logistics_sticker_upsert('traysticker', v_del, o.id, o.order_number, v_cart, 1, NULL,
21526
- public.logistics_sticker_cart_plek(v_cart, v_station), v_fo, 'wacht_op_moment',
21527
- 'Wacht op geaccepteerde belading in Floriday.');
21528
- n_tray := n_tray + 1;
21529
- END LOOP;
20290
+ t := p::text;
20291
+ ELSE
20292
+ t := p #>> '{}';
21530
20293
  END IF;
21531
-
21532
- RETURN jsonb_build_object('stickers', n_st, 'trayNieuw', n_tray, 'release', v_rel, 'trayRelease', v_tray,
21533
- 'klantstickerMoment', 'with_packing_slip');
20294
+ m := regexp_match(coalesce(t,''), '(\d+(?:[.,]\d+)?)\s*[xX×*]\s*(\d+(?:[.,]\d+)?)');
20295
+ IF m IS NULL THEN RETURN NULL; END IF;
20296
+ RETURN ARRAY[replace(m[1],',','.')::numeric, replace(m[2],',','.')::numeric];
21534
20297
  END; $function$
21535
20298
  $fsdef$;
21536
20299
  EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
21537
- RAISE NOTICE 'logistiek-engine: functie public.logistics_sticker_sync overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
20300
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_sticker_parse_size overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
21538
20301
  END $fsdo$;
21539
20302
 
20303
+
21540
20304
  DO $fsdo$ BEGIN
21541
20305
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_sticker_touch()
21542
20306
  RETURNS trigger
@@ -22396,64 +21160,6 @@ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR inva
22396
21160
  RAISE NOTICE 'logistiek-engine: functie public.logistics_tasks_prune overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
22397
21161
  END $fsdo$;
22398
21162
 
22399
- DO $fsdo$ BEGIN
22400
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_tick()
22401
- RETURNS jsonb
22402
- LANGUAGE plpgsql
22403
- SECURITY DEFINER
22404
- SET search_path TO 'public'
22405
- AS $function$
22406
- DECLARE v_result jsonb := '{}'::jsonb; v_start timestamptz := clock_timestamp(); v_t timestamptz; v_err boolean := false;
22407
- BEGIN
22408
- v_t := clock_timestamp();
22409
- BEGIN
22410
- PERFORM set_config('statement_timeout', '15000', true);
22411
- PERFORM public.logistics_rebuffer_pending();
22412
- v_result := v_result || jsonb_build_object('rebuffer', 'ok');
22413
- EXCEPTION WHEN OTHERS THEN
22414
- v_err := true;
22415
- IF SQLERRM ILIKE '%tijdzone%' THEN
22416
- v_result := v_result || jsonb_build_object('rebuffer', 'overgeslagen: tijdzone ontbreekt in Werktijden');
22417
- BEGIN
22418
- UPDATE public.logistics_rebuffer_state SET last_error = 'Herindelen overgeslagen: de tijdzone ontbreekt in Werktijden.', updated_at = now() WHERE id;
22419
- EXCEPTION WHEN OTHERS THEN NULL; END;
22420
- ELSE
22421
- v_result := v_result || jsonb_build_object('rebuffer', left(SQLERRM, 200));
22422
- END IF;
22423
- END;
22424
- v_result := v_result || jsonb_build_object('rebuffer_ms', (extract(epoch from clock_timestamp()-v_t)*1000)::int);
22425
-
22426
- v_t := clock_timestamp();
22427
- BEGIN
22428
- PERFORM set_config('statement_timeout', '20000', true);
22429
- PERFORM public.run_logistics_tasks_dispatch();
22430
- v_result := v_result || jsonb_build_object('taken', 'ok');
22431
- EXCEPTION WHEN OTHERS THEN
22432
- v_err := true; v_result := v_result || jsonb_build_object('taken', left(SQLERRM, 200));
22433
- END;
22434
- v_result := v_result || jsonb_build_object('taken_ms', (extract(epoch from clock_timestamp()-v_t)*1000)::int);
22435
-
22436
- v_t := clock_timestamp();
22437
- BEGIN
22438
- PERFORM set_config('statement_timeout', '20000', true);
22439
- v_result := v_result || jsonb_build_object('afgerond', public.logistics_order_finalize_run(25, 120));
22440
- EXCEPTION WHEN OTHERS THEN
22441
- v_err := true; v_result := v_result || jsonb_build_object('afgerond', left(SQLERRM, 200));
22442
- END;
22443
- v_result := v_result || jsonb_build_object('afgerond_ms', (extract(epoch from clock_timestamp()-v_t)*1000)::int);
22444
-
22445
- BEGIN
22446
- INSERT INTO public.logistics_tick_runs(started_at, finished_at, duration_ms, has_error, steps)
22447
- VALUES (v_start, clock_timestamp(), (extract(epoch from clock_timestamp()-v_start)*1000)::int, v_err, v_result);
22448
- EXCEPTION WHEN OTHERS THEN NULL;
22449
- END;
22450
- PERFORM set_config('statement_timeout', '30000', true);
22451
- RETURN v_result;
22452
- END $function$
22453
- $fsdef$;
22454
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
22455
- RAISE NOTICE 'logistiek-engine: functie public.logistics_tick overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
22456
- END $fsdo$;
22457
21163
 
22458
21164
  DO $fsdo$ BEGIN
22459
21165
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_tick_base_url_check()
@@ -24279,724 +22985,468 @@ DO $fsdo$ BEGIN
24279
22985
  LANGUAGE sql
24280
22986
  STABLE SECURITY DEFINER
24281
22987
  SET search_path TO 'public'
24282
- AS $function$ SELECT nullif(btrim(t.timezone), '') FROM public.logistics_time_settings t ORDER BY t.updated_at DESC NULLS LAST LIMIT 1 $function$
24283
- $fsdef$;
24284
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24285
- RAISE NOTICE 'logistiek-engine: functie public.logistics_tz overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24286
- END $fsdo$;
24287
-
24288
- DO $fsdo$ BEGIN
24289
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_unit_all_carts_scanned(p_order_id uuid)
24290
- RETURNS boolean
24291
- LANGUAGE sql
24292
- STABLE SECURITY DEFINER
24293
- SET search_path TO 'public'
24294
- AS $function$
24295
- SELECT NOT EXISTS (SELECT 1 FROM unnest(public.logistics_unit_order_ids(p_order_id)) m
24296
- WHERE NOT public.logistics_order_all_carts_scanned(m))
24297
- AND NOT EXISTS (SELECT 1 FROM public.orders o JOIN logistics_active.order_carts c ON c.buffer_id = o.buffer_id AND c.order_id IS NULL
24298
- WHERE o.id = p_order_id AND cardinality(public.logistics_unit_order_ids(p_order_id)) > 1
24299
- AND NOT public.logistics_cart_scanned(c.id))
24300
- $function$
24301
- $fsdef$;
24302
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24303
- RAISE NOTICE 'logistiek-engine: functie public.logistics_unit_all_carts_scanned overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24304
- END $fsdo$;
24305
-
24306
- DO $fsdo$ BEGIN
24307
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_unit_order_ids(p_order_id uuid)
24308
- RETURNS uuid[]
24309
- LANGUAGE sql
24310
- STABLE SECURITY DEFINER
24311
- SET search_path TO 'public'
24312
- AS $function$
24313
- WITH o AS (SELECT id, buffer_id FROM public.orders WHERE id = p_order_id),
24314
- m AS (SELECT o2.id, o2.order_number FROM o JOIN public.orders o2 ON o2.buffer_id = o.buffer_id
24315
- WHERE o.buffer_id IS NOT NULL AND o2.deleted_at IS NULL AND o2.cancelled_at IS NULL)
24316
- SELECT CASE WHEN (SELECT count(*) FROM m) > 1 THEN (SELECT array_agg(id ORDER BY order_number, id) FROM m)
24317
- ELSE ARRAY[p_order_id] END
24318
- $function$
24319
- $fsdef$;
24320
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24321
- RAISE NOTICE 'logistiek-engine: functie public.logistics_unit_order_ids overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24322
- END $fsdo$;
24323
-
24324
- DO $fsdo$ BEGIN
24325
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_upsert_task(p_user_id uuid, p_run_id uuid, p_order_ref text, p_buffer_ref text, p_strategy_id uuid, p_rule_config_id uuid, p_rule_version integer, p_task_type text, p_owner text, p_sequence integer, p_start timestamp with time zone, p_end timestamp with time zone, p_shadow boolean, p_payload jsonb)
24326
- RETURNS text
24327
- LANGUAGE plpgsql
24328
- SECURITY DEFINER
24329
- SET search_path TO 'public'
24330
- AS $function$
24331
- DECLARE existing public.logistics_tasks%ROWTYPE;
24332
- BEGIN
24333
- SELECT * INTO existing FROM public.logistics_tasks
24334
- WHERE user_id = p_user_id AND order_ref = p_order_ref AND task_type = p_task_type
24335
- AND status NOT IN ('cancelled','superseded')
24336
- ORDER BY created_at DESC LIMIT 1;
24337
-
24338
- IF FOUND THEN
24339
- IF existing.status IN ('claimed','in_progress','done','skipped') THEN
24340
- RETURN 'locked';
24341
- END IF;
24342
- UPDATE public.logistics_tasks SET
24343
- plan_run_id = p_run_id, buffer_ref = p_buffer_ref, strategy_id = p_strategy_id,
24344
- rule_config_id = p_rule_config_id, rule_version = p_rule_version,
24345
- owner_module = p_owner, sequence = p_sequence,
24346
- planned_start = p_start, planned_end = p_end, due_at = COALESCE(p_end, p_start),
24347
- shadow = p_shadow, payload = p_payload, status = 'planned', error = NULL, updated_at = now()
24348
- WHERE id = existing.id;
24349
- RETURN 'revised';
24350
- END IF;
24351
-
24352
- INSERT INTO public.logistics_tasks (
24353
- user_id, plan_run_id, order_ref, buffer_ref, strategy_id, rule_config_id, rule_version,
24354
- task_type, owner_module, sequence, planned_start, planned_end, due_at, shadow, payload
24355
- ) VALUES (
24356
- p_user_id, p_run_id, p_order_ref, p_buffer_ref, p_strategy_id, p_rule_config_id, p_rule_version,
24357
- p_task_type, p_owner, p_sequence, p_start, p_end, COALESCE(p_end, p_start), p_shadow, p_payload
24358
- );
24359
- RETURN 'created';
24360
- END; $function$
24361
- $fsdef$;
24362
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24363
- RAISE NOTICE 'logistiek-engine: functie public.logistics_upsert_task overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24364
- END $fsdo$;
24365
-
24366
- DO $fsdo$ BEGIN
24367
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_valid_timezone(_tz text)
24368
- RETURNS boolean
24369
- LANGUAGE sql
24370
- STABLE SECURITY DEFINER
24371
- SET search_path TO 'public'
24372
- AS $function$
24373
- SELECT EXISTS (SELECT 1 FROM pg_timezone_names n WHERE n.name = _tz)
24374
- $function$
24375
- $fsdef$;
24376
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24377
- RAISE NOTICE 'logistiek-engine: functie public.logistics_valid_timezone overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24378
- END $fsdo$;
24379
-
24380
- DO $fsdo$ BEGIN
24381
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_verkoop_order_link(p_order_id uuid)
24382
- RETURNS text
24383
- LANGUAGE plpgsql
24384
- STABLE SECURITY DEFINER
24385
- SET search_path TO 'public'
24386
- AS $function$
24387
- DECLARE v_base text; v_path text;
24388
- BEGIN
24389
- IF p_order_id IS NULL THEN RETURN NULL; END IF;
24390
- v_base := public.logistics_shell_base_url();
24391
- SELECT nullif(btrim(value), '') INTO v_path FROM public.module_runtime_config WHERE key = 'verkoop_order_path' LIMIT 1;
24392
- IF v_base IS NULL OR v_path IS NULL OR position('{orderId}' in v_path) = 0 OR left(v_path, 1) <> '/' THEN
24393
- RETURN NULL;
24394
- END IF;
24395
- RETURN v_base || replace(v_path, '{orderId}', p_order_id::text);
24396
- END $function$
24397
- $fsdef$;
24398
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24399
- RAISE NOTICE 'logistiek-engine: functie public.logistics_verkoop_order_link overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24400
- END $fsdo$;
24401
-
24402
- DO $fsdo$ BEGIN
24403
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_working_hours_status()
24404
- RETURNS jsonb
24405
- LANGUAGE sql
24406
- STABLE SECURITY DEFINER
24407
- SET search_path TO 'public'
24408
- AS $function$
24409
- SELECT jsonb_build_object(
24410
- 'timezone', public.logistics_opening_timezone(),
24411
- 'windows', COALESCE((SELECT jsonb_agg(jsonb_build_object('id', h.id, 'weekday', h.weekday, 'opens', h.opens, 'closes', h.closes, 'note', h.note) ORDER BY h.weekday, h.opens)
24412
- FROM public.logistics_opening_hours h WHERE h.is_active), '[]'::jsonb),
24413
- 'exceptions', COALESCE((SELECT jsonb_agg(jsonb_build_object('id', e.id, 'day', e.day, 'closed', e.is_closed, 'opens', e.opens, 'closes', e.closes, 'note', e.note) ORDER BY e.day)
24414
- FROM public.logistics_opening_exceptions e WHERE e.day >= current_date - 7), '[]'::jsonb),
24415
- 'usable', (public.logistics_opening_timezone() IS NOT NULL AND EXISTS (SELECT 1 FROM public.logistics_opening_hours WHERE is_active))
24416
- );
24417
- $function$
24418
- $fsdef$;
24419
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24420
- RAISE NOTICE 'logistiek-engine: functie public.logistics_working_hours_status overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24421
- END $fsdo$;
24422
-
24423
- DO $fsdo$ BEGIN
24424
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_write_buffer_layer_positions(p_buffer_id uuid)
24425
- RETURNS integer
24426
- LANGUAGE plpgsql
24427
- SECURITY DEFINER
24428
- SET search_path TO 'public'
24429
- AS $function$
24430
- declare c record; v_total int := 0;
24431
- begin
24432
- for c in select id from logistics_active.order_carts where buffer_id = p_buffer_id loop
24433
- v_total := v_total + public.logistics_write_cart_layer_positions(c.id);
24434
- end loop;
24435
- return v_total;
24436
- end;
24437
- $function$
22988
+ AS $function$ SELECT nullif(btrim(t.timezone), '') FROM public.logistics_time_settings t ORDER BY t.updated_at DESC NULLS LAST LIMIT 1 $function$
24438
22989
  $fsdef$;
24439
22990
  EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24440
- RAISE NOTICE 'logistiek-engine: functie public.logistics_write_buffer_layer_positions overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
22991
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_tz overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24441
22992
  END $fsdo$;
24442
22993
 
24443
22994
  DO $fsdo$ BEGIN
24444
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_write_cart_layer_positions(p_cart_id uuid)
24445
- RETURNS integer
24446
- LANGUAGE plpgsql
24447
- SECURITY DEFINER
22995
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_unit_all_carts_scanned(p_order_id uuid)
22996
+ RETURNS boolean
22997
+ LANGUAGE sql
22998
+ STABLE SECURITY DEFINER
24448
22999
  SET search_path TO 'public'
24449
23000
  AS $function$
24450
- declare
24451
- v_cart record;
24452
- v_lc record;
24453
- v_layer_w numeric;
24454
- v_layer_h numeric;
24455
- v_max_layers int;
24456
- it record;
24457
- v_w numeric; v_h numeric;
24458
- v_layer int := 1;
24459
- v_x numeric := 0; v_y numeric := 0; v_row_h numeric := 0;
24460
- v_n int; i int;
24461
- v_written int := 0;
24462
- v_seq int := 0;
24463
- v_full boolean := false;
24464
- v_fit_a int; v_fit_b int;
24465
- v_kegs jsonb := '[]'::jsonb;
24466
- v_slots jsonb := '[]'::jsonb;
24467
- v_used boolean[] := '{}';
24468
- v_pos jsonb;
24469
- v_keg jsonb;
24470
- v_ov record;
24471
- v_overrides jsonb := '{}'::jsonb;
24472
- v_k int; v_s int; v_slot jsonb;
24473
- v_placed jsonb := '[]'::jsonb;
24474
- begin
24475
- select * into v_cart from logistics_active.order_carts where id = p_cart_id;
24476
- if v_cart.id is null then return 0; end if;
24477
-
24478
- if coalesce((
24479
- select settings->'karrenindeling'->'fields'->>'cart_layout_method'
24480
- from public.logistics_rule_configs
24481
- where is_active
24482
- order by updated_at desc limit 1), 'belading') <> 'afmetingen' then
24483
- delete from public.order_cart_layer_positions where cart_id = p_cart_id;
24484
- return 0;
24485
- end if;
24486
-
24487
- select * into v_lc from public.load_carrier_types
24488
- where (v_cart.carrier_type_id is not null and id = v_cart.carrier_type_id)
24489
- or (v_cart.carrier_type_id is null and code = v_cart.carrier_type)
24490
- limit 1;
24491
- if v_lc.id is null then
24492
- select * into v_lc from public.load_carrier_types where is_active and is_default order by sort_order limit 1;
24493
- end if;
24494
-
24495
- v_layer_w := nullif(v_lc.layer_width_cm, 0) * 10;
24496
- v_layer_h := nullif(v_lc.layer_length_cm, 0) * 10;
24497
- v_max_layers := greatest(1, coalesce(v_cart.max_layers, v_lc.layers_per_carrier, 1));
24498
-
24499
- if v_layer_w is null or v_layer_h is null then
24500
- delete from public.order_cart_layer_positions where cart_id = p_cart_id;
24501
- return 0;
24502
- end if;
24503
-
24504
- -- handmatige plaatsingen bewaren vóór het vervangen
24505
- for v_ov in
24506
- select keg_id, layer, x, y, width, height, rotation, manual_overridden_at, manual_overridden_by
24507
- from public.order_cart_layer_positions
24508
- where cart_id = p_cart_id and manual_override
24509
- loop
24510
- v_overrides := v_overrides || jsonb_build_object(v_ov.keg_id, jsonb_build_object(
24511
- 'layer', v_ov.layer, 'x', v_ov.x, 'y', v_ov.y,
24512
- 'w', v_ov.width, 'h', v_ov.height,
24513
- 'at', v_ov.manual_overridden_at, 'by', v_ov.manual_overridden_by));
24514
- end loop;
24515
-
24516
- -- 1. fustlijst opbouwen (stabiele volgorde, stabiele keg-namen)
24517
- for it in
24518
- select ci.id, ci.order_item_id, ci.product_id,
24519
- coalesce(ci.product_name, p.product) as product_name,
24520
- greatest(0, coalesce(ci.crates, 0)) as crates,
24521
- d.fust_width_mm as fw, d.fust_length_mm as fl, d.fust_name as fust_name
24522
- from logistics_active.order_cart_items ci
24523
- left join public.logistics_product_dimensions d on d.product_id = ci.product_id
24524
- left join public.products p on p.id = ci.product_id
24525
- where ci.cart_id = p_cart_id
24526
- order by coalesce(ci.layer_index, 999), ci.id
24527
- loop
24528
- v_n := it.crates;
24529
- if v_n <= 0 then continue; end if;
24530
- if it.fw is null or it.fl is null or it.fw <= 0 or it.fl <= 0 then continue; end if;
24531
-
24532
- v_fit_a := floor(v_layer_w / it.fw) * floor(v_layer_h / it.fl);
24533
- v_fit_b := floor(v_layer_w / it.fl) * floor(v_layer_h / it.fw);
24534
- if v_fit_a >= v_fit_b then
24535
- v_w := it.fw; v_h := it.fl;
24536
- else
24537
- v_w := it.fl; v_h := it.fw;
24538
- end if;
24539
- if v_w > v_layer_w or v_h > v_layer_h then continue; end if;
24540
-
24541
- for i in 1..v_n loop
24542
- v_seq := v_seq + 1;
24543
- v_kegs := v_kegs || jsonb_build_array(jsonb_build_object(
24544
- 'keg', 'F' || v_seq,
24545
- 'product_id', it.product_id,
24546
- 'order_item_id', it.order_item_id,
24547
- 'label', coalesce(it.product_name, 'Fust') || coalesce(' - ' || it.fust_name, ''),
24548
- 'w', v_w, 'h', v_h));
24549
- end loop;
24550
- end loop;
24551
-
24552
- -- 2. beschikbare plekken berekenen in dezelfde volgorde
24553
- for v_k in 0 .. jsonb_array_length(v_kegs) - 1 loop
24554
- v_keg := v_kegs -> v_k;
24555
- v_w := (v_keg->>'w')::numeric; v_h := (v_keg->>'h')::numeric;
24556
- if v_x + v_w > v_layer_w + 0.001 then
24557
- v_x := 0; v_y := v_y + v_row_h; v_row_h := 0;
24558
- end if;
24559
- if v_y + v_h > v_layer_h + 0.001 then
24560
- v_layer := v_layer + 1; v_x := 0; v_y := 0; v_row_h := 0;
24561
- if v_layer > v_max_layers then
24562
- v_full := true;
24563
- exit;
24564
- end if;
24565
- end if;
24566
- v_slots := v_slots || jsonb_build_array(jsonb_build_object(
24567
- 'layer', v_layer, 'x', v_x, 'y', v_y, 'w', v_w, 'h', v_h));
24568
- v_used := v_used || false;
24569
- v_x := v_x + v_w;
24570
- v_row_h := greatest(v_row_h, v_h);
24571
- end loop;
24572
-
24573
- -- 3. handmatige plekken eerst vastzetten
24574
- for v_k in 0 .. greatest(jsonb_array_length(v_kegs) - 1, -1) loop
24575
- exit when jsonb_array_length(v_kegs) = 0;
24576
- v_keg := v_kegs -> v_k;
24577
- v_pos := v_overrides -> (v_keg->>'keg');
24578
- if v_pos is null then continue; end if;
24579
- if (v_pos->>'w')::numeric is distinct from (v_keg->>'w')::numeric
24580
- or (v_pos->>'h')::numeric is distinct from (v_keg->>'h')::numeric then
24581
- insert into public.logistics_layer_override_log(cart_id, keg_id, product_id, reason, details)
24582
- values (p_cart_id, v_keg->>'keg', nullif(v_keg->>'product_id','')::uuid,
24583
- 'afmeting_gewijzigd', jsonb_build_object('override', v_pos, 'nieuw', v_keg));
24584
- v_overrides := v_overrides - (v_keg->>'keg');
24585
- continue;
24586
- end if;
24587
- v_s := null;
24588
- for i in 0 .. jsonb_array_length(v_slots) - 1 loop
24589
- v_slot := v_slots -> i;
24590
- if not v_used[i+1]
24591
- and (v_slot->>'layer')::int = (v_pos->>'layer')::int
24592
- and (v_slot->>'x')::numeric = (v_pos->>'x')::numeric
24593
- and (v_slot->>'y')::numeric = (v_pos->>'y')::numeric then
24594
- v_s := i; exit;
24595
- end if;
24596
- end loop;
24597
- if v_s is null then
24598
- insert into public.logistics_layer_override_log(cart_id, keg_id, product_id, reason, details)
24599
- values (p_cart_id, v_keg->>'keg', nullif(v_keg->>'product_id','')::uuid,
24600
- 'plek_vervallen', jsonb_build_object('override', v_pos));
24601
- v_overrides := v_overrides - (v_keg->>'keg');
24602
- continue;
24603
- end if;
24604
- v_used[v_s+1] := true;
24605
- v_placed := v_placed || jsonb_build_array(v_keg || jsonb_build_object(
24606
- 'layer', (v_pos->>'layer')::int, 'x', (v_pos->>'x')::numeric, 'y', (v_pos->>'y')::numeric,
24607
- 'manual', true, 'at', v_pos->>'at', 'by', v_pos->>'by'));
24608
- end loop;
24609
-
24610
- -- overrides van fusten die niet meer in de kar zitten
24611
- for v_ov in select key from jsonb_each(v_overrides) as t(key, value) loop
24612
- if not exists (
24613
- select 1 from jsonb_array_elements(v_placed) e where e->>'keg' = v_ov.key
24614
- ) then
24615
- insert into public.logistics_layer_override_log(cart_id, keg_id, reason, details)
24616
- values (p_cart_id, v_ov.key, 'fust_niet_meer_in_kar', v_overrides -> v_ov.key);
24617
- end if;
24618
- end loop;
24619
-
24620
- -- 4. overige fusten om de vaste plekken heen indelen
24621
- v_s := 0;
24622
- for v_k in 0 .. greatest(jsonb_array_length(v_kegs) - 1, -1) loop
24623
- exit when jsonb_array_length(v_kegs) = 0;
24624
- v_keg := v_kegs -> v_k;
24625
- if exists (select 1 from jsonb_array_elements(v_placed) e where e->>'keg' = v_keg->>'keg') then
24626
- continue;
24627
- end if;
24628
- while v_s < jsonb_array_length(v_slots) and v_used[v_s+1] loop
24629
- v_s := v_s + 1;
24630
- end loop;
24631
- exit when v_s >= jsonb_array_length(v_slots);
24632
- v_slot := v_slots -> v_s;
24633
- v_used[v_s+1] := true;
24634
- v_placed := v_placed || jsonb_build_array(v_keg || jsonb_build_object(
24635
- 'layer', (v_slot->>'layer')::int, 'x', (v_slot->>'x')::numeric, 'y', (v_slot->>'y')::numeric,
24636
- 'manual', false));
24637
- end loop;
24638
-
24639
- -- 5. atomair vervangen inclusief teruggeschreven handmatige vlaggen
24640
- delete from public.order_cart_layer_positions where cart_id = p_cart_id;
24641
-
24642
- for v_k in 0 .. greatest(jsonb_array_length(v_placed) - 1, -1) loop
24643
- exit when jsonb_array_length(v_placed) = 0;
24644
- v_keg := v_placed -> v_k;
24645
- insert into public.order_cart_layer_positions
24646
- (cart_id, layer, keg_id, x, y, width, height, rotation,
24647
- layer_width, layer_height, product_id, order_item_id, label,
24648
- source, algorithm_version, manual_override, manual_overridden_at, manual_overridden_by)
24649
- values
24650
- (p_cart_id, (v_keg->>'layer')::int, v_keg->>'keg',
24651
- (v_keg->>'x')::numeric, (v_keg->>'y')::numeric,
24652
- (v_keg->>'w')::numeric, (v_keg->>'h')::numeric, 0,
24653
- v_layer_w, v_layer_h,
24654
- nullif(v_keg->>'product_id','')::uuid,
24655
- nullif(v_keg->>'order_item_id','')::uuid,
24656
- v_keg->>'label',
24657
- 'sql-grid-v5', 5,
24658
- coalesce((v_keg->>'manual')::boolean, false),
24659
- nullif(v_keg->>'at','')::timestamptz,
24660
- nullif(v_keg->>'by','')::uuid);
24661
- v_written := v_written + 1;
24662
- end loop;
24663
-
24664
- -- 6. karregels volgen de werkelijke posities
24665
- update public.order_cart_items ci
24666
- set layer_index = s.min_layer,
24667
- crates_in_layer = s.max_per_layer
24668
- from (
24669
- select order_item_id,
24670
- min(layer) as min_layer,
24671
- max(cnt) as max_per_layer
24672
- from (
24673
- select order_item_id, layer, count(*) as cnt
24674
- from public.order_cart_layer_positions
24675
- where cart_id = p_cart_id and order_item_id is not null
24676
- group by order_item_id, layer
24677
- ) g
24678
- group by order_item_id
24679
- ) s
24680
- where ci.cart_id = p_cart_id and ci.order_item_id = s.order_item_id;
24681
-
24682
- return v_written;
24683
- end;
23001
+ SELECT NOT EXISTS (SELECT 1 FROM unnest(public.logistics_unit_order_ids(p_order_id)) m
23002
+ WHERE NOT public.logistics_order_all_carts_scanned(m))
23003
+ AND NOT EXISTS (SELECT 1 FROM public.orders o JOIN logistics_active.order_carts c ON c.buffer_id = o.buffer_id AND c.order_id IS NULL
23004
+ WHERE o.id = p_order_id AND cardinality(public.logistics_unit_order_ids(p_order_id)) > 1
23005
+ AND NOT public.logistics_cart_scanned(c.id))
24684
23006
  $function$
24685
23007
  $fsdef$;
24686
23008
  EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24687
- RAISE NOTICE 'logistiek-engine: functie public.logistics_write_cart_layer_positions overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
23009
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_unit_all_carts_scanned overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24688
23010
  END $fsdo$;
24689
23011
 
24690
23012
  DO $fsdo$ BEGIN
24691
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.pick_cart_breakdown(_order_id uuid)
24692
- RETURNS jsonb
24693
- LANGUAGE plpgsql
23013
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_unit_order_ids(p_order_id uuid)
23014
+ RETURNS uuid[]
23015
+ LANGUAGE sql
24694
23016
  STABLE SECURITY DEFINER
24695
23017
  SET search_path TO 'public'
24696
23018
  AS $function$
24697
- DECLARE
24698
- v_orders uuid[]; v_ref text; v_status jsonb;
24699
- v_ready boolean := false; v_released boolean := false;
24700
- v_total integer := 0; v_full integer := 0; v_partial integer := 0;
24701
- v_shared integer := 0; v_crates integer := 0;
24702
- BEGIN
24703
- v_orders := public.pick_buffer_order_ids(_order_id);
24704
- IF v_orders IS NULL OR cardinality(v_orders) = 0 THEN v_orders := ARRAY[_order_id]; END IF;
24705
- SELECT o.order_number INTO v_ref FROM public.orders o WHERE o.id = _order_id;
24706
- v_status := public.logistics_cart_plan_status(v_ref);
24707
- v_ready := COALESCE((v_status->>'ready')::boolean, false);
24708
- v_released := COALESCE((v_status->>'released')::boolean, false);
24709
- IF v_ready THEN
24710
- v_total := COALESCE((v_status->>'cartTotal')::integer, (v_status->>'totalCarts')::integer, 0);
24711
- SELECT
24712
- count(*) FILTER (WHERE COALESCE((c->>'isFullCart')::boolean, false)),
24713
- count(*) FILTER (WHERE NOT COALESCE((c->>'isFullCart')::boolean, false)),
24714
- count(*) FILTER (WHERE COALESCE((c->>'isShared')::boolean, false))
24715
- INTO v_full, v_partial, v_shared
24716
- FROM jsonb_array_elements(COALESCE(v_status->'carts', '[]'::jsonb)) c;
24717
- IF v_total = 0 THEN v_total := v_full + v_partial; END IF;
24718
- IF v_total > 0 AND v_full + v_partial <> v_total THEN
24719
- v_partial := GREATEST(v_total - v_full, 0);
24720
- END IF;
24721
- END IF;
24722
- SELECT COALESCE(sum(ci.crates), 0)::integer INTO v_crates
24723
- FROM public.order_cart_items ci
24724
- JOIN logistics_active.order_items oi ON oi.id = ci.order_item_id
24725
- WHERE oi.order_id = ANY(v_orders);
24726
- RETURN jsonb_build_object(
24727
- 'totalCarts', COALESCE(v_total, 0),
24728
- 'fullCarts', COALESCE(v_full, 0),
24729
- 'partialCarts', COALESCE(v_partial, 0),
24730
- 'sharedCarts', COALESCE(v_shared, 0),
24731
- 'crates', COALESCE(v_crates, 0),
24732
- 'cartPlanReady', v_ready,
24733
- 'cartPlanReleased', v_released,
24734
- 'cartPlanReason', v_status->>'reason'
24735
- ) || public.logistics_pick_mode();
24736
- END; $function$
23019
+ WITH o AS (SELECT id, buffer_id FROM public.orders WHERE id = p_order_id),
23020
+ m AS (SELECT o2.id, o2.order_number FROM o JOIN public.orders o2 ON o2.buffer_id = o.buffer_id
23021
+ WHERE o.buffer_id IS NOT NULL AND o2.deleted_at IS NULL AND o2.cancelled_at IS NULL)
23022
+ SELECT CASE WHEN (SELECT count(*) FROM m) > 1 THEN (SELECT array_agg(id ORDER BY order_number, id) FROM m)
23023
+ ELSE ARRAY[p_order_id] END
23024
+ $function$
24737
23025
  $fsdef$;
24738
23026
  EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24739
- RAISE NOTICE 'logistiek-engine: functie public.pick_cart_breakdown overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
23027
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_unit_order_ids overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24740
23028
  END $fsdo$;
24741
23029
 
24742
23030
  DO $fsdo$ BEGIN
24743
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.pick_enqueue_print_job(_queue_item_id uuid, _printer_id uuid DEFAULT NULL::uuid, _session_id text DEFAULT NULL::text, _is_herprint boolean DEFAULT false)
24744
- RETURNS uuid
23031
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_upsert_task(p_user_id uuid, p_run_id uuid, p_order_ref text, p_buffer_ref text, p_strategy_id uuid, p_rule_config_id uuid, p_rule_version integer, p_task_type text, p_owner text, p_sequence integer, p_start timestamp with time zone, p_end timestamp with time zone, p_shadow boolean, p_payload jsonb)
23032
+ RETURNS text
24745
23033
  LANGUAGE plpgsql
24746
23034
  SECURITY DEFINER
24747
23035
  SET search_path TO 'public'
24748
23036
  AS $function$
24749
- declare
24750
- doc jsonb; target_printer uuid; job_id uuid; v_cart_id uuid;
24751
- v_doc_code text; v_plan jsonb; v_cart jsonb; v_last uuid; v_row_cart text;
24752
- begin
24753
- select lower(btrim(coalesce(nullif(q.payload->>'documentType',''), nullif(t.code,''), ''))),
24754
- nullif(q.payload->>'cartId','')
24755
- into v_doc_code, v_row_cart
24756
- from public.pick_queue q
24757
- left join public.pick_document_types t on t.id = q.document_type_id
24758
- where q.id = _queue_item_id;
24759
-
24760
- if v_doc_code = 'pakbon' then
24761
- -- Karregel: precies die ene kar. Orderregel: per kar één opdracht.
24762
- if v_row_cart is not null then
24763
- return public.pick_enqueue_print_job(_queue_item_id, _printer_id, _session_id,
24764
- coalesce(_is_herprint,false), v_row_cart);
24765
- end if;
24766
-
24767
- begin
24768
- v_plan := public.pick_queue_cart_plan(_queue_item_id);
24769
- exception when others then v_plan := null; end;
24770
-
24771
- if v_plan is null or jsonb_array_length(coalesce(v_plan->'carts','[]'::jsonb)) = 0 then
24772
- raise exception 'Het karplan voor dit document is nog niet klaar; er wordt geen pakbon zonder barcode geprint.' using errcode='check_violation';
24773
- end if;
24774
-
24775
- for v_cart in select c from jsonb_array_elements(v_plan->'carts') as c loop
24776
- v_last := public.pick_enqueue_print_job(_queue_item_id, _printer_id, _session_id,
24777
- coalesce(_is_herprint,false), v_cart->>'cartId');
24778
- end loop;
24779
- return v_last;
24780
- end if;
24781
-
24782
- doc := public.logistics_resolve_pick_document(_queue_item_id, false, _session_id, coalesce(_is_herprint,false));
24783
-
24784
- if nullif(doc->>'barcode','') is not null then
24785
- begin v_cart_id := nullif(doc->>'cart_id','')::uuid;
24786
- exception when invalid_text_representation then v_cart_id := null; end;
24787
- if v_cart_id is not null then perform public.logistics_ensure_cart_barcode(v_cart_id); end if;
24788
- end if;
24789
-
24790
- select p.id into target_printer from public.printers p
24791
- where p.id = _printer_id and p.actief and nullif(p.extern_printer_id,'') is not null;
23037
+ DECLARE existing public.logistics_tasks%ROWTYPE;
23038
+ BEGIN
23039
+ SELECT * INTO existing FROM public.logistics_tasks
23040
+ WHERE user_id = p_user_id AND order_ref = p_order_ref AND task_type = p_task_type
23041
+ AND status NOT IN ('cancelled','superseded')
23042
+ ORDER BY created_at DESC LIMIT 1;
24792
23043
 
24793
- insert into public.print_jobs(
24794
- printer_id, layout_id, documenttype, documentnaam, status, foutmelding,
24795
- payload, gebruiker_naam, pdf_url, pdf_base64, herkomst, is_herprint)
24796
- values (target_printer, nullif(doc->>'layout_id','')::uuid,
24797
- (doc->>'documenttype')::public.doc_layout_type, doc->>'documentnaam', 'wachtend',
24798
- nullif(doc->>'foutmelding',''), doc->'payload', 'Pickstation',
24799
- nullif(doc->>'pdf_url',''), nullif(doc->>'pdf_base64',''), nullif(doc->>'herkomst',''),
24800
- coalesce(_is_herprint,false))
24801
- returning id into job_id;
23044
+ IF FOUND THEN
23045
+ IF existing.status IN ('claimed','in_progress','done','skipped') THEN
23046
+ RETURN 'locked';
23047
+ END IF;
23048
+ UPDATE public.logistics_tasks SET
23049
+ plan_run_id = p_run_id, buffer_ref = p_buffer_ref, strategy_id = p_strategy_id,
23050
+ rule_config_id = p_rule_config_id, rule_version = p_rule_version,
23051
+ owner_module = p_owner, sequence = p_sequence,
23052
+ planned_start = p_start, planned_end = p_end, due_at = COALESCE(p_end, p_start),
23053
+ shadow = p_shadow, payload = p_payload, status = 'planned', error = NULL, updated_at = now()
23054
+ WHERE id = existing.id;
23055
+ RETURN 'revised';
23056
+ END IF;
24802
23057
 
24803
- perform public.logistics_enqueue_sticker_with_slip(_queue_item_id, _printer_id, _session_id,
24804
- coalesce(_is_herprint,false), nullif(doc->>'cart_id',''), job_id);
24805
- update public.pick_queue set print_job_id = job_id where id = _queue_item_id;
23058
+ INSERT INTO public.logistics_tasks (
23059
+ user_id, plan_run_id, order_ref, buffer_ref, strategy_id, rule_config_id, rule_version,
23060
+ task_type, owner_module, sequence, planned_start, planned_end, due_at, shadow, payload
23061
+ ) VALUES (
23062
+ p_user_id, p_run_id, p_order_ref, p_buffer_ref, p_strategy_id, p_rule_config_id, p_rule_version,
23063
+ p_task_type, p_owner, p_sequence, p_start, p_end, COALESCE(p_end, p_start), p_shadow, p_payload
23064
+ );
23065
+ RETURN 'created';
23066
+ END; $function$
23067
+ $fsdef$;
23068
+ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
23069
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_upsert_task overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
23070
+ END $fsdo$;
24806
23071
 
24807
- return job_id;
24808
- end; $function$
23072
+ DO $fsdo$ BEGIN
23073
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_valid_timezone(_tz text)
23074
+ RETURNS boolean
23075
+ LANGUAGE sql
23076
+ STABLE SECURITY DEFINER
23077
+ SET search_path TO 'public'
23078
+ AS $function$
23079
+ SELECT EXISTS (SELECT 1 FROM pg_timezone_names n WHERE n.name = _tz)
23080
+ $function$
24809
23081
  $fsdef$;
24810
23082
  EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24811
- RAISE NOTICE 'logistiek-engine: functie public.pick_enqueue_print_job overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
23083
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_valid_timezone overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24812
23084
  END $fsdo$;
24813
23085
 
24814
23086
  DO $fsdo$ BEGIN
24815
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.pick_enqueue_print_job(_queue_item_id uuid, _printer_id uuid, _session_id text, _is_herprint boolean, _cart_id text)
24816
- RETURNS uuid
23087
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_verkoop_order_link(p_order_id uuid)
23088
+ RETURNS text
24817
23089
  LANGUAGE plpgsql
24818
- SECURITY DEFINER
23090
+ STABLE SECURITY DEFINER
24819
23091
  SET search_path TO 'public'
24820
23092
  AS $function$
24821
- DECLARE
24822
- item public.pick_queue;
24823
- dt public.pick_document_types;
24824
- doc_code text;
24825
- target_printer uuid;
24826
- target_layout uuid;
24827
- target_plek uuid;
24828
- layout_meta jsonb;
24829
- v_types public.doc_layout_type[];
24830
- doc_type public.doc_layout_type := 'vrij';
24831
- layout_type public.doc_layout_type;
24832
- pdf_url text;
24833
- pdf_b64 text;
24834
- herkomst text;
24835
- job_id uuid;
24836
- cart_plan jsonb := NULL;
24837
- selected_cart jsonb := NULL;
24838
- v_cart_uuid uuid;
24839
- v_barcode text;
24840
- v_barcode_on boolean := public.logistics_pick_barcode_enabled();
24841
- v_is_partial boolean := false;
24842
- v_sleutel text;
23093
+ DECLARE v_base text; v_path text;
24843
23094
  BEGIN
24844
- SELECT * INTO item FROM public.pick_queue WHERE id = _queue_item_id;
24845
- IF NOT FOUND THEN RAISE EXCEPTION 'Dit document bestaat niet meer.'; END IF;
23095
+ IF p_order_id IS NULL THEN RETURN NULL; END IF;
23096
+ v_base := public.logistics_shell_base_url();
23097
+ SELECT nullif(btrim(value), '') INTO v_path FROM public.module_runtime_config WHERE key = 'verkoop_order_path' LIMIT 1;
23098
+ IF v_base IS NULL OR v_path IS NULL OR position('{orderId}' in v_path) = 0 OR left(v_path, 1) <> '/' THEN
23099
+ RETURN NULL;
23100
+ END IF;
23101
+ RETURN v_base || replace(v_path, '{orderId}', p_order_id::text);
23102
+ END $function$
23103
+ $fsdef$;
23104
+ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
23105
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_verkoop_order_link overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
23106
+ END $fsdo$;
24846
23107
 
24847
- SELECT * INTO dt FROM public.pick_document_types WHERE id = item.document_type_id;
23108
+ DO $fsdo$ BEGIN
23109
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_working_hours_status()
23110
+ RETURNS jsonb
23111
+ LANGUAGE sql
23112
+ STABLE SECURITY DEFINER
23113
+ SET search_path TO 'public'
23114
+ AS $function$
23115
+ SELECT jsonb_build_object(
23116
+ 'timezone', public.logistics_opening_timezone(),
23117
+ 'windows', COALESCE((SELECT jsonb_agg(jsonb_build_object('id', h.id, 'weekday', h.weekday, 'opens', h.opens, 'closes', h.closes, 'note', h.note) ORDER BY h.weekday, h.opens)
23118
+ FROM public.logistics_opening_hours h WHERE h.is_active), '[]'::jsonb),
23119
+ 'exceptions', COALESCE((SELECT jsonb_agg(jsonb_build_object('id', e.id, 'day', e.day, 'closed', e.is_closed, 'opens', e.opens, 'closes', e.closes, 'note', e.note) ORDER BY e.day)
23120
+ FROM public.logistics_opening_exceptions e WHERE e.day >= current_date - 7), '[]'::jsonb),
23121
+ 'usable', (public.logistics_opening_timezone() IS NOT NULL AND EXISTS (SELECT 1 FROM public.logistics_opening_hours WHERE is_active))
23122
+ );
23123
+ $function$
23124
+ $fsdef$;
23125
+ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
23126
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_working_hours_status overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
23127
+ END $fsdo$;
24848
23128
 
24849
- doc_code := lower(btrim(coalesce(
24850
- nullif(item.payload->>'documentType', ''),
24851
- nullif(dt.code, ''),
24852
- ''
24853
- )));
23129
+ DO $fsdo$ BEGIN
23130
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_write_buffer_layer_positions(p_buffer_id uuid)
23131
+ RETURNS integer
23132
+ LANGUAGE plpgsql
23133
+ SECURITY DEFINER
23134
+ SET search_path TO 'public'
23135
+ AS $function$
23136
+ declare c record; v_total int := 0;
23137
+ begin
23138
+ for c in select id from logistics_active.order_carts where buffer_id = p_buffer_id loop
23139
+ v_total := v_total + public.logistics_write_cart_layer_positions(c.id);
23140
+ end loop;
23141
+ return v_total;
23142
+ end;
23143
+ $function$
23144
+ $fsdef$;
23145
+ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
23146
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_write_buffer_layer_positions overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
23147
+ END $fsdo$;
24854
23148
 
24855
- v_types := public.logistics_document_type_map(doc_code);
23149
+ DO $fsdo$ BEGIN
23150
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_write_cart_layer_positions(p_cart_id uuid)
23151
+ RETURNS integer
23152
+ LANGUAGE plpgsql
23153
+ SECURITY DEFINER
23154
+ SET search_path TO 'public'
23155
+ AS $function$
23156
+ declare
23157
+ v_cart record;
23158
+ v_lc record;
23159
+ v_layer_w numeric;
23160
+ v_layer_h numeric;
23161
+ v_max_layers int;
23162
+ it record;
23163
+ v_w numeric; v_h numeric;
23164
+ v_layer int := 1;
23165
+ v_x numeric := 0; v_y numeric := 0; v_row_h numeric := 0;
23166
+ v_n int; i int;
23167
+ v_written int := 0;
23168
+ v_seq int := 0;
23169
+ v_full boolean := false;
23170
+ v_fit_a int; v_fit_b int;
23171
+ v_kegs jsonb := '[]'::jsonb;
23172
+ v_slots jsonb := '[]'::jsonb;
23173
+ v_used boolean[] := '{}';
23174
+ v_pos jsonb;
23175
+ v_keg jsonb;
23176
+ v_ov record;
23177
+ v_overrides jsonb := '{}'::jsonb;
23178
+ v_k int; v_s int; v_slot jsonb;
23179
+ v_placed jsonb := '[]'::jsonb;
23180
+ begin
23181
+ select * into v_cart from logistics_active.order_carts where id = p_cart_id;
23182
+ if v_cart.id is null then return 0; end if;
24856
23183
 
24857
- IF _cart_id IS NOT NULL THEN
24858
- cart_plan := public.pick_queue_cart_plan(_queue_item_id);
24859
- SELECT c INTO selected_cart
24860
- FROM jsonb_array_elements(COALESCE(cart_plan->'carts', '[]'::jsonb)) AS c
24861
- WHERE c->>'cartId' = _cart_id
24862
- LIMIT 1;
23184
+ if coalesce((
23185
+ select settings->'karrenindeling'->'fields'->>'cart_layout_method'
23186
+ from public.logistics_rule_configs
23187
+ where is_active
23188
+ order by updated_at desc limit 1), 'belading') <> 'afmetingen' then
23189
+ delete from public.order_cart_layer_positions where cart_id = p_cart_id;
23190
+ return 0;
23191
+ end if;
24863
23192
 
24864
- IF selected_cart IS NULL THEN
24865
- RAISE EXCEPTION 'De gekozen kar bestaat niet meer in het actuele karplan.';
24866
- END IF;
23193
+ select * into v_lc from public.load_carrier_types
23194
+ where (v_cart.carrier_type_id is not null and id = v_cart.carrier_type_id)
23195
+ or (v_cart.carrier_type_id is null and code = v_cart.carrier_type)
23196
+ limit 1;
23197
+ if v_lc.id is null then
23198
+ select * into v_lc from public.load_carrier_types where is_active and is_default order by sort_order limit 1;
23199
+ end if;
24867
23200
 
24868
- v_is_partial := NOT COALESCE((selected_cart->>'isFullCart')::boolean, false);
23201
+ v_layer_w := nullif(v_lc.layer_width_cm, 0) * 10;
23202
+ v_layer_h := nullif(v_lc.layer_length_cm, 0) * 10;
23203
+ v_max_layers := greatest(1, coalesce(v_cart.max_layers, v_lc.layers_per_carrier, 1));
24869
23204
 
24870
- IF v_barcode_on THEN
24871
- BEGIN
24872
- v_cart_uuid := nullif(_cart_id, '')::uuid;
24873
- EXCEPTION WHEN invalid_text_representation THEN
24874
- v_cart_uuid := NULL;
24875
- END;
24876
- IF v_cart_uuid IS NOT NULL THEN
24877
- v_barcode := public.logistics_ensure_cart_barcode(v_cart_uuid);
24878
- selected_cart := selected_cart || jsonb_build_object('barcode', v_barcode);
24879
- END IF;
24880
- END IF;
24881
- END IF;
23205
+ if v_layer_w is null or v_layer_h is null then
23206
+ delete from public.order_cart_layer_positions where cart_id = p_cart_id;
23207
+ return 0;
23208
+ end if;
24882
23209
 
24883
- BEGIN
24884
- target_layout := nullif(coalesce(
24885
- item.payload->'layout'->>'layout_id',
24886
- item.payload->>'layout_id'
24887
- ), '')::uuid;
24888
- EXCEPTION WHEN invalid_text_representation THEN
24889
- target_layout := NULL;
24890
- END;
23210
+ -- handmatige plaatsingen bewaren vóór het vervangen
23211
+ for v_ov in
23212
+ select keg_id, layer, x, y, width, height, rotation, manual_overridden_at, manual_overridden_by
23213
+ from public.order_cart_layer_positions
23214
+ where cart_id = p_cart_id and manual_override
23215
+ loop
23216
+ v_overrides := v_overrides || jsonb_build_object(v_ov.keg_id, jsonb_build_object(
23217
+ 'layer', v_ov.layer, 'x', v_ov.x, 'y', v_ov.y,
23218
+ 'w', v_ov.width, 'h', v_ov.height,
23219
+ 'at', v_ov.manual_overridden_at, 'by', v_ov.manual_overridden_by));
23220
+ end loop;
24891
23221
 
24892
- IF target_layout IS NULL AND doc_code <> '' THEN
24893
- layout_meta := public.logistics_document_layout_v2(doc_code, v_is_partial);
24894
- BEGIN
24895
- target_layout := nullif(layout_meta->>'layout_id', '')::uuid;
24896
- EXCEPTION WHEN invalid_text_representation THEN
24897
- target_layout := NULL;
24898
- END;
24899
- END IF;
23222
+ -- 1. fustlijst opbouwen (stabiele volgorde, stabiele keg-namen)
23223
+ for it in
23224
+ select ci.id, ci.order_item_id, ci.product_id,
23225
+ coalesce(ci.product_name, p.product) as product_name,
23226
+ greatest(0, coalesce(ci.crates, 0)) as crates,
23227
+ d.fust_width_mm as fw, d.fust_length_mm as fl, d.fust_name as fust_name
23228
+ from logistics_active.order_cart_items ci
23229
+ left join public.logistics_product_dimensions d on d.product_id = ci.product_id
23230
+ left join public.products p on p.id = ci.product_id
23231
+ where ci.cart_id = p_cart_id
23232
+ order by coalesce(ci.layer_index, 999), ci.id
23233
+ loop
23234
+ v_n := it.crates;
23235
+ if v_n <= 0 then continue; end if;
23236
+ if it.fw is null or it.fl is null or it.fw <= 0 or it.fl <= 0 then continue; end if;
24900
23237
 
24901
- SELECT l.documenttype INTO layout_type
24902
- FROM public.document_layouts l
24903
- WHERE l.id = target_layout
24904
- AND l.actief IS TRUE;
23238
+ v_fit_a := floor(v_layer_w / it.fw) * floor(v_layer_h / it.fl);
23239
+ v_fit_b := floor(v_layer_w / it.fl) * floor(v_layer_h / it.fw);
23240
+ if v_fit_a >= v_fit_b then
23241
+ v_w := it.fw; v_h := it.fl;
23242
+ else
23243
+ v_w := it.fl; v_h := it.fw;
23244
+ end if;
23245
+ if v_w > v_layer_w or v_h > v_layer_h then continue; end if;
24905
23246
 
24906
- IF layout_type IS NULL THEN
24907
- target_layout := NULL;
24908
- END IF;
23247
+ for i in 1..v_n loop
23248
+ v_seq := v_seq + 1;
23249
+ v_kegs := v_kegs || jsonb_build_array(jsonb_build_object(
23250
+ 'keg', 'F' || v_seq,
23251
+ 'product_id', it.product_id,
23252
+ 'order_item_id', it.order_item_id,
23253
+ 'label', coalesce(it.product_name, 'Fust') || coalesce(' - ' || it.fust_name, ''),
23254
+ 'w', v_w, 'h', v_h));
23255
+ end loop;
23256
+ end loop;
24909
23257
 
24910
- doc_type := COALESCE(layout_type, v_types[1], 'vrij');
23258
+ -- 2. beschikbare plekken berekenen in dezelfde volgorde
23259
+ for v_k in 0 .. jsonb_array_length(v_kegs) - 1 loop
23260
+ v_keg := v_kegs -> v_k;
23261
+ v_w := (v_keg->>'w')::numeric; v_h := (v_keg->>'h')::numeric;
23262
+ if v_x + v_w > v_layer_w + 0.001 then
23263
+ v_x := 0; v_y := v_y + v_row_h; v_row_h := 0;
23264
+ end if;
23265
+ if v_y + v_h > v_layer_h + 0.001 then
23266
+ v_layer := v_layer + 1; v_x := 0; v_y := 0; v_row_h := 0;
23267
+ if v_layer > v_max_layers then
23268
+ v_full := true;
23269
+ exit;
23270
+ end if;
23271
+ end if;
23272
+ v_slots := v_slots || jsonb_build_array(jsonb_build_object(
23273
+ 'layer', v_layer, 'x', v_x, 'y', v_y, 'w', v_w, 'h', v_h));
23274
+ v_used := v_used || false;
23275
+ v_x := v_x + v_w;
23276
+ v_row_h := greatest(v_row_h, v_h);
23277
+ end loop;
24911
23278
 
24912
- IF item.station_id IS NOT NULL THEN
24913
- SELECT k.print_plek_id INTO target_plek
24914
- FROM public.print_plek_koppelingen k
24915
- WHERE k.bron IN ('pickstation', 'pick_station')
24916
- AND k.bron_id = item.station_id::text
24917
- ORDER BY k.created_at
24918
- LIMIT 1;
24919
- END IF;
23279
+ -- 3. handmatige plekken eerst vastzetten
23280
+ for v_k in 0 .. greatest(jsonb_array_length(v_kegs) - 1, -1) loop
23281
+ exit when jsonb_array_length(v_kegs) = 0;
23282
+ v_keg := v_kegs -> v_k;
23283
+ v_pos := v_overrides -> (v_keg->>'keg');
23284
+ if v_pos is null then continue; end if;
23285
+ if (v_pos->>'w')::numeric is distinct from (v_keg->>'w')::numeric
23286
+ or (v_pos->>'h')::numeric is distinct from (v_keg->>'h')::numeric then
23287
+ insert into public.logistics_layer_override_log(cart_id, keg_id, product_id, reason, details)
23288
+ values (p_cart_id, v_keg->>'keg', nullif(v_keg->>'product_id','')::uuid,
23289
+ 'afmeting_gewijzigd', jsonb_build_object('override', v_pos, 'nieuw', v_keg));
23290
+ v_overrides := v_overrides - (v_keg->>'keg');
23291
+ continue;
23292
+ end if;
23293
+ v_s := null;
23294
+ for i in 0 .. jsonb_array_length(v_slots) - 1 loop
23295
+ v_slot := v_slots -> i;
23296
+ if not v_used[i+1]
23297
+ and (v_slot->>'layer')::int = (v_pos->>'layer')::int
23298
+ and (v_slot->>'x')::numeric = (v_pos->>'x')::numeric
23299
+ and (v_slot->>'y')::numeric = (v_pos->>'y')::numeric then
23300
+ v_s := i; exit;
23301
+ end if;
23302
+ end loop;
23303
+ if v_s is null then
23304
+ insert into public.logistics_layer_override_log(cart_id, keg_id, product_id, reason, details)
23305
+ values (p_cart_id, v_keg->>'keg', nullif(v_keg->>'product_id','')::uuid,
23306
+ 'plek_vervallen', jsonb_build_object('override', v_pos));
23307
+ v_overrides := v_overrides - (v_keg->>'keg');
23308
+ continue;
23309
+ end if;
23310
+ v_used[v_s+1] := true;
23311
+ v_placed := v_placed || jsonb_build_array(v_keg || jsonb_build_object(
23312
+ 'layer', (v_pos->>'layer')::int, 'x', (v_pos->>'x')::numeric, 'y', (v_pos->>'y')::numeric,
23313
+ 'manual', true, 'at', v_pos->>'at', 'by', v_pos->>'by'));
23314
+ end loop;
24920
23315
 
24921
- SELECT p.id INTO target_printer
24922
- FROM public.printers p
24923
- WHERE p.id = _printer_id
24924
- AND p.actief
24925
- AND nullif(p.extern_printer_id, '') IS NOT NULL;
23316
+ -- overrides van fusten die niet meer in de kar zitten
23317
+ for v_ov in select key from jsonb_each(v_overrides) as t(key, value) loop
23318
+ if not exists (
23319
+ select 1 from jsonb_array_elements(v_placed) e where e->>'keg' = v_ov.key
23320
+ ) then
23321
+ insert into public.logistics_layer_override_log(cart_id, keg_id, reason, details)
23322
+ values (p_cart_id, v_ov.key, 'fust_niet_meer_in_kar', v_overrides -> v_ov.key);
23323
+ end if;
23324
+ end loop;
24926
23325
 
24927
- pdf_url := nullif(item.payload->>'pdf_url', '');
24928
- pdf_b64 := nullif(item.payload->>'pdf_base64', '');
24929
- herkomst := nullif(item.payload->>'herkomst', '');
23326
+ -- 4. overige fusten om de vaste plekken heen indelen
23327
+ v_s := 0;
23328
+ for v_k in 0 .. greatest(jsonb_array_length(v_kegs) - 1, -1) loop
23329
+ exit when jsonb_array_length(v_kegs) = 0;
23330
+ v_keg := v_kegs -> v_k;
23331
+ if exists (select 1 from jsonb_array_elements(v_placed) e where e->>'keg' = v_keg->>'keg') then
23332
+ continue;
23333
+ end if;
23334
+ while v_s < jsonb_array_length(v_slots) and v_used[v_s+1] loop
23335
+ v_s := v_s + 1;
23336
+ end loop;
23337
+ exit when v_s >= jsonb_array_length(v_slots);
23338
+ v_slot := v_slots -> v_s;
23339
+ v_used[v_s+1] := true;
23340
+ v_placed := v_placed || jsonb_build_array(v_keg || jsonb_build_object(
23341
+ 'layer', (v_slot->>'layer')::int, 'x', (v_slot->>'x')::numeric, 'y', (v_slot->>'y')::numeric,
23342
+ 'manual', false));
23343
+ end loop;
24930
23344
 
24931
- -- Ontdubbeling per kar (M8): eigen sleutel, behalve bij een herprint (die krijgt van de trigger een unieke sleutel).
24932
- IF NOT COALESCE(_is_herprint, false) AND _cart_id IS NOT NULL THEN
24933
- v_sleutel := 'pakbon:' || item.id::text || ':' || _cart_id || ':'
24934
- || coalesce(target_layout::text, '-') || ':' || coalesce(target_printer::text, '-');
24935
- END IF;
23345
+ -- 5. atomair vervangen inclusief teruggeschreven handmatige vlaggen
23346
+ delete from public.order_cart_layer_positions where cart_id = p_cart_id;
24936
23347
 
24937
- INSERT INTO public.print_jobs(
24938
- printer_id, layout_id, documenttype, documentnaam, status, foutmelding,
24939
- payload, gebruiker_naam, pdf_url, pdf_base64, herkomst, is_herprint, idempotentie_sleutel
24940
- )
24941
- VALUES (
24942
- target_printer,
24943
- target_layout,
24944
- doc_type,
24945
- COALESCE(item.document_number, 'Pickdocument'),
24946
- 'wachtend',
24947
- CASE
24948
- WHEN target_layout IS NULL AND pdf_url IS NULL AND pdf_b64 IS NULL
24949
- THEN 'Geen actieve layout gevonden voor documentsoort "' || COALESCE(nullif(doc_code, ''), 'onbekend') || '". Maak een layout aan in de Layout maker of koppel er een in de Logistiek Engine.'
24950
- ELSE NULL
24951
- END,
24952
- jsonb_build_object(
24953
- 'source', 'pickstation',
24954
- 'queue_item_id', item.id,
24955
- 'station_id', item.station_id,
24956
- 'print_plek_id', target_plek,
24957
- 'session_id', _session_id,
24958
- 'is_herprint', COALESCE(_is_herprint, false),
24959
- 'document_number', item.document_number,
24960
- 'document_type_code', COALESCE(nullif(doc_code, ''), dt.code),
24961
- 'report_key', dt.report_key,
24962
- 'print_template', dt.print_template,
24963
- 'order_number', COALESCE(selected_cart->>'orderNumber', item.order_number),
24964
- 'customer_name', item.customer_name,
24965
- 'reference', item.reference,
24966
- 'layout_id', target_layout,
24967
- 'layout_variant', CASE WHEN v_is_partial THEN 'partial_order' ELSE 'full_cart' END,
24968
- 'barcode', CASE WHEN v_barcode_on THEN COALESCE(v_barcode, item.barcode) ELSE item.barcode END,
24969
- 'country_code', item.country_code,
24970
- 'cart_id', _cart_id,
24971
- 'cart', selected_cart,
24972
- 'data', COALESCE(item.payload, '{}'::jsonb) || jsonb_build_object(
24973
- 'print_plek_id', target_plek,
24974
- 'cart_id', _cart_id,
24975
- 'cart', selected_cart,
24976
- 'orderId', COALESCE(selected_cart->>'orderId', item.payload->>'orderId'),
24977
- 'orderNumber', COALESCE(selected_cart->>'orderNumber', item.order_number)
24978
- )
24979
- ),
24980
- 'Pickstation',
24981
- pdf_url,
24982
- pdf_b64,
24983
- herkomst,
24984
- COALESCE(_is_herprint, false),
24985
- v_sleutel
24986
- )
24987
- RETURNING id INTO job_id;
23348
+ for v_k in 0 .. greatest(jsonb_array_length(v_placed) - 1, -1) loop
23349
+ exit when jsonb_array_length(v_placed) = 0;
23350
+ v_keg := v_placed -> v_k;
23351
+ insert into public.order_cart_layer_positions
23352
+ (cart_id, layer, keg_id, x, y, width, height, rotation,
23353
+ layer_width, layer_height, product_id, order_item_id, label,
23354
+ source, algorithm_version, manual_override, manual_overridden_at, manual_overridden_by)
23355
+ values
23356
+ (p_cart_id, (v_keg->>'layer')::int, v_keg->>'keg',
23357
+ (v_keg->>'x')::numeric, (v_keg->>'y')::numeric,
23358
+ (v_keg->>'w')::numeric, (v_keg->>'h')::numeric, 0,
23359
+ v_layer_w, v_layer_h,
23360
+ nullif(v_keg->>'product_id','')::uuid,
23361
+ nullif(v_keg->>'order_item_id','')::uuid,
23362
+ v_keg->>'label',
23363
+ 'sql-grid-v5', 5,
23364
+ coalesce((v_keg->>'manual')::boolean, false),
23365
+ nullif(v_keg->>'at','')::timestamptz,
23366
+ nullif(v_keg->>'by','')::uuid);
23367
+ v_written := v_written + 1;
23368
+ end loop;
24988
23369
 
24989
- PERFORM public.logistics_enqueue_sticker_with_slip(_queue_item_id, _printer_id, _session_id, COALESCE(_is_herprint,false), _cart_id, job_id);
24990
- UPDATE public.pick_queue SET print_job_id = job_id WHERE id = _queue_item_id;
23370
+ -- 6. karregels volgen de werkelijke posities
23371
+ update public.order_cart_items ci
23372
+ set layer_index = s.min_layer,
23373
+ crates_in_layer = s.max_per_layer
23374
+ from (
23375
+ select order_item_id,
23376
+ min(layer) as min_layer,
23377
+ max(cnt) as max_per_layer
23378
+ from (
23379
+ select order_item_id, layer, count(*) as cnt
23380
+ from public.order_cart_layer_positions
23381
+ where cart_id = p_cart_id and order_item_id is not null
23382
+ group by order_item_id, layer
23383
+ ) g
23384
+ group by order_item_id
23385
+ ) s
23386
+ where ci.cart_id = p_cart_id and ci.order_item_id = s.order_item_id;
24991
23387
 
24992
- RETURN job_id;
24993
- END;
23388
+ return v_written;
23389
+ end;
24994
23390
  $function$
24995
23391
  $fsdef$;
24996
23392
  EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
24997
- RAISE NOTICE 'logistiek-engine: functie public.pick_enqueue_print_job overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
23393
+ RAISE NOTICE 'logistiek-engine: functie public.logistics_write_cart_layer_positions overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
23394
+ END $fsdo$;
23395
+
23396
+ DO $fsdo$ BEGIN
23397
+ EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.pick_cart_breakdown(_order_id uuid)
23398
+ RETURNS jsonb
23399
+ LANGUAGE plpgsql
23400
+ STABLE SECURITY DEFINER
23401
+ SET search_path TO 'public'
23402
+ AS $function$
23403
+ DECLARE
23404
+ v_orders uuid[]; v_ref text; v_status jsonb;
23405
+ v_ready boolean := false; v_released boolean := false;
23406
+ v_total integer := 0; v_full integer := 0; v_partial integer := 0;
23407
+ v_shared integer := 0; v_crates integer := 0;
23408
+ BEGIN
23409
+ v_orders := public.pick_buffer_order_ids(_order_id);
23410
+ IF v_orders IS NULL OR cardinality(v_orders) = 0 THEN v_orders := ARRAY[_order_id]; END IF;
23411
+ SELECT o.order_number INTO v_ref FROM public.orders o WHERE o.id = _order_id;
23412
+ v_status := public.logistics_cart_plan_status(v_ref);
23413
+ v_ready := COALESCE((v_status->>'ready')::boolean, false);
23414
+ v_released := COALESCE((v_status->>'released')::boolean, false);
23415
+ IF v_ready THEN
23416
+ v_total := COALESCE((v_status->>'cartTotal')::integer, (v_status->>'totalCarts')::integer, 0);
23417
+ SELECT
23418
+ count(*) FILTER (WHERE COALESCE((c->>'isFullCart')::boolean, false)),
23419
+ count(*) FILTER (WHERE NOT COALESCE((c->>'isFullCart')::boolean, false)),
23420
+ count(*) FILTER (WHERE COALESCE((c->>'isShared')::boolean, false))
23421
+ INTO v_full, v_partial, v_shared
23422
+ FROM jsonb_array_elements(COALESCE(v_status->'carts', '[]'::jsonb)) c;
23423
+ IF v_total = 0 THEN v_total := v_full + v_partial; END IF;
23424
+ IF v_total > 0 AND v_full + v_partial <> v_total THEN
23425
+ v_partial := GREATEST(v_total - v_full, 0);
23426
+ END IF;
23427
+ END IF;
23428
+ SELECT COALESCE(sum(ci.crates), 0)::integer INTO v_crates
23429
+ FROM public.order_cart_items ci
23430
+ JOIN logistics_active.order_items oi ON oi.id = ci.order_item_id
23431
+ WHERE oi.order_id = ANY(v_orders);
23432
+ RETURN jsonb_build_object(
23433
+ 'totalCarts', COALESCE(v_total, 0),
23434
+ 'fullCarts', COALESCE(v_full, 0),
23435
+ 'partialCarts', COALESCE(v_partial, 0),
23436
+ 'sharedCarts', COALESCE(v_shared, 0),
23437
+ 'crates', COALESCE(v_crates, 0),
23438
+ 'cartPlanReady', v_ready,
23439
+ 'cartPlanReleased', v_released,
23440
+ 'cartPlanReason', v_status->>'reason'
23441
+ ) || public.logistics_pick_mode();
23442
+ END; $function$
23443
+ $fsdef$;
23444
+ EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
23445
+ RAISE NOTICE 'logistiek-engine: functie public.pick_cart_breakdown overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
24998
23446
  END $fsdo$;
24999
23447
 
23448
+
23449
+
25000
23450
  DO $fsdo$ BEGIN
25001
23451
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.pick_queue_apply_cart_breakdown()
25002
23452
  RETURNS trigger
@@ -25056,311 +23506,22 @@ BEGIN
25056
23506
  r := public.pick_queue_cart_plan_v152(_queue_item_id);
25057
23507
  IF nullif(r->>'rowCartId','') IS NOT NULL THEN
25058
23508
  SELECT e INTO v_own FROM jsonb_array_elements(r->'carts') e WHERE e->>'cartId' = r->>'rowCartId' LIMIT 1;
25059
- END IF;
25060
- RETURN r || jsonb_build_object(
25061
- 'cartCrates', CASE WHEN v_own IS NOT NULL THEN v_own->'cartCrates' END,
25062
- 'bufferCrates', CASE WHEN v_own IS NOT NULL THEN v_own->'bufferCrates' END,
25063
- 'departureAt', CASE WHEN v_own IS NOT NULL THEN v_own->'departureAt' END,
25064
- 'pickStartsAt', CASE WHEN v_own IS NOT NULL THEN v_own->'pickStartsAt' END,
25065
- 'releaseAt', CASE WHEN v_own IS NOT NULL THEN v_own->'releaseAt' END,
25066
- 'releaseReason', CASE WHEN v_own IS NOT NULL THEN v_own->'releaseReason' END);
25067
- END $function$
25068
- $fsdef$;
25069
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
25070
- RAISE NOTICE 'logistiek-engine: functie public.pick_queue_cart_plan overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
25071
- END $fsdo$;
25072
-
25073
- DO $fsdo$ BEGIN
25074
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.recompute_all_reserved_stock()
25075
- RETURNS void
25076
- LANGUAGE plpgsql
25077
- SECURITY DEFINER
25078
- SET search_path TO 'public'
25079
- AS $function$
25080
- DECLARE
25081
- oi_rec record; part record; member_rec record;
25082
- v_key text; v_last_key text; v_remaining numeric; v_available numeric; v_take numeric; v_err text;
25083
- v_skipped jsonb := '[]'::jsonb; v_skipped_qty numeric := 0;
25084
- v_struct jsonb := '[]'::jsonb; v_struct_qty numeric := 0;
25085
- v_frozen_ids uuid[];
25086
- BEGIN
25087
- IF current_setting('app.recomputing_reserved', true) = 'on' THEN RETURN; END IF;
25088
- PERFORM set_config('app.recomputing_reserved', 'on', true);
25089
-
25090
- DROP TABLE IF EXISTS _reserved_keys;
25091
- DROP TABLE IF EXISTS _reserved_demand;
25092
- DROP TABLE IF EXISTS _reserved_line;
25093
- DROP TABLE IF EXISTS _reserved_alloc;
25094
- DROP TABLE IF EXISTS _reserved_frozen;
25095
- DROP TABLE IF EXISTS _reserved_double;
25096
- CREATE TEMP TABLE _reserved_keys (raw_key text PRIMARY KEY, canon_key text) ON COMMIT DROP;
25097
- CREATE TEMP TABLE _reserved_demand (trade_item_key text PRIMARY KEY, qty numeric NOT NULL DEFAULT 0) ON COMMIT DROP;
25098
- CREATE TEMP TABLE _reserved_line (kind text, ref text, qty numeric, note text) ON COMMIT DROP;
25099
- CREATE TEMP TABLE _reserved_alloc (order_item_id uuid, trade_item_key text, qty numeric, source text, group_id uuid) ON COMMIT DROP;
25100
- CREATE TEMP TABLE _reserved_frozen (order_item_id uuid PRIMARY KEY) ON COMMIT DROP;
25101
- CREATE TEMP TABLE _reserved_double (order_item_id uuid PRIMARY KEY) ON COMMIT DROP;
25102
-
25103
- INSERT INTO _reserved_double SELECT DISTINCT d.order_item_id FROM public.logistics_double_group_lines(NULL) d;
25104
-
25105
- IF public.logistics_plan_setting('verdeling_bevriezen_actief', 0) = 1 THEN
25106
- INSERT INTO _reserved_frozen
25107
- SELECT oi.id
25108
- FROM logistics_active.order_items oi
25109
- JOIN public.orders o ON o.id = oi.order_id
25110
- WHERE o.status = 'Nog te picken' AND NOT public.logistics_reserved_order_excluded(o.id)
25111
- AND COALESCE(oi.quantity, 0) > 0
25112
- AND oi.product_id IS NOT NULL
25113
- AND NOT EXISTS (SELECT 1 FROM _reserved_double x WHERE x.order_item_id = oi.id)
25114
- AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log sml WHERE sml.order_item_id = oi.id AND sml.mutation_type = 'consumption')
25115
- AND EXISTS (SELECT 1 FROM public.logistics_reservation_allocations a WHERE a.order_item_id = oi.id AND a.source <> 'error')
25116
- AND public.logistics_allocation_frozen(oi.order_id)
25117
- ON CONFLICT DO NOTHING;
25118
-
25119
- -- Bevroren verdeling eerst, naar rato van het huidige aantal (verlaging geeft reservering vrij).
25120
- INSERT INTO _reserved_alloc
25121
- SELECT f.order_item_id, s.trade_item_key, s.qty, s.source, s.group_id
25122
- FROM _reserved_frozen f
25123
- JOIN logistics_active.order_items oi ON oi.id = f.order_item_id
25124
- CROSS JOIN LATERAL public.logistics_frozen_allocation_scaled(f.order_item_id, COALESCE(oi.quantity, 0)::numeric) s;
25125
- INSERT INTO _reserved_demand AS d
25126
- SELECT trade_item_key, SUM(qty) FROM _reserved_alloc GROUP BY trade_item_key
25127
- ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
25128
- END IF;
25129
-
25130
- FOR oi_rec IN
25131
- SELECT oi.id, oi.product_id, COALESCE(oi.quantity, 0)::numeric AS quantity
25132
- FROM logistics_active.order_items oi
25133
- JOIN public.orders o ON o.id = oi.order_id
25134
- WHERE o.status = 'Nog te picken' AND NOT public.logistics_reserved_order_excluded(o.id)
25135
- AND COALESCE(oi.quantity, 0) > 0
25136
- AND oi.product_id IS NOT NULL
25137
- AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log sml WHERE sml.order_item_id = oi.id AND sml.mutation_type = 'consumption')
25138
- AND NOT EXISTS (SELECT 1 FROM _reserved_frozen f WHERE f.order_item_id = oi.id)
25139
- AND NOT EXISTS (SELECT 1 FROM _reserved_double x WHERE x.order_item_id = oi.id)
25140
- ORDER BY o.created_at, oi.id
25141
- LOOP
25142
- DELETE FROM _reserved_line WHERE true; -- safeupdate: nooit DELETE zonder WHERE
25143
- INSERT INTO _reserved_line SELECT * FROM public.logistics_expand_line(oi_rec.product_id, oi_rec.quantity);
25144
-
25145
- SELECT string_agg(l.note, '; ') INTO v_err FROM _reserved_line l WHERE l.kind = 'error';
25146
- IF v_err IS NOT NULL THEN
25147
- v_struct := v_struct || jsonb_build_object('order_item_id', oi_rec.id, 'product_id', oi_rec.product_id, 'reden', v_err);
25148
- v_struct_qty := v_struct_qty + oi_rec.quantity;
25149
- CONTINUE;
25150
- END IF;
25151
-
25152
- FOR part IN SELECT l.kind, l.ref, l.qty FROM _reserved_line l LOOP
25153
- IF part.kind = 'item' THEN
25154
- IF NOT EXISTS (SELECT 1 FROM _reserved_keys WHERE raw_key = part.ref) THEN
25155
- INSERT INTO _reserved_keys VALUES (part.ref, public.logistics_reserved_key(part.ref));
25156
- END IF;
25157
- SELECT canon_key INTO v_key FROM _reserved_keys WHERE raw_key = part.ref;
25158
- IF v_key IS NULL THEN
25159
- v_skipped := v_skipped || to_jsonb(part.ref);
25160
- v_skipped_qty := v_skipped_qty + part.qty;
25161
- ELSE
25162
- INSERT INTO _reserved_demand AS d VALUES (v_key, part.qty)
25163
- ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
25164
- INSERT INTO _reserved_alloc VALUES (oi_rec.id, v_key, part.qty, 'item', NULL);
25165
- END IF;
25166
- ELSE
25167
- v_remaining := part.qty;
25168
- v_last_key := NULL;
25169
- FOR member_rec IN
25170
- SELECT public.resolve_group_member_stock_key(pgi.trade_item_id) AS raw_key
25171
- FROM public.product_group_items pgi
25172
- WHERE pgi.product_group_id = part.ref::uuid
25173
- AND public.resolve_group_member_stock_key(pgi.trade_item_id) IS NOT NULL
25174
- AND public.resolve_group_member_stock_key(pgi.trade_item_id) NOT LIKE 'group:%'
25175
- ORDER BY pgi.sort_order ASC NULLS LAST, pgi.created_at ASC, pgi.id ASC
25176
- LOOP
25177
- IF NOT EXISTS (SELECT 1 FROM _reserved_keys WHERE raw_key = member_rec.raw_key) THEN
25178
- INSERT INTO _reserved_keys VALUES (member_rec.raw_key, public.logistics_reserved_key(member_rec.raw_key));
25179
- END IF;
25180
- SELECT canon_key INTO v_key FROM _reserved_keys WHERE raw_key = member_rec.raw_key;
25181
- IF v_key IS NULL THEN CONTINUE; END IF;
25182
- v_last_key := v_key;
25183
- IF v_remaining <= 0 THEN CONTINUE; END IF;
25184
-
25185
- SELECT GREATEST(COALESCE(MAX(s.physical_stock), 0)
25186
- - COALESCE((SELECT d.qty FROM _reserved_demand d WHERE d.trade_item_key = v_key), 0), 0)
25187
- INTO v_available
25188
- FROM public.trade_item_stock s
25189
- WHERE s.trade_item_key = v_key;
25190
-
25191
- IF COALESCE(v_available, 0) <= 0 THEN CONTINUE; END IF;
25192
- v_take := LEAST(v_available, v_remaining);
25193
- INSERT INTO _reserved_demand AS d VALUES (v_key, v_take)
25194
- ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
25195
- INSERT INTO _reserved_alloc VALUES (oi_rec.id, v_key, v_take, 'group', part.ref::uuid);
25196
- v_remaining := v_remaining - v_take;
25197
- END LOOP;
25198
-
25199
- IF v_remaining > 0 THEN
25200
- IF v_last_key IS NULL THEN
25201
- v_skipped := v_skipped || to_jsonb('group:' || part.ref);
25202
- v_skipped_qty := v_skipped_qty + v_remaining;
25203
- ELSE
25204
- INSERT INTO _reserved_demand AS d VALUES (v_last_key, v_remaining)
25205
- ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
25206
- INSERT INTO _reserved_alloc VALUES (oi_rec.id, v_last_key, v_remaining, 'group', part.ref::uuid);
25207
- END IF;
25208
- END IF;
25209
- END IF;
25210
- END LOOP;
25211
- END LOOP;
25212
-
25213
- DELETE FROM public.logistics_reservation_allocations a
25214
- WHERE a.order_item_id IN (
25215
- SELECT oi.id FROM logistics_active.order_items oi
25216
- JOIN public.orders o ON o.id = oi.order_id
25217
- WHERE o.status = 'Nog te picken' AND NOT public.logistics_reserved_order_excluded(o.id)
25218
- AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log sml
25219
- WHERE sml.order_item_id = oi.id AND sml.mutation_type = 'consumption'));
25220
- INSERT INTO public.logistics_reservation_allocations (order_item_id, trade_item_key, qty, source, group_id, line_qty)
25221
- SELECT r.order_item_id, r.trade_item_key, SUM(r.qty), r.source, r.group_id, MAX(COALESCE(oi.quantity, 0))::numeric
25222
- FROM _reserved_alloc r
25223
- LEFT JOIN logistics_active.order_items oi ON oi.id = r.order_item_id
25224
- WHERE r.qty > 0
25225
- GROUP BY r.order_item_id, r.trade_item_key, r.source, r.group_id;
25226
- DELETE FROM public.logistics_reservation_allocations WHERE computed_at < now() - interval '90 days';
25227
-
25228
- PERFORM set_config('stock.change_source', 'RESERVATION_RECOMPUTE', true);
25229
- UPDATE public.trade_item_stock s
25230
- SET reserved_stock = CEIL(COALESCE(d.qty, 0))::integer,
25231
- change_source = 'RESERVATION_RECOMPUTE',
25232
- updated_at = now()
25233
- FROM public.trade_item_stock s2
25234
- LEFT JOIN _reserved_demand d ON d.trade_item_key = s2.trade_item_key
25235
- WHERE s2.id = s.id
25236
- AND s.reserved_stock IS DISTINCT FROM CEIL(COALESCE(d.qty, 0))::integer;
25237
-
25238
- IF jsonb_array_length(v_skipped) > 0 THEN
25239
- PERFORM public.logistics_trace(jsonb_build_object(
25240
- 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
25241
- 'actor', 'recompute_all_reserved_stock', 'errorClass', 'ontbrekende_gegevens',
25242
- 'reason', 'Geen voorraadrij en geen actieve Floriday-koppeling om die aan te maken; reservering voor deze artikelen niet berekend.',
25243
- 'values', jsonb_build_object(
25244
- 'aantal_sleutels', (SELECT count(DISTINCT x) FROM jsonb_array_elements_text(v_skipped) x),
25245
- 'sleutels', (SELECT jsonb_agg(DISTINCT x) FROM jsonb_array_elements_text(v_skipped) x),
25246
- 'gemiste_vraag_stuks', v_skipped_qty)));
25247
- END IF;
25248
-
25249
- IF jsonb_array_length(v_struct) > 0 THEN
25250
- PERFORM public.logistics_trace(jsonb_build_object(
25251
- 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
25252
- 'actor', 'recompute_all_reserved_stock', 'errorClass', 'structuur',
25253
- 'reason', 'Samenstelling met een lus of te diepe nesting; voor deze bestelregels is niets gereserveerd.',
25254
- 'values', jsonb_build_object('aantal_regels', jsonb_array_length(v_struct), 'regels', v_struct, 'gemiste_vraag_stuks', v_struct_qty)));
25255
- END IF;
25256
-
25257
- SELECT array_agg(order_item_id) INTO v_frozen_ids FROM _reserved_frozen;
25258
- PERFORM public.logistics_report_frozen_shortage(v_frozen_ids);
25259
- PERFORM public.logistics_allocation_check_changes(NULL);
25260
-
25261
- PERFORM set_config('app.recomputing_reserved', 'off', true);
25262
- END;
25263
- $function$
25264
- $fsdef$;
25265
- EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
25266
- RAISE NOTICE 'logistiek-engine: functie public.recompute_all_reserved_stock overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
25267
- END $fsdo$;
25268
-
25269
- DO $fsdo$ BEGIN
25270
- EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.recompute_reserved_stock(p_trade_item_key text)
25271
- RETURNS void
25272
- LANGUAGE plpgsql
25273
- SECURITY DEFINER
25274
- SET search_path TO 'public'
25275
- AS $function$
25276
- DECLARE
25277
- v_key text;
25278
- v_sum integer;
25279
- v_products uuid[];
25280
- v_pid uuid;
25281
- BEGIN
25282
- IF p_trade_item_key IS NULL OR btrim(p_trade_item_key) = '' THEN RETURN; END IF;
25283
- IF current_setting('app.recomputing_reserved', true) = 'on' THEN RETURN; END IF;
25284
-
25285
- -- B1: nieuwe of losse sleutel zonder orderregels, toewijzing of set-/groepsrelatie en reservering 0: niets te doen.
25286
- v_key := public.logistics_reserved_key(p_trade_item_key);
25287
- IF public.logistics_reserved_fast_skip(p_trade_item_key, v_key) THEN RETURN; END IF;
25288
-
25289
- -- Set of groepshoofdproduct: gericht herberekenen op de onderdelen en leden (M7).
25290
- SELECT array_agg(p.id) INTO v_products
25291
- FROM public.products p
25292
- WHERE (p.trade_item_key = btrim(p_trade_item_key)
25293
- OR p.floriday_trade_item_id = btrim(p_trade_item_key)
25294
- OR p.id::text = btrim(p_trade_item_key))
25295
- AND (EXISTS (SELECT 1 FROM public.product_bom_items b WHERE b.parent_product_id = p.id)
25296
- OR EXISTS (SELECT 1 FROM public.product_groups pg
25297
- WHERE pg.is_active = true AND (pg.id = p.id OR pg.head_product_id = p.id)));
25298
- IF v_products IS NOT NULL THEN
25299
- FOREACH v_pid IN ARRAY v_products LOOP
25300
- PERFORM public.logistics_recompute_reserved_targeted(v_pid);
25301
- END LOOP;
25302
- RETURN;
25303
- END IF;
25304
-
25305
- IF v_key IS NULL THEN
25306
- PERFORM public.logistics_trace(jsonb_build_object(
25307
- 'phase', 'reservering',
25308
- 'step', 'reservering herberekenen',
25309
- 'status', 'waarschuwing',
25310
- 'actor', 'recompute_reserved_stock',
25311
- 'errorClass', 'ontbrekende_gegevens',
25312
- 'reason', 'Geen voorraadrij en geen actieve Floriday-koppeling om die aan te maken; reservering niet berekend.',
25313
- 'values', jsonb_build_object('sleutel', btrim(p_trade_item_key))));
25314
- RETURN;
25315
- END IF;
25316
-
25317
- -- Sleutel hoort bij een groep of samenstelling: de toewijzing hangt van andere
25318
- -- regels af, dus volledig herberekenen.
25319
- IF v_key LIKE 'group:%'
25320
- OR EXISTS (SELECT 1 FROM public.logistics_reservation_allocations a
25321
- WHERE a.trade_item_key = v_key AND a.source = 'group')
25322
- OR EXISTS (SELECT 1 FROM public.product_group_items pgi
25323
- JOIN public.product_groups pg ON pg.id = pgi.product_group_id AND pg.is_active = true
25324
- WHERE public.resolve_group_member_stock_key(pgi.trade_item_id) IN (v_key, btrim(p_trade_item_key)))
25325
- OR EXISTS (SELECT 1 FROM public.product_bom_items b
25326
- JOIN public.products p ON p.id = b.component_product_id
25327
- WHERE COALESCE(NULLIF(btrim(COALESCE(p.floriday_trade_item_id, '')), ''),
25328
- NULLIF(btrim(COALESCE(p.trade_item_key, '')), ''), p.id::text) = v_key
25329
- OR p.trade_item_key = btrim(p_trade_item_key))
25330
- THEN
25331
- PERFORM public.recompute_all_reserved_stock();
25332
- RETURN;
25333
- END IF;
25334
-
25335
- SELECT CEIL(COALESCE(SUM(COALESCE(oi.quantity, 0)), 0))::integer INTO v_sum
25336
- FROM logistics_active.order_items oi
25337
- JOIN public.orders o ON o.id = oi.order_id
25338
- JOIN public.products p ON p.id = oi.product_id
25339
- WHERE COALESCE(NULLIF(btrim(COALESCE(p.floriday_trade_item_id, '')), ''),
25340
- NULLIF(btrim(COALESCE(p.trade_item_key, '')), ''), p.id::text) = v_key
25341
- AND o.status = 'Nog te picken' AND NOT public.logistics_reserved_order_excluded(o.id)
25342
- AND NOT EXISTS (SELECT 1 FROM public.product_bom_items b WHERE b.parent_product_id = p.id)
25343
- AND NOT EXISTS (SELECT 1 FROM public.product_groups pg
25344
- WHERE pg.is_active = true AND (pg.id = p.id OR pg.head_product_id = p.id))
25345
- AND NOT EXISTS (
25346
- SELECT 1 FROM public.stock_mutation_log sml
25347
- WHERE sml.order_item_id = oi.id
25348
- AND sml.mutation_type = 'consumption');
25349
-
25350
- PERFORM set_config('stock.change_source', 'RESERVATION_RECOMPUTE', true);
25351
- UPDATE public.trade_item_stock
25352
- SET reserved_stock = v_sum,
25353
- change_source = 'RESERVATION_RECOMPUTE',
25354
- updated_at = now()
25355
- WHERE trade_item_key = v_key
25356
- AND reserved_stock IS DISTINCT FROM v_sum;
25357
- END
25358
- $function$
23509
+ END IF;
23510
+ RETURN r || jsonb_build_object(
23511
+ 'cartCrates', CASE WHEN v_own IS NOT NULL THEN v_own->'cartCrates' END,
23512
+ 'bufferCrates', CASE WHEN v_own IS NOT NULL THEN v_own->'bufferCrates' END,
23513
+ 'departureAt', CASE WHEN v_own IS NOT NULL THEN v_own->'departureAt' END,
23514
+ 'pickStartsAt', CASE WHEN v_own IS NOT NULL THEN v_own->'pickStartsAt' END,
23515
+ 'releaseAt', CASE WHEN v_own IS NOT NULL THEN v_own->'releaseAt' END,
23516
+ 'releaseReason', CASE WHEN v_own IS NOT NULL THEN v_own->'releaseReason' END);
23517
+ END $function$
25359
23518
  $fsdef$;
25360
23519
  EXCEPTION WHEN undefined_object OR undefined_table OR undefined_function OR invalid_schema_name THEN
25361
- RAISE NOTICE 'logistiek-engine: functie public.recompute_reserved_stock overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
23520
+ RAISE NOTICE 'logistiek-engine: functie public.pick_queue_cart_plan overgeslagen (onderdeel van andere module ontbreekt): %', SQLERRM;
25362
23521
  END $fsdo$;
25363
23522
 
23523
+
23524
+
25364
23525
  DO $fsdo$ BEGIN
25365
23526
  EXECUTE $fsdef$CREATE OR REPLACE FUNCTION public.logistics_register_tick_apikey(p_key text)
25366
23527
  RETURNS jsonb
@@ -28323,78 +26484,6 @@ AS $function$
28323
26484
  $function$;
28324
26485
 
28325
26486
  -- 4. Centrale beslisregel.
28326
- CREATE OR REPLACE FUNCTION public.logistics_document_for_order(p_order_id uuid, p_documenttype text)
28327
- RETURNS jsonb
28328
- LANGUAGE plpgsql
28329
- STABLE SECURITY DEFINER
28330
- SET search_path TO 'public'
28331
- AS $function$
28332
- DECLARE
28333
- v_soort text := lower(btrim(coalesce(p_documenttype, '')));
28334
- v_floriday boolean;
28335
- v_bron text;
28336
- v_type text;
28337
- v_res jsonb;
28338
- v_n int; v_std int; v_id uuid;
28339
- v_l public.document_layouts%ROWTYPE;
28340
- BEGIN
28341
- IF v_soort = '' THEN
28342
- RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'reden', 'Geen documentsoort opgegeven.');
28343
- END IF;
28344
-
28345
- IF to_regprocedure('public.layout_voor_document(uuid,text)') IS NOT NULL THEN
28346
- EXECUTE 'SELECT to_jsonb(public.layout_voor_document($1, $2))' INTO v_res USING p_order_id, v_soort;
28347
- IF jsonb_typeof(v_res) <> 'object' THEN
28348
- RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'layouts',
28349
- 'reden', 'Layouts gaf geen bruikbaar antwoord voor ' || v_soort || '.');
28350
- END IF;
28351
- RETURN v_res || jsonb_build_object('keuze', 'layouts',
28352
- 'ok', coalesce((v_res->>'ok')::boolean, nullif(v_res->>'layout_id', '') IS NOT NULL));
28353
- END IF;
28354
-
28355
- -- Automatische keuze (Layouts nog niet bijgewerkt).
28356
- SELECT o.order_source = 'floriday' INTO v_floriday FROM public.orders o WHERE o.id = p_order_id;
28357
- IF v_floriday IS NULL THEN
28358
- RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'automatisch', 'reden', 'Order niet gevonden.');
28359
- END IF;
28360
-
28361
- v_type := CASE
28362
- WHEN v_soort IN ('eigen_sticker', 'sticker', 'etiket') THEN 'sticker'
28363
- WHEN v_soort IN ('verzenddocument', 'verzendbrief', 'afleverbrief', 'afleverbon') THEN 'afleverbrief'
28364
- WHEN v_soort IN ('veilingbrief', 'veilingbon') THEN 'veilingbrief'
28365
- WHEN v_soort LIKE '%pakbon%' THEN 'pakbon'
28366
- WHEN v_soort LIKE '%productie%' THEN 'productiebon'
28367
- ELSE v_soort END;
28368
- v_bron := CASE
28369
- WHEN v_floriday AND v_soort <> 'eigen_sticker' AND v_type IN ('sticker', 'traysticker', 'afleverbrief', 'veilingbrief') THEN 'floriday'
28370
- ELSE 'eigen' END;
28371
- IF v_bron = 'eigen' AND v_type = 'veilingbrief' THEN
28372
- RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'automatisch',
28373
- 'reden', 'Een veilingbrief hoort alleen bij een Floriday-order; voor een eigen order wordt de afleverbrief gebruikt.');
28374
- END IF;
28375
-
28376
- SELECT count(*), count(*) FILTER (WHERE is_standaard IS TRUE) INTO v_n, v_std
28377
- FROM public.document_layouts
28378
- WHERE actief IS TRUE AND coalesce(bron, 'eigen') = v_bron AND documenttype::text = v_type;
28379
- IF v_n = 1 THEN
28380
- SELECT * INTO v_l FROM public.document_layouts
28381
- WHERE actief IS TRUE AND coalesce(bron, 'eigen') = v_bron AND documenttype::text = v_type;
28382
- ELSIF v_n > 1 THEN
28383
- SELECT * INTO v_l FROM public.document_layouts
28384
- WHERE actief IS TRUE AND coalesce(bron, 'eigen') = v_bron AND documenttype::text = v_type
28385
- ORDER BY is_standaard DESC NULLS LAST, updated_at DESC LIMIT 1;
28386
- END IF;
28387
- IF v_l.id IS NULL THEN
28388
- RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'automatisch', 'bron', v_bron, 'documenttype', v_type,
28389
- 'reden', CASE WHEN v_bron = 'floriday'
28390
- THEN 'Geen actieve ' || v_type || '-layout met bron Floriday. Activeer de Floriday-systeemlayout in Layouts en printers.'
28391
- ELSE 'Maak of activeer een ' || v_type || 'layout in Layouts en printers.' END);
28392
- END IF;
28393
- RETURN jsonb_build_object('ok', true, 'layout_id', v_l.id, 'keuze', 'automatisch', 'bron', v_bron,
28394
- 'documenttype', v_type, 'breedte_mm', v_l.breedte_mm, 'hoogte_mm', v_l.hoogte_mm, 'reden', NULL);
28395
- END;
28396
- $function$;
28397
-
28398
26487
  -- 5. Eigen stickers (HO/MO/AO): één per klant+product, aantal = planten van regels met needs_sticker.
28399
26488
  CREATE OR REPLACE FUNCTION public.logistics_own_sticker_count(p_unit text, p_quantity numeric, p_ppc_regel numeric, p_ppc_product numeric)
28400
26489
  RETURNS integer LANGUAGE sql IMMUTABLE SET search_path = public AS $cnt$
@@ -28436,271 +26525,95 @@ BEGIN
28436
26525
  AND oi.quarantined_at IS NULL
28437
26526
  AND oi.product_id IS NOT NULL
28438
26527
  AND coalesce(o.order_source, '') <> 'floriday'
28439
- AND o.deleted_at IS NULL AND o.cancelled_at IS NULL
28440
- AND coalesce(o.status, '') <> 'geannuleerd'
28441
- AND o.delivery_date >= current_date - 1
28442
- GROUP BY o.id, o.order_number, o.customer_id, oi.product_id
28443
- LIMIT greatest(1, p_limit)
28444
- LOOP
28445
- v_bron := r.order_id::text || ':' || r.product_id::text;
28446
- SELECT count(*), (array_agg(s.id))[1] INTO v_n, v_sticker
28447
- FROM public.customer_product_stickers s
28448
- JOIN public.customers c ON c.id = r.customer_id
28449
- WHERE s.trade_item_key = r.product_id::text
28450
- AND s.customer_key IN (c.id::text, 'tf:' || coalesce(c.twinfield_code, '-'), 'cn:' || coalesce(c.customer_number, '-'));
28451
- v_status := 'wacht_op_moment'; v_reden := NULL;
28452
- IF r.onbekend THEN
28453
- v_status := 'geblokkeerd'; v_reden := 'Aantal stickers onbekend: regel in fust zonder planten per fust (niet op de regel en niet bij het product). Vul planten per fust in.';
28454
- ELSIF coalesce(r.planten, 0) < 1 THEN
28455
- v_status := 'geblokkeerd'; v_reden := 'Aantal planten is 0; geen sticker te printen.';
28456
- ELSIF v_n = 0 THEN
28457
- v_status := 'geblokkeerd'; v_reden := 'Geen eigen sticker gevonden voor deze klant en dit product. Voeg hem toe bij de klant.';
28458
- ELSIF v_n > 1 THEN
28459
- v_status := 'geblokkeerd'; v_reden := 'Meer dan één eigen sticker voor deze klant en dit product; er mag er maar één zijn.';
28460
- END IF;
28461
-
28462
- UPDATE public.logistics_sticker_prints
28463
- SET kopieen = greatest(coalesce(r.planten, 0), 1),
28464
- eigen_sticker_id = CASE WHEN v_n = 1 THEN v_sticker ELSE NULL END,
28465
- status = CASE WHEN status = 'geblokkeerd' AND v_status = 'wacht_op_moment' THEN 'wacht_op_moment'
28466
- WHEN status = 'niet_printen' THEN v_status
28467
- WHEN status = 'wacht_op_moment' THEN v_status ELSE status END,
28468
- reden = CASE WHEN status IN ('wacht_op_moment', 'geblokkeerd', 'niet_printen') THEN v_reden ELSE reden END,
28469
- updated_at = now()
28470
- WHERE soort = 'eigen_sticker' AND bron_id = v_bron
28471
- AND status IN ('wacht_op_moment', 'geblokkeerd', 'niet_printen');
28472
- IF NOT FOUND AND NOT EXISTS (SELECT 1 FROM public.logistics_sticker_prints WHERE soort = 'eigen_sticker' AND bron_id = v_bron) THEN
28473
- INSERT INTO public.logistics_sticker_prints (soort, bron_id, order_id, order_number, kopieen, status, reden, eigen_sticker_id)
28474
- VALUES ('eigen_sticker', v_bron, r.order_id, r.order_number, greatest(coalesce(r.planten, 0), 1), v_status, v_reden,
28475
- CASE WHEN v_n = 1 THEN v_sticker ELSE NULL END);
28476
- END IF;
28477
- v_bijgewerkt := v_bijgewerkt + 1;
28478
- END LOOP;
28479
-
28480
- -- Vinkje uitgezet (of order vervallen) vóór het printen: niet printen.
28481
- UPDATE public.logistics_sticker_prints p
28482
- SET status = 'niet_printen', reden = 'Sticker uitgezet op de orderregel of order vervallen.', updated_at = now()
28483
- WHERE p.soort = 'eigen_sticker' AND p.status IN ('wacht_op_moment', 'geblokkeerd', 'klaar')
28484
- AND NOT EXISTS (
28485
- SELECT 1 FROM public.order_items oi JOIN public.orders o ON o.id = oi.order_id
28486
- WHERE oi.order_id = p.order_id AND p.bron_id = oi.order_id::text || ':' || oi.product_id::text
28487
- AND oi.needs_sticker IS TRUE AND oi.quarantined_at IS NULL
28488
- AND o.deleted_at IS NULL AND o.cancelled_at IS NULL AND coalesce(o.status, '') <> 'geannuleerd');
28489
- GET DIAGNOSTICS v_uit = ROW_COUNT;
28490
-
28491
- -- Vrijgeven met de pakbon: kar en printplek van de geprinte pakbonrij, formaat uit sticker of layout.
28492
- WITH vrij AS (
28493
- SELECT p.id,
28494
- (SELECT public.logistics_try_uuid(q.payload->>'cartId')
28495
- FROM public.pick_queue q
28496
- WHERE q.cancelled_at IS NULL AND q.print_job_id IS NOT NULL
28497
- AND (q.payload->>'orderId' = p.order_id::text OR q.payload->'orderIds' ? p.order_id::text)
28498
- ORDER BY q.created_at LIMIT 1) AS kar,
28499
- (SELECT k.print_plek_id::text
28500
- FROM public.pick_queue q
28501
- JOIN public.print_plek_koppelingen k ON k.bron IN ('pickstation', 'pick_station') AND k.bron_id = q.station_id::text
28502
- WHERE q.cancelled_at IS NULL AND q.print_job_id IS NOT NULL
28503
- AND (q.payload->>'orderId' = p.order_id::text OR q.payload->'orderIds' ? p.order_id::text)
28504
- ORDER BY q.created_at, k.created_at LIMIT 1) AS plek,
28505
- s.width_mm, s.height_mm,
28506
- public.logistics_document_for_order(p.order_id, 'eigen_sticker') AS lay
28507
- FROM public.logistics_sticker_prints p
28508
- LEFT JOIN public.customer_product_stickers s ON s.id = p.eigen_sticker_id
28509
- WHERE p.soort = 'eigen_sticker' AND p.status = 'wacht_op_moment'
28510
- AND EXISTS (SELECT 1 FROM public.pick_queue q
28511
- WHERE q.cancelled_at IS NULL AND q.print_job_id IS NOT NULL
28512
- AND (q.payload->>'orderId' = p.order_id::text OR q.payload->'orderIds' ? p.order_id::text))
28513
- )
28514
- UPDATE public.logistics_sticker_prints p
28515
- SET status = 'klaar', cart_id = vrij.kar, print_plek_id = vrij.plek,
28516
- breedte_mm = coalesce(vrij.width_mm, (vrij.lay->>'breedte_mm')::numeric),
28517
- hoogte_mm = coalesce(vrij.height_mm, (vrij.lay->>'hoogte_mm')::numeric),
28518
- updated_at = now()
28519
- FROM vrij
28520
- WHERE p.id = vrij.id;
28521
- GET DIAGNOSTICS v_vrij = ROW_COUNT;
28522
-
28523
- RETURN jsonb_build_object('regels', v_bijgewerkt, 'vrijgegeven', v_vrij, 'niet_printen', v_uit);
28524
- END;
28525
- $function$;
28526
-
28527
- -- 6. Pakbon van het pickstation: alleen de centrale beslisregel (geen layout uit payload of werkwijze).
28528
- CREATE OR REPLACE FUNCTION public.pick_enqueue_print_job(_queue_item_id uuid, _printer_id uuid, _session_id text, _is_herprint boolean, _cart_id text)
28529
- RETURNS uuid
28530
- LANGUAGE plpgsql
28531
- SECURITY DEFINER
28532
- SET search_path TO 'public'
28533
- AS $function$
28534
- DECLARE
28535
- item public.pick_queue;
28536
- dt public.pick_document_types;
28537
- doc_code text;
28538
- target_printer uuid;
28539
- target_layout uuid;
28540
- target_plek uuid;
28541
- layout_meta jsonb;
28542
- layout_reden text;
28543
- v_types public.doc_layout_type[];
28544
- doc_type public.doc_layout_type := 'vrij';
28545
- layout_type public.doc_layout_type;
28546
- pdf_url text;
28547
- pdf_b64 text;
28548
- herkomst text;
28549
- job_id uuid;
28550
- cart_plan jsonb := NULL;
28551
- selected_cart jsonb := NULL;
28552
- v_cart_uuid uuid;
28553
- v_barcode text;
28554
- v_barcode_on boolean := public.logistics_pick_barcode_enabled();
28555
- v_is_partial boolean := false;
28556
- v_sleutel text;
28557
- v_order uuid;
28558
- BEGIN
28559
- SELECT * INTO item FROM public.pick_queue WHERE id = _queue_item_id;
28560
- IF NOT FOUND THEN RAISE EXCEPTION 'Dit document bestaat niet meer.'; END IF;
28561
-
28562
- SELECT * INTO dt FROM public.pick_document_types WHERE id = item.document_type_id;
28563
-
28564
- doc_code := lower(btrim(coalesce(
28565
- nullif(item.payload->>'documentType', ''),
28566
- nullif(dt.code, ''),
28567
- ''
28568
- )));
28569
-
28570
- v_types := public.logistics_document_type_map(doc_code);
28571
-
28572
- IF _cart_id IS NOT NULL THEN
28573
- cart_plan := public.pick_queue_cart_plan(_queue_item_id);
28574
- SELECT c INTO selected_cart
28575
- FROM jsonb_array_elements(COALESCE(cart_plan->'carts', '[]'::jsonb)) AS c
28576
- WHERE c->>'cartId' = _cart_id
28577
- LIMIT 1;
28578
-
28579
- IF selected_cart IS NULL THEN
28580
- RAISE EXCEPTION 'De gekozen kar bestaat niet meer in het actuele karplan.';
28581
- END IF;
28582
-
28583
- v_is_partial := NOT COALESCE((selected_cart->>'isFullCart')::boolean, false);
28584
-
28585
- IF v_barcode_on THEN
28586
- BEGIN
28587
- v_cart_uuid := nullif(_cart_id, '')::uuid;
28588
- EXCEPTION WHEN invalid_text_representation THEN
28589
- v_cart_uuid := NULL;
28590
- END;
28591
- IF v_cart_uuid IS NOT NULL THEN
28592
- v_barcode := public.logistics_ensure_cart_barcode(v_cart_uuid);
28593
- selected_cart := selected_cart || jsonb_build_object('barcode', v_barcode);
28594
- END IF;
28595
- END IF;
28596
- END IF;
28597
-
28598
- -- 1.0.176: het document wordt automatisch bepaald uit de order.
28599
- v_order := public.logistics_try_uuid(COALESCE(selected_cart->>'orderId', item.payload->>'orderId', item.payload->>'order_id'));
28600
- IF doc_code <> '' AND v_order IS NOT NULL THEN
28601
- layout_meta := public.logistics_document_for_order(v_order, doc_code);
28602
- target_layout := public.logistics_try_uuid(layout_meta->>'layout_id');
28603
- layout_reden := nullif(layout_meta->>'reden', '');
28604
- ELSIF doc_code <> '' THEN
28605
- layout_meta := public.logistics_document_layout_v2(doc_code, v_is_partial);
28606
- target_layout := public.logistics_try_uuid(layout_meta->>'layout_id');
28607
- END IF;
28608
-
28609
- SELECT l.documenttype INTO layout_type
28610
- FROM public.document_layouts l
28611
- WHERE l.id = target_layout
28612
- AND l.actief IS TRUE;
28613
-
28614
- IF layout_type IS NULL THEN
28615
- target_layout := NULL;
28616
- END IF;
28617
-
28618
- doc_type := COALESCE(layout_type, v_types[1], 'vrij');
28619
-
28620
- IF item.station_id IS NOT NULL THEN
28621
- SELECT k.print_plek_id INTO target_plek
28622
- FROM public.print_plek_koppelingen k
28623
- WHERE k.bron IN ('pickstation', 'pick_station')
28624
- AND k.bron_id = item.station_id::text
28625
- ORDER BY k.created_at
28626
- LIMIT 1;
28627
- END IF;
28628
-
28629
- SELECT p.id INTO target_printer
28630
- FROM public.printers p
28631
- WHERE p.id = _printer_id
28632
- AND p.actief
28633
- AND nullif(p.extern_printer_id, '') IS NOT NULL;
26528
+ AND o.deleted_at IS NULL AND o.cancelled_at IS NULL
26529
+ AND coalesce(o.status, '') <> 'geannuleerd'
26530
+ AND o.delivery_date >= current_date - 1
26531
+ GROUP BY o.id, o.order_number, o.customer_id, oi.product_id
26532
+ LIMIT greatest(1, p_limit)
26533
+ LOOP
26534
+ v_bron := r.order_id::text || ':' || r.product_id::text;
26535
+ SELECT count(*), (array_agg(s.id))[1] INTO v_n, v_sticker
26536
+ FROM public.customer_product_stickers s
26537
+ JOIN public.customers c ON c.id = r.customer_id
26538
+ WHERE s.trade_item_key = r.product_id::text
26539
+ AND s.customer_key IN (c.id::text, 'tf:' || coalesce(c.twinfield_code, '-'), 'cn:' || coalesce(c.customer_number, '-'));
26540
+ v_status := 'wacht_op_moment'; v_reden := NULL;
26541
+ IF r.onbekend THEN
26542
+ v_status := 'geblokkeerd'; v_reden := 'Aantal stickers onbekend: regel in fust zonder planten per fust (niet op de regel en niet bij het product). Vul planten per fust in.';
26543
+ ELSIF coalesce(r.planten, 0) < 1 THEN
26544
+ v_status := 'geblokkeerd'; v_reden := 'Aantal planten is 0; geen sticker te printen.';
26545
+ ELSIF v_n = 0 THEN
26546
+ v_status := 'geblokkeerd'; v_reden := 'Geen eigen sticker gevonden voor deze klant en dit product. Voeg hem toe bij de klant.';
26547
+ ELSIF v_n > 1 THEN
26548
+ v_status := 'geblokkeerd'; v_reden := 'Meer dan één eigen sticker voor deze klant en dit product; er mag er maar één zijn.';
26549
+ END IF;
28634
26550
 
28635
- pdf_url := nullif(item.payload->>'pdf_url', '');
28636
- pdf_b64 := nullif(item.payload->>'pdf_base64', '');
28637
- herkomst := nullif(item.payload->>'herkomst', '');
26551
+ UPDATE public.logistics_sticker_prints
26552
+ SET kopieen = greatest(coalesce(r.planten, 0), 1),
26553
+ eigen_sticker_id = CASE WHEN v_n = 1 THEN v_sticker ELSE NULL END,
26554
+ status = CASE WHEN status = 'geblokkeerd' AND v_status = 'wacht_op_moment' THEN 'wacht_op_moment'
26555
+ WHEN status = 'niet_printen' THEN v_status
26556
+ WHEN status = 'wacht_op_moment' THEN v_status ELSE status END,
26557
+ reden = CASE WHEN status IN ('wacht_op_moment', 'geblokkeerd', 'niet_printen') THEN v_reden ELSE reden END,
26558
+ updated_at = now()
26559
+ WHERE soort = 'eigen_sticker' AND bron_id = v_bron
26560
+ AND status IN ('wacht_op_moment', 'geblokkeerd', 'niet_printen');
26561
+ IF NOT FOUND AND NOT EXISTS (SELECT 1 FROM public.logistics_sticker_prints WHERE soort = 'eigen_sticker' AND bron_id = v_bron) THEN
26562
+ INSERT INTO public.logistics_sticker_prints (soort, bron_id, order_id, order_number, kopieen, status, reden, eigen_sticker_id)
26563
+ VALUES ('eigen_sticker', v_bron, r.order_id, r.order_number, greatest(coalesce(r.planten, 0), 1), v_status, v_reden,
26564
+ CASE WHEN v_n = 1 THEN v_sticker ELSE NULL END);
26565
+ END IF;
26566
+ v_bijgewerkt := v_bijgewerkt + 1;
26567
+ END LOOP;
28638
26568
 
28639
- IF NOT COALESCE(_is_herprint, false) AND _cart_id IS NOT NULL THEN
28640
- v_sleutel := 'pakbon:' || item.id::text || ':' || _cart_id || ':'
28641
- || coalesce(target_layout::text, '-') || ':' || coalesce(target_printer::text, '-');
28642
- END IF;
26569
+ -- Vinkje uitgezet (of order vervallen) vóór het printen: niet printen.
26570
+ UPDATE public.logistics_sticker_prints p
26571
+ SET status = 'niet_printen', reden = 'Sticker uitgezet op de orderregel of order vervallen.', updated_at = now()
26572
+ WHERE p.soort = 'eigen_sticker' AND p.status IN ('wacht_op_moment', 'geblokkeerd', 'klaar')
26573
+ AND NOT EXISTS (
26574
+ SELECT 1 FROM public.order_items oi JOIN public.orders o ON o.id = oi.order_id
26575
+ WHERE oi.order_id = p.order_id AND p.bron_id = oi.order_id::text || ':' || oi.product_id::text
26576
+ AND oi.needs_sticker IS TRUE AND oi.quarantined_at IS NULL
26577
+ AND o.deleted_at IS NULL AND o.cancelled_at IS NULL AND coalesce(o.status, '') <> 'geannuleerd');
26578
+ GET DIAGNOSTICS v_uit = ROW_COUNT;
28643
26579
 
28644
- INSERT INTO public.print_jobs(
28645
- printer_id, layout_id, documenttype, documentnaam, status, foutmelding,
28646
- payload, gebruiker_naam, pdf_url, pdf_base64, herkomst, is_herprint, idempotentie_sleutel
28647
- )
28648
- VALUES (
28649
- target_printer,
28650
- target_layout,
28651
- doc_type,
28652
- COALESCE(item.document_number, 'Pickdocument'),
28653
- 'wachtend',
28654
- CASE
28655
- WHEN target_layout IS NULL AND pdf_url IS NULL AND pdf_b64 IS NULL
28656
- THEN COALESCE(layout_reden, 'Maak of activeer een ' || COALESCE(nullif(doc_code, ''), 'document') || 'layout in Layouts en printers.')
28657
- ELSE NULL
28658
- END,
28659
- jsonb_build_object(
28660
- 'source', 'pickstation',
28661
- 'queue_item_id', item.id,
28662
- 'station_id', item.station_id,
28663
- 'print_plek_id', target_plek,
28664
- 'session_id', _session_id,
28665
- 'is_herprint', COALESCE(_is_herprint, false),
28666
- 'document_number', item.document_number,
28667
- 'document_type_code', COALESCE(nullif(doc_code, ''), dt.code),
28668
- 'report_key', dt.report_key,
28669
- 'print_template', dt.print_template,
28670
- 'order_number', COALESCE(selected_cart->>'orderNumber', item.order_number),
28671
- 'customer_name', item.customer_name,
28672
- 'reference', item.reference,
28673
- 'layout_id', target_layout,
28674
- 'layout_keuze', layout_meta->>'keuze',
28675
- 'layout_variant', CASE WHEN v_is_partial THEN 'partial_order' ELSE 'full_cart' END,
28676
- 'barcode', CASE WHEN v_barcode_on THEN COALESCE(v_barcode, item.barcode) ELSE item.barcode END,
28677
- 'country_code', item.country_code,
28678
- 'cart_id', _cart_id,
28679
- 'cart', selected_cart,
28680
- 'data', COALESCE(item.payload, '{}'::jsonb) || jsonb_build_object(
28681
- 'print_plek_id', target_plek,
28682
- 'cart_id', _cart_id,
28683
- 'cart', selected_cart,
28684
- 'orderId', COALESCE(selected_cart->>'orderId', item.payload->>'orderId'),
28685
- 'orderNumber', COALESCE(selected_cart->>'orderNumber', item.order_number)
28686
- )
28687
- ),
28688
- 'Pickstation',
28689
- pdf_url,
28690
- pdf_b64,
28691
- herkomst,
28692
- COALESCE(_is_herprint, false),
28693
- v_sleutel
26580
+ -- Vrijgeven met de pakbon: kar en printplek van de geprinte pakbonrij, formaat uit sticker of layout.
26581
+ WITH vrij AS (
26582
+ SELECT p.id,
26583
+ (SELECT public.logistics_try_uuid(q.payload->>'cartId')
26584
+ FROM public.pick_queue q
26585
+ WHERE q.cancelled_at IS NULL AND q.print_job_id IS NOT NULL
26586
+ AND (q.payload->>'orderId' = p.order_id::text OR q.payload->'orderIds' ? p.order_id::text)
26587
+ ORDER BY q.created_at LIMIT 1) AS kar,
26588
+ (SELECT k.print_plek_id::text
26589
+ FROM public.pick_queue q
26590
+ JOIN public.print_plek_koppelingen k ON k.bron IN ('pickstation', 'pick_station') AND k.bron_id = q.station_id::text
26591
+ WHERE q.cancelled_at IS NULL AND q.print_job_id IS NOT NULL
26592
+ AND (q.payload->>'orderId' = p.order_id::text OR q.payload->'orderIds' ? p.order_id::text)
26593
+ ORDER BY q.created_at, k.created_at LIMIT 1) AS plek,
26594
+ s.width_mm, s.height_mm,
26595
+ public.logistics_document_for_order(p.order_id, 'eigen_sticker') AS lay
26596
+ FROM public.logistics_sticker_prints p
26597
+ LEFT JOIN public.customer_product_stickers s ON s.id = p.eigen_sticker_id
26598
+ WHERE p.soort = 'eigen_sticker' AND p.status = 'wacht_op_moment'
26599
+ AND EXISTS (SELECT 1 FROM public.pick_queue q
26600
+ WHERE q.cancelled_at IS NULL AND q.print_job_id IS NOT NULL
26601
+ AND (q.payload->>'orderId' = p.order_id::text OR q.payload->'orderIds' ? p.order_id::text))
28694
26602
  )
28695
- RETURNING id INTO job_id;
28696
-
28697
- PERFORM public.logistics_enqueue_sticker_with_slip(_queue_item_id, _printer_id, _session_id, COALESCE(_is_herprint,false), _cart_id, job_id);
28698
- UPDATE public.pick_queue SET print_job_id = job_id WHERE id = _queue_item_id;
26603
+ UPDATE public.logistics_sticker_prints p
26604
+ SET status = 'klaar', cart_id = vrij.kar, print_plek_id = vrij.plek,
26605
+ breedte_mm = coalesce(vrij.width_mm, (vrij.lay->>'breedte_mm')::numeric),
26606
+ hoogte_mm = coalesce(vrij.height_mm, (vrij.lay->>'hoogte_mm')::numeric),
26607
+ updated_at = now()
26608
+ FROM vrij
26609
+ WHERE p.id = vrij.id;
26610
+ GET DIAGNOSTICS v_vrij = ROW_COUNT;
28699
26611
 
28700
- RETURN job_id;
26612
+ RETURN jsonb_build_object('regels', v_bijgewerkt, 'vrijgegeven', v_vrij, 'niet_printen', v_uit);
28701
26613
  END;
28702
26614
  $function$;
28703
26615
 
26616
+ -- 6. Pakbon van het pickstation: alleen de centrale beslisregel (geen layout uit payload of werkwijze).
28704
26617
  REVOKE EXECUTE ON FUNCTION public.logistics_own_sticker_sync(integer) FROM PUBLIC, anon, authenticated;
28705
26618
  GRANT EXECUTE ON FUNCTION public.logistics_own_sticker_sync(integer) TO service_role;
28706
26619
  REVOKE EXECUTE ON FUNCTION public.logistics_document_for_order(uuid, text) FROM PUBLIC, anon;
@@ -29468,243 +27381,76 @@ BEGIN
29468
27381
  v_soort := v_ship->>'documenttype';
29469
27382
  v_extra := jsonb_build_object('verzenddocument_bepaald', v_soort,
29470
27383
  'fulfillment_order_id', v_ship->>'fulfillment_order_id');
29471
- ELSE
29472
- v_soort := 'afleverbrief';
29473
- v_extra := jsonb_build_object('verzenddocument_bepaald', 'afleverbrief');
29474
- END IF;
29475
- END IF;
29476
-
29477
- IF to_regprocedure('public.layout_voor_document(uuid,text)') IS NOT NULL THEN
29478
- EXECUTE 'SELECT to_jsonb(public.layout_voor_document($1, $2))' INTO v_res USING p_order_id, v_soort;
29479
- IF jsonb_typeof(v_res) IS DISTINCT FROM 'object' THEN
29480
- RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'layouts',
29481
- 'reden', 'Layouts gaf geen bruikbaar antwoord voor ' || v_soort || '.') || v_extra;
29482
- END IF;
29483
- RETURN v_res || jsonb_build_object('keuze', 'layouts',
29484
- 'ok', coalesce((v_res->>'ok')::boolean, nullif(v_res->>'layout_id', '') IS NOT NULL)) || v_extra;
29485
- END IF;
29486
- SELECT o.order_source = 'floriday' INTO v_floriday FROM public.orders o WHERE o.id = p_order_id;
29487
- IF v_floriday IS NULL THEN
29488
- SELECT EXISTS (SELECT 1 FROM public.orders WHERE id = p_order_id) INTO v_floriday;
29489
- IF NOT v_floriday THEN
29490
- RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'automatisch', 'reden', 'Order niet gevonden.');
29491
- END IF;
29492
- v_floriday := false;
29493
- END IF;
29494
- v_type := CASE
29495
- WHEN v_soort IN ('eigen_sticker', 'sticker', 'etiket') THEN 'sticker'
29496
- WHEN v_soort IN ('verzenddocument', 'verzendbrief', 'afleverbrief', 'afleverbon') THEN 'afleverbrief'
29497
- WHEN v_soort IN ('veilingbrief', 'veilingbon') THEN 'veilingbrief'
29498
- WHEN v_soort LIKE '%pakbon%' THEN 'pakbon'
29499
- WHEN v_soort LIKE '%productie%' THEN 'productiebon'
29500
- ELSE v_soort END;
29501
- v_bron := CASE
29502
- WHEN v_floriday AND v_soort <> 'eigen_sticker' AND v_type IN ('sticker', 'traysticker', 'afleverbrief', 'veilingbrief') THEN 'floriday'
29503
- ELSE 'eigen' END;
29504
- IF v_bron = 'eigen' AND v_type = 'veilingbrief' THEN
29505
- RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'automatisch',
29506
- 'reden', 'Een veilingbrief hoort alleen bij een Floriday-order; voor een eigen order wordt de afleverbrief gebruikt.');
29507
- END IF;
29508
- SELECT count(*), count(*) FILTER (WHERE is_standaard IS TRUE) INTO v_n, v_std
29509
- FROM public.document_layouts
29510
- WHERE actief IS TRUE AND coalesce(bron, 'eigen') = v_bron AND documenttype::text = v_type;
29511
- IF v_n >= 1 THEN
29512
- SELECT * INTO v_l FROM public.document_layouts
29513
- WHERE actief IS TRUE AND coalesce(bron, 'eigen') = v_bron AND documenttype::text = v_type
29514
- ORDER BY is_standaard DESC NULLS LAST, updated_at DESC LIMIT 1;
29515
- END IF;
29516
- IF v_l.id IS NULL THEN
29517
- RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'automatisch', 'bron', v_bron, 'documenttype', v_type,
29518
- 'reden', CASE WHEN v_bron = 'floriday'
29519
- THEN 'Geen actieve ' || v_type || '-layout met bron Floriday. Activeer de Floriday-systeemlayout in Layouts en printers.'
29520
- ELSE 'Maak of activeer een ' || v_type || 'layout in Layouts en printers.' END) || v_extra;
29521
- END IF;
29522
- RETURN jsonb_build_object('ok', true, 'layout_id', v_l.id, 'keuze', 'automatisch', 'bron', v_bron,
29523
- 'documenttype', v_type, 'breedte_mm', v_l.breedte_mm, 'hoogte_mm', v_l.hoogte_mm, 'reden', NULL) || v_extra;
29524
- END;
29525
- $function$;
29526
-
29527
-
29528
- -- ===== 1.0.178: fusten en land per kar, eigen traystickers =====
29529
- -- 1.0.178 A: fusten en land per kar in control_get_todays_carts (achteraan toegevoegd)
29530
- DO $m$ BEGIN
29531
- IF pg_get_function_result('public.control_get_todays_carts()'::regprocedure) NOT LIKE '%crates_on_cart%' THEN
29532
- DROP FUNCTION public.control_get_todays_carts();
29533
- END IF;
29534
- END $m$;
29535
-
29536
- CREATE OR REPLACE FUNCTION public.control_get_todays_carts()
29537
- RETURNS TABLE(cart_number_id uuid, cart_number text, order_source text, order_id text, cart_index integer, order_number text, pick_date date, status text, printed_at timestamp with time zone, printed_today boolean, crates_on_cart integer, country_code text)
29538
- LANGUAGE sql STABLE SECURITY DEFINER SET search_path TO 'public'
29539
- AS $function$
29540
- WITH vandaag AS (
29541
- SELECT (now() AT TIME ZONE public.logistics_tz())::date AS d
29542
- ),
29543
- idx AS (
29544
- SELECT DISTINCT ON (i.cart_id) i.*
29545
- FROM public.logistics_cart_index((SELECT d FROM vandaag), (SELECT d FROM vandaag)) i
29546
- ORDER BY i.cart_id, i.order_number
29547
- ),
29548
- gecontroleerd AS (
29549
- SELECT cci.cart_number_id, cci.order_source, cci.order_id, cci.cart_index
29550
- FROM public.cart_control_items cci
29551
- JOIN public.cart_control_sessions ccs ON ccs.id = cci.session_id
29552
- WHERE ccs.control_date = (SELECT d FROM vandaag)
29553
- AND cci.action = 'checked'
29554
- )
29555
- SELECT
29556
- i.cart_id,
29557
- COALESCE(cn.cart_number, i.cart_label),
29558
- i.order_source,
29559
- i.order_uuid::text,
29560
- i.cart_index,
29561
- i.order_number,
29562
- i.pick_date,
29563
- COALESCE(cn.status, 'pending'),
29564
- i.printed_at,
29565
- (i.printed_at >= (SELECT d FROM vandaag)::timestamptz),
29566
- (SELECT sum(ci.crates)::int FROM public.order_cart_items ci WHERE ci.cart_id = i.cart_id),
29567
- (SELECT CASE WHEN upper(btrim(l.country)) ~ '^[A-Z]{2}$' THEN upper(btrim(l.country)) END
29568
- FROM public.orders o
29569
- JOIN public.customer_delivery_locations l
29570
- ON l.id = public.logistics_try_uuid(o.custom_fields->>'delivery_location')
29571
- WHERE o.id = i.order_uuid
29572
- LIMIT 1)
29573
- FROM idx i
29574
- LEFT JOIN public.cart_numbers cn
29575
- ON cn.order_source = i.order_source
29576
- AND cn.order_id = i.order_uuid::text
29577
- AND cn.cart_index = i.cart_index
29578
- LEFT JOIN gecontroleerd g1 ON g1.cart_number_id = i.cart_id
29579
- LEFT JOIN gecontroleerd g2
29580
- ON g2.order_source = i.order_source
29581
- AND g2.order_id = i.order_uuid::text
29582
- AND g2.cart_index = i.cart_index
29583
- WHERE COALESCE(cn.status, 'pending') <> 'delivered'
29584
- AND g1.cart_number_id IS NULL
29585
- AND g2.order_id IS NULL
29586
- ORDER BY i.order_number, i.cart_index;
29587
- $function$;
29588
- REVOKE ALL ON FUNCTION public.control_get_todays_carts() FROM PUBLIC, anon;
29589
- GRANT EXECUTE ON FUNCTION public.control_get_todays_carts() TO authenticated, service_role;
29590
-
29591
- -- 1.0.178 B: eigen traystickers
29592
- ALTER TABLE public.logistics_sticker_prints DROP CONSTRAINT IF EXISTS logistics_sticker_prints_soort_check;
29593
- ALTER TABLE public.logistics_sticker_prints ADD CONSTRAINT logistics_sticker_prints_soort_check
29594
- CHECK (soort = ANY (ARRAY['sticker','traysticker','eigen_sticker','eigen_traysticker']));
29595
-
29596
- CREATE OR REPLACE FUNCTION public.logistics_own_traysticker_sync(p_limit integer DEFAULT 200)
29597
- RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public'
29598
- AS $function$
29599
- DECLARE
29600
- s jsonb; v_tray text; v_rel text; v_station text;
29601
- r record; v_bron text; v_keep text[] := '{}'; n int := 0; n_geblokkeerd int := 0; n_vrij int := 0; n_uit int := 0;
29602
- BEGIN
29603
- SELECT settings INTO s FROM public.logistics_rule_configs
29604
- ORDER BY is_active DESC, (status = 'actief') DESC, updated_at DESC LIMIT 1;
29605
- v_tray := public.logistics_rule_text(s, 'pickstations', 'traysticker_release', 'uit');
29606
- IF v_tray = 'uit' THEN
29607
- RETURN jsonb_build_object('trayRelease', v_tray, 'regels', 0);
27384
+ ELSE
27385
+ v_soort := 'afleverbrief';
27386
+ v_extra := jsonb_build_object('verzenddocument_bepaald', 'afleverbrief');
27387
+ END IF;
29608
27388
  END IF;
29609
- v_rel := public.logistics_rule_text(s, 'pickstations', 'sticker_release', 'with_pick_release');
29610
- v_station := CASE WHEN v_rel = 'with_packing_slip'
29611
- THEN public.logistics_rule_text(s, 'pickstations', 'packing_slip_station', '')
29612
- ELSE public.logistics_rule_text(s, 'pickstations', 'sticker_station', '') END;
29613
27389
 
29614
- FOR r IN
29615
- SELECT o.id AS order_id, o.order_number, ci.cart_id,
29616
- bool_or(ci.layer_index IS NULL) AS zonder_indeling,
29617
- array_agg(DISTINCT ci.layer_index) FILTER (WHERE ci.layer_index IS NOT NULL) AS lagen
29618
- FROM public.orders o
29619
- JOIN public.order_items oi ON oi.order_id = o.id
29620
- JOIN public.order_cart_items ci ON ci.order_item_id = oi.id
29621
- WHERE coalesce(o.order_source, '') <> 'floriday'
29622
- AND o.deleted_at IS NULL AND o.cancelled_at IS NULL
29623
- AND coalesce(o.status, '') <> 'geannuleerd'
29624
- AND oi.quarantined_at IS NULL
29625
- AND o.delivery_date >= current_date - 1
29626
- GROUP BY o.id, o.order_number, ci.cart_id
29627
- LIMIT greatest(1, p_limit)
29628
- LOOP
29629
- IF r.zonder_indeling OR r.lagen IS NULL THEN
29630
- v_bron := r.order_id::text || ':' || r.cart_id::text || ':geen-indeling';
29631
- v_keep := v_keep || v_bron;
29632
- INSERT INTO public.logistics_sticker_prints (soort, bron_id, order_id, order_number, cart_id, kopieen, status, reden)
29633
- VALUES ('eigen_traysticker', v_bron, r.order_id, r.order_number, r.cart_id, 1, 'geblokkeerd',
29634
- 'Trayindeling ontbreekt voor deze kar: niet elke orderregel heeft een tray (laag). Deel de kar opnieuw in; de rest van de order gaat door.')
29635
- ON CONFLICT DO NOTHING;
29636
- n_geblokkeerd := n_geblokkeerd + 1;
29637
- CONTINUE;
27390
+ IF to_regprocedure('public.layout_voor_document(uuid,text)') IS NOT NULL THEN
27391
+ EXECUTE 'SELECT to_jsonb(public.layout_voor_document($1, $2))' INTO v_res USING p_order_id, v_soort;
27392
+ IF jsonb_typeof(v_res) IS DISTINCT FROM 'object' THEN
27393
+ RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'layouts',
27394
+ 'reden', 'Layouts gaf geen bruikbaar antwoord voor ' || v_soort || '.') || v_extra;
29638
27395
  END IF;
29639
- FOR i IN 1 .. array_length(r.lagen, 1) LOOP
29640
- v_bron := r.order_id::text || ':' || r.cart_id::text || ':' || r.lagen[i]::text;
29641
- v_keep := v_keep || v_bron;
29642
- INSERT INTO public.logistics_sticker_prints (soort, bron_id, order_id, order_number, cart_id, kopieen, print_plek_id, status, reden)
29643
- VALUES ('eigen_traysticker', v_bron, r.order_id, r.order_number, r.cart_id, 1,
29644
- public.logistics_sticker_cart_plek(r.cart_id, v_station), 'wacht_op_moment', 'Wacht op de pakbon van deze kar.')
29645
- ON CONFLICT DO NOTHING;
29646
- n := n + 1;
29647
- END LOOP;
29648
- END LOOP;
27396
+ RETURN v_res || jsonb_build_object('keuze', 'layouts',
27397
+ 'ok', coalesce((v_res->>'ok')::boolean, nullif(v_res->>'layout_id', '') IS NOT NULL)) || v_extra;
27398
+ END IF;
27399
+ SELECT o.order_source = 'floriday' INTO v_floriday FROM public.orders o WHERE o.id = p_order_id;
27400
+ IF v_floriday IS NULL THEN
27401
+ SELECT EXISTS (SELECT 1 FROM public.orders WHERE id = p_order_id) INTO v_floriday;
27402
+ IF NOT v_floriday THEN
27403
+ RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'automatisch', 'reden', 'Order niet gevonden.');
27404
+ END IF;
27405
+ v_floriday := false;
27406
+ END IF;
27407
+ v_type := CASE
27408
+ WHEN v_soort IN ('eigen_sticker', 'sticker', 'etiket') THEN 'sticker'
27409
+ WHEN v_soort IN ('verzenddocument', 'verzendbrief', 'afleverbrief', 'afleverbon') THEN 'afleverbrief'
27410
+ WHEN v_soort IN ('veilingbrief', 'veilingbon') THEN 'veilingbrief'
27411
+ WHEN v_soort LIKE '%pakbon%' THEN 'pakbon'
27412
+ WHEN v_soort LIKE '%productie%' THEN 'productiebon'
27413
+ ELSE v_soort END;
27414
+ v_bron := CASE
27415
+ WHEN v_floriday AND v_soort <> 'eigen_sticker' AND v_type IN ('sticker', 'traysticker', 'afleverbrief', 'veilingbrief') THEN 'floriday'
27416
+ ELSE 'eigen' END;
27417
+ IF v_bron = 'eigen' AND v_type = 'veilingbrief' THEN
27418
+ RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'automatisch',
27419
+ 'reden', 'Een veilingbrief hoort alleen bij een Floriday-order; voor een eigen order wordt de afleverbrief gebruikt.');
27420
+ END IF;
27421
+ SELECT count(*), count(*) FILTER (WHERE is_standaard IS TRUE) INTO v_n, v_std
27422
+ FROM public.document_layouts
27423
+ WHERE actief IS TRUE AND coalesce(bron, 'eigen') = v_bron AND documenttype::text = v_type;
27424
+ IF v_n >= 1 THEN
27425
+ SELECT * INTO v_l FROM public.document_layouts
27426
+ WHERE actief IS TRUE AND coalesce(bron, 'eigen') = v_bron AND documenttype::text = v_type
27427
+ ORDER BY is_standaard DESC NULLS LAST, updated_at DESC LIMIT 1;
27428
+ END IF;
27429
+ IF v_l.id IS NULL THEN
27430
+ RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'automatisch', 'bron', v_bron, 'documenttype', v_type,
27431
+ 'reden', CASE WHEN v_bron = 'floriday'
27432
+ THEN 'Geen actieve ' || v_type || '-layout met bron Floriday. Activeer de Floriday-systeemlayout in Layouts en printers.'
27433
+ ELSE 'Maak of activeer een ' || v_type || 'layout in Layouts en printers.' END) || v_extra;
27434
+ END IF;
27435
+ RETURN jsonb_build_object('ok', true, 'layout_id', v_l.id, 'keuze', 'automatisch', 'bron', v_bron,
27436
+ 'documenttype', v_type, 'breedte_mm', v_l.breedte_mm, 'hoogte_mm', v_l.hoogte_mm, 'reden', NULL) || v_extra;
27437
+ END;
27438
+ $function$;
29649
27439
 
29650
- -- Indeling hersteld of tray vervallen: niet meer printen (alleen nog niet geprinte rijen).
29651
- UPDATE public.logistics_sticker_prints p
29652
- SET status = 'niet_printen', reden = 'Tray hoort niet meer bij deze kar of order vervallen.', updated_at = now()
29653
- WHERE p.soort = 'eigen_traysticker' AND p.status IN ('wacht_op_moment', 'geblokkeerd', 'klaar')
29654
- AND NOT (p.bron_id = ANY (v_keep))
29655
- AND p.order_id IN (SELECT o.id FROM public.orders o WHERE o.delivery_date >= current_date - 1);
29656
- GET DIAGNOSTICS n_uit = ROW_COUNT;
29657
27440
 
29658
- -- Vrijgave: met de pakbon van de kar (zoals eigen stickers).
29659
- UPDATE public.logistics_sticker_prints p
29660
- SET status = 'klaar', reden = NULL,
29661
- print_plek_id = coalesce(p.print_plek_id, public.logistics_sticker_cart_plek(p.cart_id, v_station)),
29662
- updated_at = now()
29663
- WHERE p.soort = 'eigen_traysticker' AND p.status = 'wacht_op_moment'
29664
- AND p.cart_id IS NOT NULL AND public.logistics_cart_slip_queued(p.cart_id);
29665
- GET DIAGNOSTICS n_vrij = ROW_COUNT;
27441
+ -- ===== 1.0.178: fusten en land per kar, eigen traystickers =====
27442
+ -- 1.0.178 A: fusten en land per kar in control_get_todays_carts (achteraan toegevoegd)
27443
+ DO $m$ BEGIN
27444
+ IF pg_get_function_result('public.control_get_todays_carts()'::regprocedure) NOT LIKE '%crates_on_cart%' THEN
27445
+ DROP FUNCTION public.control_get_todays_carts();
27446
+ END IF;
27447
+ END $m$;
29666
27448
 
29667
- RETURN jsonb_build_object('trayRelease', v_tray, 'trays', n, 'geblokkeerd', n_geblokkeerd, 'vrijgegeven', n_vrij, 'niet_printen', n_uit);
29668
- END; $function$;
29669
- REVOKE ALL ON FUNCTION public.logistics_own_traysticker_sync(integer) FROM PUBLIC, anon, authenticated;
29670
- GRANT EXECUTE ON FUNCTION public.logistics_own_traysticker_sync(integer) TO service_role;
27449
+ -- 1.0.178 B: eigen traystickers
27450
+ ALTER TABLE public.logistics_sticker_prints DROP CONSTRAINT IF EXISTS logistics_sticker_prints_soort_check;
27451
+ ALTER TABLE public.logistics_sticker_prints ADD CONSTRAINT logistics_sticker_prints_soort_check
27452
+ CHECK (soort = ANY (ARRAY['sticker','traysticker','eigen_sticker','eigen_traysticker']));
29671
27453
 
29672
- CREATE OR REPLACE FUNCTION public.logistics_own_traysticker_payload(p_id uuid)
29673
- RETURNS jsonb LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public'
29674
- AS $function$
29675
- DECLARE p record; o record; v_laag int; v_nr int; v_tot int; v_loc text; v_prod jsonb;
29676
- BEGIN
29677
- SELECT * INTO p FROM public.logistics_sticker_prints WHERE id = p_id AND soort = 'eigen_traysticker';
29678
- IF p.id IS NULL THEN RETURN jsonb_build_object('ok', false, 'reden', 'Traysticker niet gevonden.'); END IF;
29679
- v_laag := nullif(split_part(p.bron_id, ':', 3), '')::int;
29680
- SELECT * INTO o FROM public.orders WHERE id = p.order_id;
29681
- SELECT count(DISTINCT ci.layer_index),
29682
- count(DISTINCT ci.layer_index) FILTER (WHERE ci.layer_index <= v_laag)
29683
- INTO v_tot, v_nr
29684
- FROM public.order_cart_items ci JOIN public.order_items oi ON oi.id = ci.order_item_id
29685
- WHERE oi.order_id = p.order_id AND ci.cart_id = p.cart_id AND ci.layer_index IS NOT NULL;
29686
- SELECT coalesce(jsonb_agg(jsonb_build_object(
29687
- 'artikelnummer', prd.article_number,
29688
- 'naam', coalesce(ci.product_name, oi.product_name),
29689
- 'aantal', coalesce(ci.crates_in_layer, ci.crates)) ORDER BY coalesce(ci.product_name, oi.product_name)), '[]'::jsonb)
29690
- INTO v_prod
29691
- FROM public.order_cart_items ci JOIN public.order_items oi ON oi.id = ci.order_item_id
29692
- LEFT JOIN public.products prd ON prd.id = coalesce(ci.product_id, oi.product_id)
29693
- WHERE oi.order_id = p.order_id AND ci.cart_id = p.cart_id AND ci.layer_index = v_laag;
29694
- SELECT nullif(btrim(concat_ws(', ', nullif(btrim(l.name), ''), nullif(btrim(l.city), ''), nullif(btrim(l.country), ''))), '')
29695
- INTO v_loc
29696
- FROM public.customer_delivery_locations l
29697
- WHERE l.id = public.logistics_try_uuid(o.custom_fields->>'delivery_location');
29698
- IF v_tot IS NULL OR v_tot = 0 OR jsonb_array_length(v_prod) = 0 THEN
29699
- RETURN jsonb_build_object('ok', false, 'reden', 'Trayindeling ontbreekt voor deze tray. Deel de kar opnieuw in.');
29700
- END IF;
29701
- RETURN jsonb_build_object('ok', true, 'payload', jsonb_build_object(
29702
- 'order_id', o.id, 'order_number', o.order_number, 'ordernummer', o.order_number,
29703
- 'klantnaam', o.customer_name, 'leverdatum', o.delivery_date, 'afleverlocatie', v_loc,
29704
- 'cart_id', p.cart_id, 'tray_nummer', v_nr, 'tray_totaal', v_tot, 'tray_producten', v_prod));
29705
- END; $function$;
29706
- REVOKE ALL ON FUNCTION public.logistics_own_traysticker_payload(uuid) FROM PUBLIC, anon, authenticated;
29707
- GRANT EXECUTE ON FUNCTION public.logistics_own_traysticker_payload(uuid) TO service_role;
29708
27454
  -- ===== 1.0.178 correctie: eigen traysticker per fust =====
29709
27455
  -- 1.0.178 correctie: eigen traysticker per FUST (niet per laag).
29710
27456
  -- Sleutel: order:kar:karregel:fustnummer. Crates onbekend: één geblokkeerde rij per karregel.
@@ -29719,79 +27465,6 @@ RETURNS integer LANGUAGE sql IMMUTABLE SET search_path = public AS $tp$
29719
27465
  END
29720
27466
  $tp$;
29721
27467
 
29722
- CREATE OR REPLACE FUNCTION public.logistics_own_traysticker_sync(p_limit integer DEFAULT 200)
29723
- RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public'
29724
- AS $function$
29725
- DECLARE
29726
- s jsonb; v_tray text; v_rel text; v_station text;
29727
- r record; v_bron text; v_keep text[] := '{}'; n int := 0; n_geblokkeerd int := 0; n_vrij int := 0; n_uit int := 0;
29728
- BEGIN
29729
- SELECT settings INTO s FROM public.logistics_rule_configs
29730
- ORDER BY is_active DESC, (status = 'actief') DESC, updated_at DESC LIMIT 1;
29731
- v_tray := public.logistics_rule_text(s, 'pickstations', 'traysticker_release', 'uit');
29732
- IF v_tray = 'uit' THEN
29733
- RETURN jsonb_build_object('trayRelease', v_tray, 'regels', 0);
29734
- END IF;
29735
- v_rel := public.logistics_rule_text(s, 'pickstations', 'sticker_release', 'with_pick_release');
29736
- v_station := CASE WHEN v_rel = 'with_packing_slip'
29737
- THEN public.logistics_rule_text(s, 'pickstations', 'packing_slip_station', '')
29738
- ELSE public.logistics_rule_text(s, 'pickstations', 'sticker_station', '') END;
29739
-
29740
- FOR r IN
29741
- SELECT o.id AS order_id, o.order_number, ci.cart_id, ci.id AS ci_id, ci.crates
29742
- FROM public.orders o
29743
- JOIN public.order_items oi ON oi.order_id = o.id
29744
- JOIN public.order_cart_items ci ON ci.order_item_id = oi.id
29745
- WHERE coalesce(o.order_source, '') <> 'floriday'
29746
- AND o.deleted_at IS NULL AND o.cancelled_at IS NULL
29747
- AND coalesce(o.status, '') <> 'geannuleerd'
29748
- AND oi.quarantined_at IS NULL
29749
- AND o.delivery_date >= current_date - 1
29750
- ORDER BY o.order_number, ci.cart_id, ci.created_at, ci.id
29751
- LIMIT greatest(1, p_limit)
29752
- LOOP
29753
- IF r.crates IS NULL OR r.crates <= 0 THEN
29754
- v_bron := r.order_id::text || ':' || r.cart_id::text || ':' || r.ci_id::text || ':onbekend';
29755
- v_keep := v_keep || v_bron;
29756
- INSERT INTO public.logistics_sticker_prints (soort, bron_id, order_id, order_number, cart_id, kopieen, status, reden)
29757
- VALUES ('eigen_traysticker', v_bron, r.order_id, r.order_number, r.cart_id, 1, 'geblokkeerd',
29758
- 'Aantal fusten onbekend voor deze regel op de kar. De rest van de order gaat door.')
29759
- ON CONFLICT DO NOTHING;
29760
- n_geblokkeerd := n_geblokkeerd + 1;
29761
- CONTINUE;
29762
- END IF;
29763
- FOR k IN 1 .. r.crates LOOP
29764
- v_bron := r.order_id::text || ':' || r.cart_id::text || ':' || r.ci_id::text || ':' || k::text;
29765
- v_keep := v_keep || v_bron;
29766
- INSERT INTO public.logistics_sticker_prints (soort, bron_id, order_id, order_number, cart_id, kopieen, print_plek_id, status, reden)
29767
- VALUES ('eigen_traysticker', v_bron, r.order_id, r.order_number, r.cart_id, 1,
29768
- public.logistics_sticker_cart_plek(r.cart_id, v_station), 'wacht_op_moment', 'Wacht op de pakbon van deze kar.')
29769
- ON CONFLICT DO NOTHING;
29770
- n := n + 1;
29771
- END LOOP;
29772
- END LOOP;
29773
-
29774
- -- Oude laagsleutels, vervallen fusten of orders: niet meer printen (alleen nog niet geprinte rijen, geen DELETE).
29775
- UPDATE public.logistics_sticker_prints p
29776
- SET status = 'niet_printen', reden = 'Fust hoort niet meer bij deze kar of order vervallen.', updated_at = now()
29777
- WHERE p.soort = 'eigen_traysticker' AND p.status IN ('wacht_op_moment', 'geblokkeerd', 'klaar')
29778
- AND NOT (p.bron_id = ANY (v_keep))
29779
- AND p.order_id IN (SELECT o.id FROM public.orders o WHERE o.delivery_date >= current_date - 1);
29780
- GET DIAGNOSTICS n_uit = ROW_COUNT;
29781
-
29782
- UPDATE public.logistics_sticker_prints p
29783
- SET status = 'klaar', reden = NULL,
29784
- print_plek_id = coalesce(p.print_plek_id, public.logistics_sticker_cart_plek(p.cart_id, v_station)),
29785
- updated_at = now()
29786
- WHERE p.soort = 'eigen_traysticker' AND p.status = 'wacht_op_moment'
29787
- AND p.cart_id IS NOT NULL AND public.logistics_cart_slip_queued(p.cart_id);
29788
- GET DIAGNOSTICS n_vrij = ROW_COUNT;
29789
-
29790
- RETURN jsonb_build_object('trayRelease', v_tray, 'fusten', n, 'geblokkeerd', n_geblokkeerd, 'vrijgegeven', n_vrij, 'niet_printen', n_uit);
29791
- END; $function$;
29792
- REVOKE ALL ON FUNCTION public.logistics_own_traysticker_sync(integer) FROM PUBLIC, anon, authenticated;
29793
- GRANT EXECUTE ON FUNCTION public.logistics_own_traysticker_sync(integer) TO service_role;
29794
-
29795
27468
  CREATE OR REPLACE FUNCTION public.logistics_own_traysticker_payload(p_id uuid)
29796
27469
  RETURNS jsonb LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public'
29797
27470
  AS $function$
@@ -31388,30 +29061,6 @@ BEGIN
31388
29061
  END $function$;
31389
29062
 
31390
29063
  -- Handmatig kiezen: de enige manier. NULL = terug naar automatisch. Verstuurt nooit een mail.
31391
- CREATE OR REPLACE FUNCTION public.logistics_set_order_carrier(p_order_id uuid, p_carrier_id uuid)
31392
- RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public'
31393
- AS $function$
31394
- DECLARE v_uid uuid := auth.uid(); o jsonb; v_skip text; r jsonb;
31395
- BEGIN
31396
- IF v_uid IS NULL AND coalesce(auth.role(), '') <> 'service_role' THEN RAISE EXCEPTION 'Niet ingelogd'; END IF;
31397
- SELECT to_jsonb(x) INTO o FROM public.orders x WHERE x.id = p_order_id AND x.deleted_at IS NULL FOR UPDATE;
31398
- IF o IS NULL THEN RAISE EXCEPTION 'Order niet gevonden.'; END IF;
31399
- IF p_carrier_id IS NOT NULL THEN
31400
- v_skip := public.logistics_carrier_skip_reason(p_carrier_id);
31401
- IF v_skip IS NOT NULL THEN RAISE EXCEPTION 'Deze vervoerder kan niet gekozen worden: %.', v_skip; END IF;
31402
- r := public.logistics_carrier_result(p_carrier_id, 'handmatig', 'Handmatig gekozen op de order.', '{}'::text[]);
31403
- ELSE
31404
- r := public.logistics_resolve_carrier_core(o, true);
31405
- END IF;
31406
- PERFORM set_config('logistics.carrier_writer', 'engine', true);
31407
- UPDATE public.orders
31408
- SET carrier_id = public.logistics_try_uuid(r->>'carrier_id'), carrier_source = r->>'bron',
31409
- carrier_reason = r->>'reden', carrier_resolved_at = now(), carrier_set_by = v_uid, carrier_set_at = now()
31410
- WHERE id = p_order_id;
31411
- PERFORM set_config('logistics.carrier_writer', '', true);
31412
- RETURN r || jsonb_build_object('bevroren', public.logistics_carrier_frozen_reason(p_order_id));
31413
- END $function$;
31414
-
31415
29064
  -- Lijst F: wat de regel per bestaande open order zou opslaan (alleen lezen).
31416
29065
  CREATE OR REPLACE FUNCTION public.logistics_carrier_preview_existing(p_limit integer DEFAULT 1000)
31417
29066
  RETURNS TABLE(order_id uuid, order_number text, order_source text, delivery_date text, huidig_custom_carrier text,
@@ -31918,10 +29567,21 @@ AS $function$
31918
29567
  OR session_user::text <> 'authenticator'
31919
29568
  $function$;
31920
29569
 
29570
+ CREATE OR REPLACE FUNCTION public.logistics_acc_entry()
29571
+ RETURNS text LANGUAGE sql STABLE SET search_path TO 'public'
29572
+ AS $function$
29573
+ SELECT nullif(current_setting('request.path', true), '')
29574
+ $function$;
29575
+ REVOKE ALL ON FUNCTION public.logistics_acc_entry() FROM PUBLIC, anon;
29576
+ GRANT EXECUTE ON FUNCTION public.logistics_acc_entry() TO authenticated, service_role;
29577
+ COMMENT ON FUNCTION public.logistics_acc_entry() IS 'Logistiek Engine: het API-adres van het lopende verzoek (request.path), voor de ketenherkenning.';
29578
+
31921
29579
  CREATE OR REPLACE FUNCTION public.logistics_acc_direct(p_fn text)
31922
29580
  RETURNS boolean LANGUAGE sql STABLE SET search_path TO 'public'
31923
29581
  AS $function$
31924
- SELECT coalesce(current_query(), '') ~* ('(^|[^a-z0-9_])' || p_fn || '([^a-z0-9_]|$)')
29582
+ -- Rechtstreeks = het API-adres van dit verzoek is deze functie zelf, of onbekend (fail closed).
29583
+ -- Een ander /rpc/-adres: aangeroepen vanuit een functie die de API mocht uitvoeren (keten).
29584
+ SELECT coalesce(lower(public.logistics_acc_entry()) = '/rpc/' || lower(p_fn), true)
31925
29585
  $function$;
31926
29586
 
31927
29587
  CREATE OR REPLACE FUNCTION public.logistics_acc_guard(p_level text, p_fn text)
@@ -32099,4 +29759,60 @@ BEGIN
32099
29759
  v_def := regexp_replace(v_def, 'END \$function\$\s*$', E' RETURN QUERY SELECT * FROM public.logistics_access_check();\nEND $function$');
32100
29760
  EXECUTE v_def;
32101
29761
  END IF;
32102
- END $ic$;
29762
+ END $ic$;
29763
+
29764
+ -- 1.0.186: pickschermlijst verwerkt. Alleen de 16 functies uit tabel C blijven open;
29765
+ -- de rest krijgt het niveau 'ingelogd' met ketenherkenning via request.path.
29766
+ DO $lv$
29767
+ DECLARE v_def text;
29768
+ BEGIN
29769
+ v_def := pg_get_functiondef('public.logistics_acc_levels()'::regprocedure);
29770
+ IF position('wacht op pickschermlijst' IN v_def) > 0 AND position('SELECT * FROM (' IN v_def) > 0 THEN
29771
+ v_def := replace(v_def, 'SELECT * FROM (',
29772
+ 'SELECT v.functie, CASE WHEN v.niveau = ''wacht op pickschermlijst'' THEN CASE WHEN v.functie = ANY (ARRAY[''logistics_auto_send_status'',''logistics_barcode_lookup'',''logistics_cart_plan_status'',''logistics_cart_slip_plan'',''logistics_cart_sticker_state'',''logistics_complete_task'',''logistics_document_for_order'',''logistics_is_pick_workday'',''logistics_order_released'',''logistics_order_sticker_meldingen'',''logistics_pick_mode'',''logistics_scan_resolve'',''logistics_shipping_note_from_barcode'',''logistics_shipping_note_status'',''logistics_working_hours_status'',''pick_queue_cart_plan'']) THEN ''open (pickscherm/scanner zonder inlog)'' ELSE ''ingelogd'' END ELSE v.niveau END AS niveau FROM (');
29773
+ v_def := replace(v_def, ') v(functie, niveau)', ') v(functie, niveau)');
29774
+ EXECUTE v_def;
29775
+ END IF;
29776
+ END $lv$;
29777
+
29778
+ DO $acc$
29779
+ DECLARE r record; v_def text; v_new text; v_pos int; v_fail text[] := '{}'::text[]; n int := 0;
29780
+ BEGIN
29781
+ FOR r IN
29782
+ SELECT p.oid, p.proname, l.lanname, p.prosrc, x.lvl
29783
+ FROM public.logistics_acc_levels() x(fn, lvl)
29784
+ JOIN pg_proc p ON p.proname = x.fn AND p.pronamespace = 'public'::regnamespace
29785
+ JOIN pg_language l ON l.oid = p.prolang
29786
+ WHERE x.lvl IN ('server', 'ingelogd', 'beheer')
29787
+ AND p.prorettype <> 'trigger'::regtype AND coalesce(p.prosrc, '') <> ''
29788
+ AND p.prosrc NOT LIKE '%logistics_acc_guard(%'
29789
+ AND (p.proname <> 'pick_enqueue_print_job'
29790
+ OR pg_get_function_identity_arguments(p.oid) IN (
29791
+ '_queue_item_id uuid, _printer_id uuid, _session_id text, _is_herprint boolean',
29792
+ '_queue_item_id uuid, _printer_id uuid, _session_id text, _is_herprint boolean, _cart_id text'))
29793
+ LOOP
29794
+ BEGIN
29795
+ v_def := pg_get_functiondef(r.oid);
29796
+ IF r.lanname = 'plpgsql' THEN
29797
+ v_pos := regexp_instr(r.prosrc, '\mBEGIN\M', 1, 1, 1, 'i');
29798
+ IF v_pos = 0 THEN RAISE EXCEPTION 'geen BEGIN gevonden'; END IF;
29799
+ v_new := left(r.prosrc, v_pos - 1) || E'\n PERFORM public.logistics_acc_guard(' || quote_literal(r.lvl) || ', '
29800
+ || quote_literal(r.proname) || ');' || substr(r.prosrc, v_pos);
29801
+ ELSIF r.lanname = 'sql' THEN
29802
+ v_new := E'\n SELECT public.logistics_acc_guard(' || quote_literal(r.lvl) || ', ' || quote_literal(r.proname) || ');' || r.prosrc;
29803
+ ELSE
29804
+ RAISE EXCEPTION 'taal % niet ondersteund', r.lanname;
29805
+ END IF;
29806
+ v_pos := position(r.prosrc IN v_def);
29807
+ IF v_pos = 0 THEN RAISE EXCEPTION 'definitie niet te herschrijven'; END IF;
29808
+ v_def := overlay(v_def placing v_new from v_pos for length(r.prosrc));
29809
+ EXECUTE v_def;
29810
+ n := n + 1;
29811
+ EXCEPTION WHEN OTHERS THEN
29812
+ v_fail := v_fail || (r.proname || ': ' || SQLERRM);
29813
+ END;
29814
+ END LOOP;
29815
+ RAISE NOTICE 'pickschermlijst: % functies voorzien; mislukt: %', n, array_to_string(v_fail, ' | ');
29816
+ END $acc$;
29817
+
29818
+ ALTER FUNCTION public.logistics_cart_barcode_value(uuid) SET search_path TO 'public';