@flowselections/logistiek-engine 1.0.174 → 1.0.177

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/db/schema/logistiek-engine.sql +1262 -0
  2. package/dist-lib/components/BeheerPage.d.ts.map +1 -1
  3. package/dist-lib/components/BeheerPage.js +2 -1
  4. package/dist-lib/components/DocumentkeuzeCard.d.ts +2 -0
  5. package/dist-lib/components/DocumentkeuzeCard.d.ts.map +1 -0
  6. package/dist-lib/components/DocumentkeuzeCard.js +18 -0
  7. package/dist-lib/index.d.ts +1 -0
  8. package/dist-lib/index.d.ts.map +1 -1
  9. package/dist-lib/index.js +1 -0
  10. package/dist-lib/integrations/supabase/types.d.ts +239 -15
  11. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  12. package/dist-lib/lib/controlScan.server.js +1 -1
  13. package/dist-lib/lib/layoutPrint.server.d.ts +10 -1
  14. package/dist-lib/lib/layoutPrint.server.d.ts.map +1 -1
  15. package/dist-lib/lib/layoutPrint.server.js +19 -1
  16. package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
  17. package/dist-lib/lib/modulePrompts.js +157 -0
  18. package/dist-lib/lib/moduleVersion.d.ts +1 -1
  19. package/dist-lib/lib/moduleVersion.js +1 -1
  20. package/dist-lib/lib/ruleEngineSchema.d.ts.map +1 -1
  21. package/dist-lib/lib/ruleEngineSchema.js +4 -38
  22. package/dist-lib/lib/ruleEngineWizard.d.ts.map +1 -1
  23. package/dist-lib/lib/ruleEngineWizard.js +0 -2
  24. package/dist-lib/lib/shippingNoteQueue.server.js +1 -1
  25. package/dist-lib/lib/stickerQueue.server.d.ts +25 -0
  26. package/dist-lib/lib/stickerQueue.server.d.ts.map +1 -1
  27. package/dist-lib/lib/stickerQueue.server.js +62 -22
  28. package/dist-lib/lib/stickerRules.d.ts +17 -0
  29. package/dist-lib/lib/stickerRules.d.ts.map +1 -1
  30. package/dist-lib/lib/stickerRules.js +30 -0
  31. package/package.json +1 -1
@@ -28261,3 +28261,1265 @@ ON CONFLICT (key) DO NOTHING;
28261
28261
 
28262
28262
  RESET check_function_bodies;
28263
28263
 
28264
+
28265
+ -- ===== 1.0.176 automatische documentkeuze (idempotent: CREATE OR REPLACE / IF NOT EXISTS) =====
28266
+ -- Logistiek Engine 1.0.176: automatische documentkeuze.
28267
+ -- De gebruiker kiest nergens meer een layout. Eén centrale beslisregel:
28268
+ -- logistics_document_for_order(order, documenttype). Bestaat Layouts'
28269
+ -- layout_voor_document, dan alleen die; anders de automatische standaardlayout.
28270
+ -- Oude werkwijze-keuzes (packing_slip_layout e.d.) blijven staan maar worden genegeerd.
28271
+ -- Geen omgevingswaarden, geen datacorrectie.
28272
+
28273
+ -- 1. Eigen stickers in dezelfde wachtrij als de Floriday-stickers.
28274
+ ALTER TABLE public.logistics_sticker_prints ADD COLUMN IF NOT EXISTS eigen_sticker_id uuid;
28275
+ ALTER TABLE public.logistics_sticker_prints DROP CONSTRAINT IF EXISTS logistics_sticker_prints_soort_check;
28276
+ ALTER TABLE public.logistics_sticker_prints ADD CONSTRAINT logistics_sticker_prints_soort_check
28277
+ CHECK (soort = ANY (ARRAY['sticker'::text, 'traysticker'::text, 'eigen_sticker'::text]));
28278
+
28279
+ -- 2. Automatische keuze zonder werkwijze-overrides.
28280
+ CREATE OR REPLACE FUNCTION public.logistics_document_layout_v2(_document_code text, _is_partial boolean DEFAULT false)
28281
+ RETURNS jsonb
28282
+ LANGUAGE plpgsql
28283
+ STABLE SECURITY DEFINER
28284
+ SET search_path TO 'public'
28285
+ AS $function$
28286
+ DECLARE
28287
+ v_types public.doc_layout_type[];
28288
+ v_layout public.document_layouts%ROWTYPE;
28289
+ BEGIN
28290
+ v_types := public.logistics_document_type_map(lower(btrim(coalesce(_document_code, ''))));
28291
+ IF array_length(v_types, 1) IS NULL THEN RETURN NULL; END IF;
28292
+ SELECT * INTO v_layout
28293
+ FROM public.document_layouts
28294
+ WHERE actief IS TRUE AND documenttype = ANY (v_types)
28295
+ ORDER BY array_position(v_types, documenttype),
28296
+ (coalesce(bron, 'eigen') = 'eigen') DESC,
28297
+ is_standaard DESC NULLS LAST, updated_at DESC
28298
+ LIMIT 1;
28299
+ IF v_layout.id IS NULL THEN RETURN NULL; END IF;
28300
+ RETURN jsonb_build_object(
28301
+ 'layoutId', v_layout.id, 'layoutName', v_layout.naam, 'layoutVersion', v_layout.huidige_versie,
28302
+ 'layoutDocumentType', v_layout.documenttype::text, 'layoutSettingKey', NULL,
28303
+ 'layoutVariant', CASE WHEN COALESCE(_is_partial, false) THEN 'partial_order' ELSE 'full_cart' END,
28304
+ 'layoutFallbackUsed', false,
28305
+ 'layout_id', v_layout.id, 'layout_name', v_layout.naam, 'layout_version', v_layout.huidige_versie,
28306
+ 'layout_document_type', v_layout.documenttype::text, 'layout_setting_key', NULL,
28307
+ 'layout_variant', CASE WHEN COALESCE(_is_partial, false) THEN 'partial_order' ELSE 'full_cart' END,
28308
+ 'layout_fallback_used', false);
28309
+ END;
28310
+ $function$;
28311
+
28312
+ -- 3. Is Layouts al bijgewerkt?
28313
+ CREATE OR REPLACE FUNCTION public.logistics_document_selection_status()
28314
+ RETURNS jsonb
28315
+ LANGUAGE sql
28316
+ STABLE SECURITY DEFINER
28317
+ SET search_path TO 'public'
28318
+ AS $function$
28319
+ SELECT CASE WHEN to_regprocedure('public.layout_voor_document(uuid,text)') IS NOT NULL
28320
+ THEN jsonb_build_object('layouts_bijgewerkt', true, 'melding', 'Het document wordt automatisch bepaald uit de order (Layouts).')
28321
+ ELSE jsonb_build_object('layouts_bijgewerkt', false, 'melding', 'Layouts is nog niet bijgewerkt – automatische keuze gebruikt')
28322
+ END;
28323
+ $function$;
28324
+
28325
+ -- 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
+ -- 5. Eigen stickers (HO/MO/AO): één per klant+product, aantal = planten van regels met needs_sticker.
28399
+ CREATE OR REPLACE FUNCTION public.logistics_own_sticker_count(p_unit text, p_quantity numeric, p_ppc_regel numeric, p_ppc_product numeric)
28400
+ RETURNS integer LANGUAGE sql IMMUTABLE SET search_path = public AS $cnt$
28401
+ -- stuks/planten = aantal planten; fust = aantal x planten per fust (regel, anders product); onbekend = NULL (tegenhouden).
28402
+ SELECT CASE
28403
+ WHEN p_quantity IS NULL THEN NULL
28404
+ WHEN lower(coalesce(p_unit, '')) = 'fust' THEN
28405
+ CASE WHEN coalesce(nullif(p_ppc_regel, 0), nullif(p_ppc_product, 0)) IS NULL THEN NULL
28406
+ ELSE (p_quantity * coalesce(nullif(p_ppc_regel, 0), nullif(p_ppc_product, 0)))::int END
28407
+ ELSE p_quantity::int
28408
+ END
28409
+ $cnt$;
28410
+
28411
+ CREATE OR REPLACE FUNCTION public.logistics_own_sticker_sync(p_limit integer DEFAULT 200)
28412
+ RETURNS jsonb
28413
+ LANGUAGE plpgsql
28414
+ SECURITY DEFINER
28415
+ SET search_path TO 'public'
28416
+ AS $function$
28417
+ DECLARE
28418
+ r record;
28419
+ v_bron text;
28420
+ v_n int;
28421
+ v_sticker uuid;
28422
+ v_status text;
28423
+ v_reden text;
28424
+ v_bijgewerkt int := 0;
28425
+ v_vrij int := 0;
28426
+ v_uit int := 0;
28427
+ BEGIN
28428
+ FOR r IN
28429
+ SELECT o.id AS order_id, o.order_number, o.customer_id, oi.product_id,
28430
+ sum(public.logistics_own_sticker_count(oi.unit, oi.quantity, oi.plants_per_crate, prd.plants_per_crate))::int AS planten,
28431
+ bool_or(public.logistics_own_sticker_count(oi.unit, oi.quantity, oi.plants_per_crate, prd.plants_per_crate) IS NULL) AS onbekend
28432
+ FROM public.orders o
28433
+ JOIN public.order_items oi ON oi.order_id = o.id
28434
+ LEFT JOIN public.products prd ON prd.id = oi.product_id
28435
+ WHERE oi.needs_sticker IS TRUE
28436
+ AND oi.quarantined_at IS NULL
28437
+ AND oi.product_id IS NOT NULL
28438
+ 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;
28634
+
28635
+ pdf_url := nullif(item.payload->>'pdf_url', '');
28636
+ pdf_b64 := nullif(item.payload->>'pdf_base64', '');
28637
+ herkomst := nullif(item.payload->>'herkomst', '');
28638
+
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;
28643
+
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
28694
+ )
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;
28699
+
28700
+ RETURN job_id;
28701
+ END;
28702
+ $function$;
28703
+
28704
+ REVOKE EXECUTE ON FUNCTION public.logistics_own_sticker_sync(integer) FROM PUBLIC, anon, authenticated;
28705
+ GRANT EXECUTE ON FUNCTION public.logistics_own_sticker_sync(integer) TO service_role;
28706
+ REVOKE EXECUTE ON FUNCTION public.logistics_document_for_order(uuid, text) FROM PUBLIC, anon;
28707
+ GRANT EXECUTE ON FUNCTION public.logistics_document_for_order(uuid, text) TO authenticated, service_role;
28708
+ REVOKE EXECUTE ON FUNCTION public.logistics_document_selection_status() FROM PUBLIC, anon;
28709
+ GRANT EXECUTE ON FUNCTION public.logistics_document_selection_status() TO authenticated, service_role;
28710
+
28711
+ -- Veiligheid: draaien met de rechten van de aanroeper.
28712
+ ALTER FUNCTION public.logistics_document_selection_status() SECURITY INVOKER;
28713
+ ALTER FUNCTION public.logistics_document_for_order(uuid, text) SECURITY INVOKER;
28714
+
28715
+ -- ===== 1.0.176 reserveringen FO + pakbon centrale keuze =====
28716
+ -- 1.0.176: reserveringen (Voorraad 1.0.93) en pakbon via centrale documentkeuze (Pickschermen 1.0.56).
28717
+ CREATE OR REPLACE FUNCTION public.logistics_recompute_reserved_targeted(p_product_id uuid)
28718
+ RETURNS jsonb
28719
+ LANGUAGE plpgsql
28720
+ SECURITY DEFINER
28721
+ SET search_path TO 'public'
28722
+ AS $function$
28723
+ DECLARE
28724
+ v_t0 timestamptz := clock_timestamp();
28725
+ v_max_lines constant integer := 500;
28726
+ v_max_keys constant integer := 200;
28727
+ v_trace_ms constant numeric := 500;
28728
+ v_nkeys integer; v_nkeys_prev integer := -1; v_nlines integer := 0; v_changed integer := 0; v_ms numeric;
28729
+ oi_rec record; part record; member_rec record;
28730
+ v_key text; v_last_key text; v_remaining numeric; v_available numeric; v_take numeric; v_err text;
28731
+ v_skipped jsonb := '[]'::jsonb; v_skipped_qty numeric := 0;
28732
+ v_struct jsonb := '[]'::jsonb; v_struct_qty numeric := 0;
28733
+ v_freeze boolean := public.logistics_plan_setting('verdeling_bevriezen_actief', 0) = 1;
28734
+ v_frozen_ids uuid[]; v_line_ids uuid[];
28735
+ BEGIN
28736
+ IF p_product_id IS NULL THEN RETURN jsonb_build_object('status', 'geen_product'); END IF;
28737
+ IF current_setting('app.recomputing_reserved', true) = 'on' THEN RETURN jsonb_build_object('status', 'al_bezig'); END IF;
28738
+ PERFORM set_config('app.recomputing_reserved', 'on', true);
28739
+
28740
+ DROP TABLE IF EXISTS _rt_open;
28741
+ DROP TABLE IF EXISTS _rt_pexp;
28742
+ DROP TABLE IF EXISTS _rt_gm;
28743
+ DROP TABLE IF EXISTS _rt_raw;
28744
+ DROP TABLE IF EXISTS _rt_pkeys;
28745
+ DROP TABLE IF EXISTS _rt_keys;
28746
+ DROP TABLE IF EXISTS _rt_lines;
28747
+ DROP TABLE IF EXISTS _rt_demand;
28748
+ DROP TABLE IF EXISTS _rt_alloc;
28749
+ DROP TABLE IF EXISTS _rt_frozen;
28750
+ 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;
28751
+ CREATE TEMP TABLE _rt_pexp (product_id uuid, ord bigint, kind text, ref text, qty numeric, note text) ON COMMIT DROP;
28752
+ CREATE TEMP TABLE _rt_gm (group_id uuid, raw_key text, canon_key text, ord bigint) ON COMMIT DROP;
28753
+ CREATE TEMP TABLE _rt_raw (raw_key text PRIMARY KEY, canon_key text) ON COMMIT DROP;
28754
+ CREATE TEMP TABLE _rt_pkeys (product_id uuid, canon_key text) ON COMMIT DROP;
28755
+ CREATE TEMP TABLE _rt_keys (canon_key text PRIMARY KEY) ON COMMIT DROP;
28756
+ CREATE TEMP TABLE _rt_lines (order_item_id uuid PRIMARY KEY) ON COMMIT DROP;
28757
+ CREATE TEMP TABLE _rt_demand (trade_item_key text PRIMARY KEY, qty numeric NOT NULL DEFAULT 0) ON COMMIT DROP;
28758
+ CREATE TEMP TABLE _rt_alloc (order_item_id uuid, trade_item_key text, qty numeric, source text, group_id uuid) ON COMMIT DROP;
28759
+ CREATE TEMP TABLE _rt_frozen (order_item_id uuid PRIMARY KEY) ON COMMIT DROP;
28760
+
28761
+ INSERT INTO _rt_open
28762
+ SELECT oi.id, oi.product_id, COALESCE(oi.quantity, 0)::numeric, o.created_at, oi.order_id
28763
+ FROM logistics_active.order_items oi
28764
+ JOIN public.orders o ON o.id = oi.order_id
28765
+ WHERE o.status = 'Nog te picken' AND NOT public.logistics_reserved_order_excluded(o.id)
28766
+ AND COALESCE(oi.quantity, 0) > 0
28767
+ AND oi.product_id IS NOT NULL
28768
+ AND NOT EXISTS (SELECT 1 FROM public.stock_mutation_log sml WHERE sml.order_item_id = oi.id AND sml.mutation_type = 'consumption');
28769
+ -- Groepsregel naast eigen ledenregels: nooit reserveren.
28770
+ DELETE FROM _rt_open o WHERE o.order_item_id IN (SELECT d.order_item_id FROM public.logistics_double_group_lines(NULL) d);
28771
+
28772
+ INSERT INTO _rt_pexp (product_id, ord, kind, ref, qty, note)
28773
+ SELECT d.pid, e.ord, e.kind, e.ref, e.qty, e.note
28774
+ FROM (SELECT DISTINCT product_id AS pid FROM _rt_open UNION SELECT p_product_id) d,
28775
+ LATERAL public.logistics_expand_line(d.pid, 1) WITH ORDINALITY AS e(kind, ref, qty, note, ord);
28776
+
28777
+ INSERT INTO _rt_gm (group_id, raw_key, ord)
28778
+ SELECT g.gid, m.raw_key, m.ord
28779
+ FROM (SELECT DISTINCT ref::uuid AS gid FROM _rt_pexp WHERE kind = 'group') g,
28780
+ LATERAL (
28781
+ SELECT public.resolve_group_member_stock_key(pgi.trade_item_id) AS raw_key,
28782
+ row_number() OVER (ORDER BY pgi.sort_order ASC NULLS LAST, pgi.created_at ASC, pgi.id ASC) AS ord
28783
+ FROM public.product_group_items pgi
28784
+ WHERE pgi.product_group_id = g.gid) m
28785
+ WHERE m.raw_key IS NOT NULL
28786
+ AND m.raw_key NOT LIKE 'group:%';
28787
+
28788
+ INSERT INTO _rt_raw (raw_key, canon_key)
28789
+ SELECT x.k, public.logistics_reserved_key(x.k)
28790
+ FROM (SELECT ref AS k FROM _rt_pexp WHERE kind = 'item' UNION SELECT raw_key FROM _rt_gm) x;
28791
+ UPDATE _rt_gm g SET canon_key = r.canon_key FROM _rt_raw r WHERE r.raw_key = g.raw_key;
28792
+
28793
+ INSERT INTO _rt_pkeys (product_id, canon_key)
28794
+ SELECT DISTINCT e.product_id, r.canon_key
28795
+ FROM _rt_pexp e JOIN _rt_raw r ON r.raw_key = e.ref
28796
+ WHERE e.kind = 'item' AND r.canon_key IS NOT NULL
28797
+ UNION
28798
+ SELECT DISTINCT e.product_id, g.canon_key
28799
+ FROM _rt_pexp e JOIN _rt_gm g ON g.group_id = e.ref::uuid
28800
+ WHERE e.kind = 'group' AND g.canon_key IS NOT NULL;
28801
+
28802
+ INSERT INTO _rt_keys SELECT DISTINCT canon_key FROM _rt_pkeys WHERE product_id = p_product_id ON CONFLICT DO NOTHING;
28803
+ LOOP
28804
+ SELECT count(*) INTO v_nkeys FROM _rt_keys;
28805
+ EXIT WHEN v_nkeys = v_nkeys_prev;
28806
+ v_nkeys_prev := v_nkeys;
28807
+ INSERT INTO _rt_lines
28808
+ SELECT o.order_item_id FROM _rt_open o
28809
+ WHERE EXISTS (SELECT 1 FROM _rt_pkeys pk JOIN _rt_keys k ON k.canon_key = pk.canon_key
28810
+ WHERE pk.product_id = o.product_id)
28811
+ ON CONFLICT DO NOTHING;
28812
+ INSERT INTO _rt_keys
28813
+ SELECT DISTINCT pk.canon_key FROM _rt_pkeys pk
28814
+ JOIN _rt_open o ON o.product_id = pk.product_id
28815
+ JOIN _rt_lines l ON l.order_item_id = o.order_item_id
28816
+ ON CONFLICT DO NOTHING;
28817
+ SELECT count(*) INTO v_nlines FROM _rt_lines;
28818
+ IF v_nlines > v_max_lines OR (SELECT count(*) FROM _rt_keys) > v_max_keys THEN
28819
+ PERFORM set_config('app.recomputing_reserved', 'off', true);
28820
+ PERFORM public.recompute_all_reserved_stock();
28821
+ v_ms := round(extract(epoch FROM clock_timestamp() - v_t0) * 1000, 1);
28822
+ PERFORM public.logistics_trace(jsonb_build_object(
28823
+ 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
28824
+ 'actor', 'logistics_recompute_reserved_targeted', 'errorClass', 'omvang',
28825
+ 'reason', 'Gerichte herberekening te groot; volledige herberekening uitgevoerd.',
28826
+ 'values', jsonb_build_object('product_id', p_product_id, 'regels', v_nlines,
28827
+ 'sleutels', (SELECT count(*) FROM _rt_keys), 'duur_ms', v_ms)));
28828
+ RETURN jsonb_build_object('status', 'volledig', 'regels', v_nlines, 'duur_ms', v_ms);
28829
+ END IF;
28830
+ END LOOP;
28831
+
28832
+ -- Bevroren regels eerst, naar rato van het huidige aantal.
28833
+ IF v_freeze THEN
28834
+ INSERT INTO _rt_frozen
28835
+ SELECT o.order_item_id FROM _rt_open o JOIN _rt_lines l ON l.order_item_id = o.order_item_id
28836
+ WHERE EXISTS (SELECT 1 FROM public.logistics_reservation_allocations a WHERE a.order_item_id = o.order_item_id AND a.source <> 'error')
28837
+ AND public.logistics_allocation_frozen(o.order_id)
28838
+ ON CONFLICT DO NOTHING;
28839
+ INSERT INTO _rt_alloc
28840
+ SELECT f.order_item_id, s.trade_item_key, s.qty, s.source, s.group_id
28841
+ FROM _rt_frozen f
28842
+ JOIN _rt_open o ON o.order_item_id = f.order_item_id
28843
+ CROSS JOIN LATERAL public.logistics_frozen_allocation_scaled(f.order_item_id, o.qty) s;
28844
+ INSERT INTO _rt_demand AS d
28845
+ SELECT trade_item_key, SUM(qty) FROM _rt_alloc GROUP BY trade_item_key
28846
+ ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
28847
+ INSERT INTO _rt_keys SELECT DISTINCT trade_item_key FROM _rt_alloc ON CONFLICT DO NOTHING;
28848
+ END IF;
28849
+
28850
+ FOR oi_rec IN
28851
+ SELECT o.order_item_id AS id, o.product_id, o.qty
28852
+ FROM _rt_open o JOIN _rt_lines l ON l.order_item_id = o.order_item_id
28853
+ WHERE NOT EXISTS (SELECT 1 FROM _rt_frozen f WHERE f.order_item_id = o.order_item_id)
28854
+ ORDER BY o.created_at, o.order_item_id
28855
+ LOOP
28856
+ SELECT string_agg(e.note, '; ') INTO v_err FROM _rt_pexp e WHERE e.product_id = oi_rec.product_id AND e.kind = 'error';
28857
+ IF v_err IS NOT NULL THEN
28858
+ v_struct := v_struct || jsonb_build_object('order_item_id', oi_rec.id, 'product_id', oi_rec.product_id, 'reden', v_err);
28859
+ v_struct_qty := v_struct_qty + oi_rec.qty;
28860
+ CONTINUE;
28861
+ END IF;
28862
+
28863
+ FOR part IN
28864
+ SELECT e.kind, e.ref, e.qty * oi_rec.qty AS qty
28865
+ FROM _rt_pexp e WHERE e.product_id = oi_rec.product_id ORDER BY e.ord
28866
+ LOOP
28867
+ IF part.kind = 'item' THEN
28868
+ SELECT canon_key INTO v_key FROM _rt_raw WHERE raw_key = part.ref;
28869
+ IF v_key IS NULL THEN
28870
+ v_skipped := v_skipped || to_jsonb(part.ref);
28871
+ v_skipped_qty := v_skipped_qty + part.qty;
28872
+ ELSE
28873
+ INSERT INTO _rt_demand AS d VALUES (v_key, part.qty)
28874
+ ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
28875
+ INSERT INTO _rt_alloc VALUES (oi_rec.id, v_key, part.qty, 'item', NULL);
28876
+ END IF;
28877
+ ELSE
28878
+ v_remaining := part.qty;
28879
+ v_last_key := NULL;
28880
+ FOR member_rec IN
28881
+ SELECT g.canon_key FROM _rt_gm g WHERE g.group_id = part.ref::uuid ORDER BY g.ord
28882
+ LOOP
28883
+ v_key := member_rec.canon_key;
28884
+ IF v_key IS NULL THEN CONTINUE; END IF;
28885
+ v_last_key := v_key;
28886
+ IF v_remaining <= 0 THEN CONTINUE; END IF;
28887
+
28888
+ SELECT GREATEST(COALESCE(MAX(s.physical_stock), 0)
28889
+ - COALESCE((SELECT d.qty FROM _rt_demand d WHERE d.trade_item_key = v_key), 0), 0)
28890
+ INTO v_available
28891
+ FROM public.trade_item_stock s
28892
+ WHERE s.trade_item_key = v_key;
28893
+
28894
+ IF COALESCE(v_available, 0) <= 0 THEN CONTINUE; END IF;
28895
+ v_take := LEAST(v_available, v_remaining);
28896
+ INSERT INTO _rt_demand AS d VALUES (v_key, v_take)
28897
+ ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
28898
+ INSERT INTO _rt_alloc VALUES (oi_rec.id, v_key, v_take, 'group', part.ref::uuid);
28899
+ v_remaining := v_remaining - v_take;
28900
+ END LOOP;
28901
+
28902
+ IF v_remaining > 0 THEN
28903
+ IF v_last_key IS NULL THEN
28904
+ v_skipped := v_skipped || to_jsonb('group:' || part.ref);
28905
+ v_skipped_qty := v_skipped_qty + v_remaining;
28906
+ ELSE
28907
+ INSERT INTO _rt_demand AS d VALUES (v_last_key, v_remaining)
28908
+ ON CONFLICT (trade_item_key) DO UPDATE SET qty = d.qty + EXCLUDED.qty;
28909
+ INSERT INTO _rt_alloc VALUES (oi_rec.id, v_last_key, v_remaining, 'group', part.ref::uuid);
28910
+ END IF;
28911
+ END IF;
28912
+ END IF;
28913
+ END LOOP;
28914
+ END LOOP;
28915
+
28916
+ DELETE FROM public.logistics_reservation_allocations a
28917
+ WHERE a.order_item_id IN (SELECT l.order_item_id FROM _rt_lines l);
28918
+ INSERT INTO public.logistics_reservation_allocations (order_item_id, trade_item_key, qty, source, group_id, line_qty)
28919
+ SELECT r.order_item_id, r.trade_item_key, SUM(r.qty), r.source, r.group_id, MAX(o.qty)
28920
+ FROM _rt_alloc r
28921
+ LEFT JOIN _rt_open o ON o.order_item_id = r.order_item_id
28922
+ WHERE r.qty > 0
28923
+ GROUP BY r.order_item_id, r.trade_item_key, r.source, r.group_id;
28924
+
28925
+ -- 1.0.176 (Voorraad 1.0.93): Floriday-orders op Floriday-leidende sleutels tellen niet mee
28926
+ -- in reserved_stock; de toewijzingen zelf blijven bestaan. Oudere Voorraad: gedrag zoals nu + melding.
28927
+ IF to_regprocedure('public.voorraad_floriday_leads_key(text)') IS NOT NULL THEN
28928
+ UPDATE _rt_demand d
28929
+ SET qty = COALESCE((
28930
+ SELECT SUM(r.qty)
28931
+ FROM _rt_alloc r
28932
+ JOIN _rt_open op ON op.order_item_id = r.order_item_id
28933
+ JOIN public.orders ord ON ord.id = op.order_id
28934
+ WHERE r.trade_item_key = d.trade_item_key AND r.qty > 0
28935
+ AND NOT (public.normalize_order_source(ord.order_source) = 'FO'
28936
+ AND COALESCE(public.voorraad_floriday_leads_key(d.trade_item_key), false))), 0);
28937
+ ELSE
28938
+ PERFORM public.logistics_trace(jsonb_build_object(
28939
+ 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
28940
+ 'actor', 'logistics_recompute_reserved_targeted', 'errorClass', 'afhankelijkheid',
28941
+ 'reason', 'voorraad_floriday_leads_key ontbreekt (oudere Voorraad); Floriday-orders tellen nog mee in de reservering.',
28942
+ 'values', jsonb_build_object('product_id', p_product_id)));
28943
+ END IF;
28944
+
28945
+ PERFORM set_config('stock.change_source', 'RESERVATION_RECOMPUTE', true);
28946
+ UPDATE public.trade_item_stock s
28947
+ SET reserved_stock = CEIL(COALESCE(d.qty, 0))::integer,
28948
+ change_source = 'RESERVATION_RECOMPUTE',
28949
+ updated_at = now()
28950
+ FROM _rt_keys k
28951
+ LEFT JOIN _rt_demand d ON d.trade_item_key = k.canon_key
28952
+ WHERE s.trade_item_key = k.canon_key
28953
+ AND s.reserved_stock IS DISTINCT FROM CEIL(COALESCE(d.qty, 0))::integer;
28954
+ GET DIAGNOSTICS v_changed = ROW_COUNT;
28955
+
28956
+ v_ms := round(extract(epoch FROM clock_timestamp() - v_t0) * 1000, 1);
28957
+
28958
+ IF jsonb_array_length(v_skipped) > 0 THEN
28959
+ PERFORM public.logistics_trace(jsonb_build_object(
28960
+ 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
28961
+ 'actor', 'logistics_recompute_reserved_targeted', 'errorClass', 'ontbrekende_gegevens',
28962
+ 'reason', 'Geen voorraadrij en geen actieve Floriday-koppeling om die aan te maken; reservering voor deze artikelen niet berekend.',
28963
+ 'values', jsonb_build_object('product_id', p_product_id,
28964
+ 'sleutels', (SELECT jsonb_agg(DISTINCT x) FROM jsonb_array_elements_text(v_skipped) x),
28965
+ 'gemiste_vraag_stuks', v_skipped_qty)));
28966
+ END IF;
28967
+
28968
+ IF jsonb_array_length(v_struct) > 0 THEN
28969
+ PERFORM public.logistics_trace(jsonb_build_object(
28970
+ 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
28971
+ 'actor', 'logistics_recompute_reserved_targeted', 'errorClass', 'structuur',
28972
+ 'reason', 'Samenstelling met een lus of te diepe nesting; voor deze bestelregels is niets gereserveerd.',
28973
+ 'values', jsonb_build_object('regels', v_struct, 'gemiste_vraag_stuks', v_struct_qty)));
28974
+ END IF;
28975
+
28976
+ IF v_ms > v_trace_ms THEN
28977
+ PERFORM public.logistics_trace(jsonb_build_object(
28978
+ 'phase', 'reservering', 'step', 'reservering herberekenen', 'status', 'waarschuwing',
28979
+ 'actor', 'logistics_recompute_reserved_targeted', 'errorClass', 'traag',
28980
+ 'reason', 'Gerichte herberekening duurde langer dan verwacht.',
28981
+ 'values', jsonb_build_object('product_id', p_product_id, 'regels', v_nlines,
28982
+ 'sleutels', (SELECT count(*) FROM _rt_keys), 'duur_ms', v_ms)));
28983
+ END IF;
28984
+
28985
+ IF v_freeze THEN
28986
+ SELECT array_agg(order_item_id) INTO v_frozen_ids FROM _rt_frozen;
28987
+ SELECT array_agg(order_item_id) INTO v_line_ids FROM _rt_lines;
28988
+ PERFORM public.logistics_report_frozen_shortage(v_frozen_ids);
28989
+ PERFORM public.logistics_allocation_check_changes(v_line_ids);
28990
+ END IF;
28991
+
28992
+ PERFORM set_config('app.recomputing_reserved', 'off', true);
28993
+ RETURN jsonb_build_object('status', 'gericht', 'regels', v_nlines,
28994
+ 'sleutels', (SELECT count(*) FROM _rt_keys), 'gewijzigd', v_changed, 'duur_ms', v_ms);
28995
+ END
28996
+ $function$;
28997
+
28998
+ CREATE OR REPLACE FUNCTION public.logistics_resolve_pick_document(_queue_item_id uuid, _voorbeeld boolean, _session_id text, _is_herprint boolean)
28999
+ RETURNS jsonb
29000
+ LANGUAGE plpgsql
29001
+ STABLE SECURITY DEFINER
29002
+ SET search_path TO 'public'
29003
+ AS $function$
29004
+ DECLARE
29005
+ item public.pick_queue;
29006
+ dt public.pick_document_types;
29007
+ doc_code text;
29008
+ target_layout uuid;
29009
+ target_plek uuid;
29010
+ layout_meta jsonb;
29011
+ v_types public.doc_layout_type[];
29012
+ doc_type public.doc_layout_type := 'vrij';
29013
+ layout_type public.doc_layout_type;
29014
+ pdf_url text;
29015
+ pdf_b64 text;
29016
+ herkomst text;
29017
+ v_variant text;
29018
+ v_is_partial boolean := false;
29019
+ v_cart_id uuid;
29020
+ v_cart jsonb;
29021
+ v_order_id uuid;
29022
+ v_barcode text;
29023
+ v_barcode_on boolean := public.logistics_pick_barcode_enabled();
29024
+ v_ok boolean := true;
29025
+ v_reden text;
29026
+ BEGIN
29027
+ SELECT * INTO item FROM public.pick_queue WHERE id = _queue_item_id;
29028
+ IF NOT FOUND THEN
29029
+ RAISE EXCEPTION 'Dit document bestaat niet meer.' USING ERRCODE = 'P0002';
29030
+ END IF;
29031
+
29032
+ SELECT * INTO dt FROM public.pick_document_types WHERE id = item.document_type_id;
29033
+
29034
+ doc_code := lower(btrim(coalesce(
29035
+ nullif(item.payload->>'documentType', ''),
29036
+ nullif(dt.code, ''),
29037
+ ''
29038
+ )));
29039
+
29040
+ v_types := public.logistics_document_type_map(doc_code);
29041
+
29042
+ BEGIN
29043
+ v_cart_id := nullif(coalesce(
29044
+ item.payload->>'cartId', item.payload->>'cart_id',
29045
+ item.payload->'data'->>'cartId', item.payload->'data'->>'cart_id'
29046
+ ), '')::uuid;
29047
+ EXCEPTION WHEN invalid_text_representation THEN
29048
+ v_cart_id := NULL;
29049
+ END;
29050
+
29051
+ IF v_cart_id IS NOT NULL THEN
29052
+ SELECT COALESCE(c.order_id, (
29053
+ SELECT oi.order_id FROM logistics_active.order_cart_items ci
29054
+ JOIN logistics_active.order_items oi ON oi.id = ci.order_item_id
29055
+ WHERE ci.cart_id = c.id LIMIT 1))
29056
+ INTO v_order_id
29057
+ FROM logistics_active.order_carts c WHERE c.id = v_cart_id;
29058
+
29059
+ IF v_order_id IS NOT NULL THEN
29060
+ SELECT c INTO v_cart
29061
+ FROM jsonb_array_elements(public.logistics_cart_slip_plan(v_order_id)->'carts') AS c
29062
+ WHERE (c->>'cartId')::uuid = v_cart_id;
29063
+ END IF;
29064
+
29065
+ IF v_barcode_on THEN
29066
+ v_barcode := COALESCE(
29067
+ (SELECT b.barcode FROM public.logistics_cart_barcode b WHERE b.cart_id = v_cart_id),
29068
+ public.logistics_cart_barcode_value(v_cart_id)
29069
+ );
29070
+ IF v_cart IS NOT NULL THEN
29071
+ v_cart := v_cart || jsonb_build_object('barcode', v_barcode);
29072
+ END IF;
29073
+ END IF;
29074
+ END IF;
29075
+
29076
+ IF v_cart IS NOT NULL THEN
29077
+ v_is_partial := NOT COALESCE((v_cart->>'isFullCart')::boolean, false);
29078
+ ELSE
29079
+ v_variant := nullif(coalesce(
29080
+ item.payload->>'slipVariant',
29081
+ item.payload->'data'->>'slipVariant'
29082
+ ), '');
29083
+ IF v_variant IS NOT NULL THEN
29084
+ v_is_partial := (v_variant = 'partial_order');
29085
+ ELSIF nullif(coalesce(item.payload->>'pickUnit', item.payload->'data'->>'pickUnit'), '') IS NOT NULL THEN
29086
+ v_is_partial := (coalesce(item.payload->>'pickUnit', item.payload->'data'->>'pickUnit') = 'order');
29087
+ ELSE
29088
+ v_is_partial := (item.cart_count IS NOT NULL AND item.cart_count <= 1);
29089
+ END IF;
29090
+ END IF;
29091
+
29092
+ -- 1.0.176 (Pickschermen 1.0.56): layout alleen via de centrale documentkeuze; payload.layout_id wordt genegeerd.
29093
+ v_order_id := COALESCE(v_order_id, public.logistics_try_uuid(COALESCE(
29094
+ v_cart->>'orderId', item.payload->>'orderId', item.payload->>'order_id', item.payload->'data'->>'orderId')));
29095
+ IF doc_code <> '' AND v_order_id IS NOT NULL THEN
29096
+ layout_meta := public.logistics_document_for_order(v_order_id, doc_code);
29097
+ target_layout := public.logistics_try_uuid(layout_meta->>'layout_id');
29098
+ v_ok := COALESCE(layout_meta->>'ok', 'true') <> 'false';
29099
+ v_reden := nullif(layout_meta->>'reden', '');
29100
+ ELSIF doc_code <> '' THEN
29101
+ layout_meta := public.logistics_document_layout_v2(doc_code, v_is_partial);
29102
+ target_layout := public.logistics_try_uuid(layout_meta->>'layout_id');
29103
+ END IF;
29104
+
29105
+ SELECT l.documenttype INTO layout_type
29106
+ FROM public.document_layouts l
29107
+ WHERE l.id = target_layout AND l.actief IS TRUE;
29108
+
29109
+ IF layout_type IS NULL THEN target_layout := NULL; END IF;
29110
+
29111
+ doc_type := COALESCE(layout_type, v_types[1], 'vrij');
29112
+
29113
+ IF item.station_id IS NOT NULL THEN
29114
+ SELECT k.print_plek_id INTO target_plek
29115
+ FROM public.print_plek_koppelingen k
29116
+ WHERE k.bron IN ('pickstation', 'pick_station')
29117
+ AND k.bron_id = item.station_id::text
29118
+ ORDER BY k.created_at
29119
+ LIMIT 1;
29120
+ END IF;
29121
+
29122
+ pdf_url := nullif(item.payload->>'pdf_url', '');
29123
+ pdf_b64 := nullif(item.payload->>'pdf_base64', '');
29124
+ herkomst := nullif(item.payload->>'herkomst', '');
29125
+
29126
+ IF target_layout IS NULL AND v_reden IS NULL AND pdf_url IS NULL AND pdf_b64 IS NULL THEN
29127
+ v_reden := 'Maak of activeer een ' || COALESCE(nullif(doc_code, ''), 'document') || 'layout in Layouts en printers.';
29128
+ END IF;
29129
+ IF NOT v_ok AND (pdf_url IS NOT NULL OR pdf_b64 IS NOT NULL) THEN v_ok := true; END IF;
29130
+
29131
+ RETURN jsonb_build_object(
29132
+ 'ok', v_ok,
29133
+ 'reden', CASE WHEN v_ok THEN NULL ELSE v_reden END,
29134
+ 'layout_keuze', layout_meta->>'keuze',
29135
+ 'queue_item_id', item.id,
29136
+ 'station_id', item.station_id,
29137
+ 'doc_code', doc_code,
29138
+ 'layout_id', target_layout,
29139
+ 'layout_variant', CASE WHEN v_is_partial THEN 'partial_order' ELSE 'full_cart' END,
29140
+ 'cart_id', v_cart_id,
29141
+ 'cart', v_cart,
29142
+ 'barcode', v_barcode,
29143
+ 'documenttype', doc_type,
29144
+ 'documentnaam', COALESCE(item.document_number, 'Pickdocument'),
29145
+ 'pdf_url', pdf_url,
29146
+ 'pdf_base64', pdf_b64,
29147
+ 'herkomst', herkomst,
29148
+ 'render_html', nullif(item.payload->>'render_html', ''),
29149
+ 'print_plek_id', target_plek,
29150
+ 'foutmelding',
29151
+ CASE WHEN target_layout IS NULL AND pdf_url IS NULL AND pdf_b64 IS NULL
29152
+ THEN v_reden
29153
+ ELSE NULL END,
29154
+ 'payload', jsonb_build_object(
29155
+ 'source', 'pickstation',
29156
+ 'voorbeeld', COALESCE(_voorbeeld, false),
29157
+ 'queue_item_id', item.id,
29158
+ 'station_id', item.station_id,
29159
+ 'print_plek_id', target_plek,
29160
+ 'session_id', _session_id,
29161
+ 'is_herprint', COALESCE(_is_herprint, false),
29162
+ 'document_number', item.document_number,
29163
+ 'document_type_code', COALESCE(nullif(doc_code, ''), dt.code),
29164
+ 'layout_variant', CASE WHEN v_is_partial THEN 'partial_order' ELSE 'full_cart' END,
29165
+ 'layout_id', target_layout,
29166
+ 'report_key', dt.report_key,
29167
+ 'print_template', dt.print_template,
29168
+ 'order_number', item.order_number,
29169
+ 'customer_name', item.customer_name,
29170
+ 'reference', item.reference,
29171
+ 'barcode', CASE WHEN v_barcode_on THEN COALESCE(v_barcode, item.barcode) ELSE item.barcode END,
29172
+ 'cart_id', v_cart_id,
29173
+ 'cart_index', v_cart->>'cartIndex',
29174
+ 'cart_total', v_cart->>'cartTotal',
29175
+ 'cart_label', v_cart->>'cartLabel',
29176
+ 'cart_items', COALESCE(v_cart->'items', '[]'::jsonb),
29177
+ 'country_code', item.country_code,
29178
+ 'data', (COALESCE(item.payload, '{}'::jsonb) - 'layout_id' - 'layout')
29179
+ || jsonb_build_object('print_plek_id', target_plek)
29180
+ || COALESCE(jsonb_build_object('cart', v_cart), '{}'::jsonb)
29181
+ )
29182
+ );
29183
+ END;
29184
+ $function$;
29185
+
29186
+ 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)
29187
+ RETURNS uuid
29188
+ LANGUAGE plpgsql
29189
+ SECURITY DEFINER
29190
+ SET search_path TO 'public'
29191
+ AS $function$
29192
+ declare
29193
+ doc jsonb; target_printer uuid; job_id uuid; v_cart_id uuid;
29194
+ v_doc_code text; v_plan jsonb; v_cart jsonb; v_last uuid; v_row_cart text;
29195
+ begin
29196
+ select lower(btrim(coalesce(nullif(q.payload->>'documentType',''), nullif(t.code,''), ''))),
29197
+ nullif(q.payload->>'cartId','')
29198
+ into v_doc_code, v_row_cart
29199
+ from public.pick_queue q
29200
+ left join public.pick_document_types t on t.id = q.document_type_id
29201
+ where q.id = _queue_item_id;
29202
+
29203
+ if v_doc_code = 'pakbon' then
29204
+ -- Karregel: precies die ene kar. Orderregel: per kar één opdracht.
29205
+ if v_row_cart is not null then
29206
+ return public.pick_enqueue_print_job(_queue_item_id, _printer_id, _session_id,
29207
+ coalesce(_is_herprint,false), v_row_cart);
29208
+ end if;
29209
+
29210
+ begin
29211
+ v_plan := public.pick_queue_cart_plan(_queue_item_id);
29212
+ exception when others then v_plan := null; end;
29213
+
29214
+ if v_plan is null or jsonb_array_length(coalesce(v_plan->'carts','[]'::jsonb)) = 0 then
29215
+ raise exception 'Het karplan voor dit document is nog niet klaar; er wordt geen pakbon zonder barcode geprint.' using errcode='check_violation';
29216
+ end if;
29217
+
29218
+ for v_cart in select c from jsonb_array_elements(v_plan->'carts') as c loop
29219
+ v_last := public.pick_enqueue_print_job(_queue_item_id, _printer_id, _session_id,
29220
+ coalesce(_is_herprint,false), v_cart->>'cartId');
29221
+ end loop;
29222
+ return v_last;
29223
+ end if;
29224
+
29225
+ doc := public.logistics_resolve_pick_document(_queue_item_id, false, _session_id, coalesce(_is_herprint,false));
29226
+ -- 1.0.176: centrale documentkeuze zegt nee -> dit document niet maken, reden teruggeven.
29227
+ if doc->>'ok' = 'false' then
29228
+ raise exception '%', coalesce(nullif(doc->>'reden',''), 'Geen layout voor dit document.') using errcode='check_violation';
29229
+ end if;
29230
+
29231
+ if nullif(doc->>'barcode','') is not null then
29232
+ begin v_cart_id := nullif(doc->>'cart_id','')::uuid;
29233
+ exception when invalid_text_representation then v_cart_id := null; end;
29234
+ if v_cart_id is not null then perform public.logistics_ensure_cart_barcode(v_cart_id); end if;
29235
+ end if;
29236
+
29237
+ select p.id into target_printer from public.printers p
29238
+ where p.id = _printer_id and p.actief and nullif(p.extern_printer_id,'') is not null;
29239
+
29240
+ insert into public.print_jobs(
29241
+ printer_id, layout_id, documenttype, documentnaam, status, foutmelding,
29242
+ payload, gebruiker_naam, pdf_url, pdf_base64, herkomst, is_herprint)
29243
+ values (target_printer, nullif(doc->>'layout_id','')::uuid,
29244
+ (doc->>'documenttype')::public.doc_layout_type, doc->>'documentnaam', 'wachtend',
29245
+ nullif(doc->>'foutmelding',''), doc->'payload', 'Pickstation',
29246
+ nullif(doc->>'pdf_url',''), nullif(doc->>'pdf_base64',''), nullif(doc->>'herkomst',''),
29247
+ coalesce(_is_herprint,false))
29248
+ returning id into job_id;
29249
+
29250
+ perform public.logistics_enqueue_sticker_with_slip(_queue_item_id, _printer_id, _session_id,
29251
+ coalesce(_is_herprint,false), nullif(doc->>'cart_id',''), job_id);
29252
+ update public.pick_queue set print_job_id = job_id where id = _queue_item_id;
29253
+
29254
+ return job_id;
29255
+ end; $function$;
29256
+
29257
+ 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)
29258
+ RETURNS uuid
29259
+ LANGUAGE plpgsql
29260
+ SECURITY DEFINER
29261
+ SET search_path TO 'public'
29262
+ AS $function$
29263
+ DECLARE
29264
+ item public.pick_queue;
29265
+ dt public.pick_document_types;
29266
+ doc_code text;
29267
+ target_printer uuid;
29268
+ target_layout uuid;
29269
+ target_plek uuid;
29270
+ layout_meta jsonb;
29271
+ layout_reden text;
29272
+ v_types public.doc_layout_type[];
29273
+ doc_type public.doc_layout_type := 'vrij';
29274
+ layout_type public.doc_layout_type;
29275
+ pdf_url text;
29276
+ pdf_b64 text;
29277
+ herkomst text;
29278
+ job_id uuid;
29279
+ cart_plan jsonb := NULL;
29280
+ selected_cart jsonb := NULL;
29281
+ v_cart_uuid uuid;
29282
+ v_barcode text;
29283
+ v_barcode_on boolean := public.logistics_pick_barcode_enabled();
29284
+ v_is_partial boolean := false;
29285
+ v_sleutel text;
29286
+ v_order uuid;
29287
+ BEGIN
29288
+ SELECT * INTO item FROM public.pick_queue WHERE id = _queue_item_id;
29289
+ IF NOT FOUND THEN RAISE EXCEPTION 'Dit document bestaat niet meer.'; END IF;
29290
+ SELECT * INTO dt FROM public.pick_document_types WHERE id = item.document_type_id;
29291
+ doc_code := lower(btrim(coalesce(nullif(item.payload->>'documentType', ''), nullif(dt.code, ''), '')));
29292
+ v_types := public.logistics_document_type_map(doc_code);
29293
+
29294
+ IF _cart_id IS NOT NULL THEN
29295
+ cart_plan := public.pick_queue_cart_plan(_queue_item_id);
29296
+ SELECT c INTO selected_cart FROM jsonb_array_elements(COALESCE(cart_plan->'carts', '[]'::jsonb)) AS c
29297
+ WHERE c->>'cartId' = _cart_id LIMIT 1;
29298
+ IF selected_cart IS NULL THEN
29299
+ RAISE EXCEPTION 'De gekozen kar bestaat niet meer in het actuele karplan.';
29300
+ END IF;
29301
+ v_is_partial := NOT COALESCE((selected_cart->>'isFullCart')::boolean, false);
29302
+ IF v_barcode_on THEN
29303
+ BEGIN
29304
+ v_cart_uuid := nullif(_cart_id, '')::uuid;
29305
+ EXCEPTION WHEN invalid_text_representation THEN
29306
+ v_cart_uuid := NULL;
29307
+ END;
29308
+ IF v_cart_uuid IS NOT NULL THEN
29309
+ v_barcode := public.logistics_ensure_cart_barcode(v_cart_uuid);
29310
+ selected_cart := selected_cart || jsonb_build_object('barcode', v_barcode);
29311
+ END IF;
29312
+ END IF;
29313
+ END IF;
29314
+
29315
+ -- 1.0.176: het document wordt automatisch bepaald uit de order.
29316
+ v_order := public.logistics_try_uuid(COALESCE(selected_cart->>'orderId', item.payload->>'orderId', item.payload->>'order_id'));
29317
+ IF doc_code <> '' AND v_order IS NOT NULL THEN
29318
+ layout_meta := public.logistics_document_for_order(v_order, doc_code);
29319
+ target_layout := public.logistics_try_uuid(layout_meta->>'layout_id');
29320
+ layout_reden := nullif(layout_meta->>'reden', '');
29321
+ IF layout_meta->>'ok' = 'false' AND nullif(item.payload->>'pdf_url', '') IS NULL AND nullif(item.payload->>'pdf_base64', '') IS NULL THEN
29322
+ RAISE EXCEPTION '%', COALESCE(layout_reden, 'Geen layout voor dit document.') USING ERRCODE = 'check_violation';
29323
+ END IF;
29324
+ ELSIF doc_code <> '' THEN
29325
+ layout_meta := public.logistics_document_layout_v2(doc_code, v_is_partial);
29326
+ target_layout := public.logistics_try_uuid(layout_meta->>'layout_id');
29327
+ END IF;
29328
+
29329
+ SELECT l.documenttype INTO layout_type FROM public.document_layouts l WHERE l.id = target_layout AND l.actief IS TRUE;
29330
+ IF layout_type IS NULL THEN target_layout := NULL; END IF;
29331
+ doc_type := COALESCE(layout_type, v_types[1], 'vrij');
29332
+
29333
+ IF item.station_id IS NOT NULL THEN
29334
+ SELECT k.print_plek_id INTO target_plek FROM public.print_plek_koppelingen k
29335
+ WHERE k.bron IN ('pickstation', 'pick_station') AND k.bron_id = item.station_id::text
29336
+ ORDER BY k.created_at LIMIT 1;
29337
+ END IF;
29338
+
29339
+ SELECT p.id INTO target_printer FROM public.printers p
29340
+ WHERE p.id = _printer_id AND p.actief AND nullif(p.extern_printer_id, '') IS NOT NULL;
29341
+
29342
+ pdf_url := nullif(item.payload->>'pdf_url', '');
29343
+ pdf_b64 := nullif(item.payload->>'pdf_base64', '');
29344
+ herkomst := nullif(item.payload->>'herkomst', '');
29345
+
29346
+ IF NOT COALESCE(_is_herprint, false) AND _cart_id IS NOT NULL THEN
29347
+ v_sleutel := 'pakbon:' || item.id::text || ':' || _cart_id || ':'
29348
+ || coalesce(target_layout::text, '-') || ':' || coalesce(target_printer::text, '-');
29349
+ END IF;
29350
+
29351
+ INSERT INTO public.print_jobs(
29352
+ printer_id, layout_id, documenttype, documentnaam, status, foutmelding,
29353
+ payload, gebruiker_naam, pdf_url, pdf_base64, herkomst, is_herprint, idempotentie_sleutel
29354
+ )
29355
+ VALUES (
29356
+ target_printer, target_layout, doc_type, COALESCE(item.document_number, 'Pickdocument'), 'wachtend',
29357
+ CASE WHEN target_layout IS NULL AND pdf_url IS NULL AND pdf_b64 IS NULL
29358
+ THEN COALESCE(layout_reden, 'Maak of activeer een ' || COALESCE(nullif(doc_code, ''), 'document') || 'layout in Layouts en printers.')
29359
+ ELSE NULL END,
29360
+ jsonb_build_object(
29361
+ 'source', 'pickstation', 'queue_item_id', item.id, 'station_id', item.station_id,
29362
+ 'print_plek_id', target_plek, 'session_id', _session_id, 'is_herprint', COALESCE(_is_herprint, false),
29363
+ 'document_number', item.document_number, 'document_type_code', COALESCE(nullif(doc_code, ''), dt.code),
29364
+ 'report_key', dt.report_key, 'print_template', dt.print_template,
29365
+ 'order_number', COALESCE(selected_cart->>'orderNumber', item.order_number),
29366
+ 'customer_name', item.customer_name, 'reference', item.reference,
29367
+ 'layout_id', target_layout, 'layout_keuze', layout_meta->>'keuze',
29368
+ 'layout_variant', CASE WHEN v_is_partial THEN 'partial_order' ELSE 'full_cart' END,
29369
+ 'barcode', CASE WHEN v_barcode_on THEN COALESCE(v_barcode, item.barcode) ELSE item.barcode END,
29370
+ 'country_code', item.country_code, 'cart_id', _cart_id, 'cart', selected_cart,
29371
+ 'data', (COALESCE(item.payload, '{}'::jsonb) - 'layout_id' - 'layout') || jsonb_build_object(
29372
+ 'print_plek_id', target_plek, 'cart_id', _cart_id, 'cart', selected_cart,
29373
+ 'orderId', COALESCE(selected_cart->>'orderId', item.payload->>'orderId'),
29374
+ 'orderNumber', COALESCE(selected_cart->>'orderNumber', item.order_number))
29375
+ ),
29376
+ 'Pickstation', pdf_url, pdf_b64, herkomst, COALESCE(_is_herprint, false), v_sleutel
29377
+ )
29378
+ RETURNING id INTO job_id;
29379
+
29380
+ PERFORM public.logistics_enqueue_sticker_with_slip(_queue_item_id, _printer_id, _session_id, COALESCE(_is_herprint,false), _cart_id, job_id);
29381
+ UPDATE public.pick_queue SET print_job_id = job_id WHERE id = _queue_item_id;
29382
+ RETURN job_id;
29383
+ END;
29384
+ $function$;
29385
+
29386
+ -- ===== 1.0.176 verzenddocument Floriday bepalen =====
29387
+ -- 1.0.176: welk verzenddocument levert Floriday voor deze order (afleverbrief of veilingbrief).
29388
+ -- Bron: logistics_slip_shipping_note.fulfillment_order_id -> floriday_fulfillment_orders_cache.floriday_id,
29389
+ -- velden data->>'type', data->>'isReAuctioning', data->'deliveryNoteCodes'. Fail closed: niet gokken.
29390
+ CREATE OR REPLACE FUNCTION public.logistics_floriday_shipping_document(p_order_id uuid)
29391
+ RETURNS jsonb
29392
+ LANGUAGE plpgsql
29393
+ STABLE
29394
+ SET search_path TO 'public'
29395
+ AS $function$
29396
+ DECLARE
29397
+ v_fo text;
29398
+ v_data jsonb;
29399
+ v_type text;
29400
+ BEGIN
29401
+ IF to_regclass('public.logistics_slip_shipping_note') IS NULL OR to_regclass('public.floriday_fulfillment_orders_cache') IS NULL THEN
29402
+ RETURN jsonb_build_object('ok', false, 'documenttype', NULL, 'fulfillment_order_id', NULL,
29403
+ 'reden', 'De Floriday-zendinggegevens ontbreken in deze omgeving; het verzenddocument kan niet worden bepaald.');
29404
+ END IF;
29405
+ SELECT s.fulfillment_order_id::text INTO v_fo
29406
+ FROM public.logistics_slip_shipping_note s
29407
+ WHERE s.order_id = p_order_id AND s.fulfillment_order_id IS NOT NULL
29408
+ ORDER BY s.updated_at DESC NULLS LAST, s.created_at DESC LIMIT 1;
29409
+ IF v_fo IS NULL THEN
29410
+ RETURN jsonb_build_object('ok', false, 'documenttype', NULL, 'fulfillment_order_id', NULL,
29411
+ 'reden', 'Nog geen Floriday-zending bekend voor deze order; het verzenddocument volgt zodra Floriday die heeft vastgelegd.');
29412
+ END IF;
29413
+ SELECT c.data INTO v_data FROM public.floriday_fulfillment_orders_cache c
29414
+ WHERE c.floriday_id::text = v_fo AND c.is_deleted IS NOT TRUE ORDER BY c.fetched_at DESC NULLS LAST LIMIT 1;
29415
+ IF v_data IS NULL THEN
29416
+ RETURN jsonb_build_object('ok', false, 'documenttype', NULL, 'fulfillment_order_id', v_fo,
29417
+ 'reden', 'De Floriday-zending van deze order is nog niet opgehaald; het verzenddocument volgt na de volgende Floriday-synchronisatie.');
29418
+ END IF;
29419
+ v_type := upper(coalesce(v_data->>'type', ''));
29420
+ IF lower(coalesce(v_data->>'isReAuctioning', '')) = 'true' OR v_type LIKE '%CLOCK%' OR v_type LIKE '%AUCTION%' THEN
29421
+ RETURN jsonb_build_object('ok', true, 'documenttype', 'veilingbrief', 'fulfillment_order_id', v_fo, 'reden', NULL);
29422
+ END IF;
29423
+ IF v_type = 'DIRECT_SALES' AND jsonb_typeof(v_data->'deliveryNoteCodes') = 'array'
29424
+ AND jsonb_array_length(v_data->'deliveryNoteCodes') > 0 THEN
29425
+ RETURN jsonb_build_object('ok', true, 'documenttype', 'afleverbrief', 'fulfillment_order_id', v_fo, 'reden', NULL);
29426
+ END IF;
29427
+ RETURN jsonb_build_object('ok', false, 'documenttype', NULL, 'fulfillment_order_id', v_fo,
29428
+ 'reden', 'Onbekend Floriday-zendingtype "' || coalesce(nullif(v_type, ''), 'leeg')
29429
+ || '" of nog geen verzendbriefcode; het verzenddocument wordt niet gegokt.');
29430
+ END;
29431
+ $function$;
29432
+ REVOKE EXECUTE ON FUNCTION public.logistics_floriday_shipping_document(uuid) FROM PUBLIC, anon;
29433
+ GRANT EXECUTE ON FUNCTION public.logistics_floriday_shipping_document(uuid) TO authenticated, service_role;
29434
+
29435
+ CREATE OR REPLACE FUNCTION public.logistics_document_for_order(p_order_id uuid, p_documenttype text)
29436
+ RETURNS jsonb
29437
+ LANGUAGE plpgsql
29438
+ STABLE
29439
+ SET search_path TO 'public'
29440
+ AS $function$
29441
+ DECLARE
29442
+ v_soort text := lower(btrim(coalesce(p_documenttype, '')));
29443
+ v_floriday boolean;
29444
+ v_bron text;
29445
+ v_type text;
29446
+ v_res jsonb;
29447
+ v_n int; v_std int;
29448
+ v_l public.document_layouts%ROWTYPE;
29449
+ v_ship jsonb;
29450
+ v_extra jsonb := '{}'::jsonb;
29451
+ BEGIN
29452
+ IF v_soort = '' THEN
29453
+ RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'reden', 'Geen documentsoort opgegeven.');
29454
+ END IF;
29455
+
29456
+ -- 1.0.176: 'verzenddocument' wordt hier vertaald naar het echte document van de order.
29457
+ IF v_soort IN ('verzenddocument', 'verzendbrief') THEN
29458
+ SELECT o.order_source = 'floriday' INTO v_floriday FROM public.orders o WHERE o.id = p_order_id;
29459
+ IF NOT FOUND THEN
29460
+ RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'reden', 'Order niet gevonden.');
29461
+ END IF;
29462
+ IF coalesce(v_floriday, false) THEN
29463
+ v_ship := public.logistics_floriday_shipping_document(p_order_id);
29464
+ IF NOT coalesce((v_ship->>'ok')::boolean, false) THEN
29465
+ RETURN jsonb_build_object('ok', false, 'layout_id', NULL, 'keuze', 'engine',
29466
+ 'fulfillment_order_id', v_ship->>'fulfillment_order_id', 'reden', v_ship->>'reden');
29467
+ END IF;
29468
+ v_soort := v_ship->>'documenttype';
29469
+ v_extra := jsonb_build_object('verzenddocument_bepaald', v_soort,
29470
+ '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$;