4H Phase 1 + Phase 2 is on uat — since 2026-08-25, updated 2026-08-28: QA round live, automated part green on gouat, 74 manual cases + P7 open, kl_view 4H AUTO row now in the all-regions price table (#634/#635 merged). This page carries the automated results from the merge result (green), the full manual test list handed to QA on 2026-08-26 (not yet run), and below them the earlier dev evidence kept as provenance.
4H Phase 1 + Phase 2 merged to uat on 2026-08-25 (cut day — the 2026-08-27/28 live gouat round below is newer). The automated matrix below was run on the merge result and is green; the 121 manual cases were handed to QA on 2026-08-26. On 2026-08-27 an automated first pass was run live against gouat (block below) and its results are entered in the Result column; every other row still awaits manual QA. Nothing on this page claims a full manual UAT pass.
| repo | uat merge | PR | base (master) | payload |
|---|---|---|---|---|
| pakas | a88b5ea9 | #618 · pipeline #733 | a130b76c | 42 files, +2032 / −47 |
| siunta | ff4e6aef | #158 | d7214ae6 | 28 files, +1764 / −35 |
| go-import | 842eaa41 | #64 | d0fe853c | 5 files, +735 / −1 |
| veniship | excluded from this window — nothing on this page tests it | |||
| object | state |
|---|---|
LT postcodes posti_service1 | all 17 246 enabled |
tbl_klientai.kl_4rp_auto | present · 4 AUTO (3464 JYSK BALTIC · 89398 ROMITEKA · 157673 ELECTRONIC TRADE · 158280 PRO TRADE) · 166 557 MANUAL |
tbl_log_siunt_4rp | present, empty · s4rp_user_id int(8) unsigned · s4rp_source enum('auto','manual') |
| ALTER cost | 19 s on 166 561 rows / 60.3 MB (MyISAM full copy) — the prod estimate |
Credentials are identical on godev.* and gouat.* — so veni-tests/.env.uat is .env.dev plus the go-import block from .env.local with godev. swapped for gouat.. No new logins to chase; .gitignore already covers .env.*.
Every auto-apply case below depends on the payer being AUTO. QA ticks 4H AUTO on their own client card in PAKAS and tests as their own account — no DB change, no borrowed payer, nothing to restore for anyone else. Set GO_IMPORT_USER_ID in .env.uat to your own client id.
| step | action | why this order |
|---|---|---|
| 1 | Run the MANUAL-payer cases while the box is still unticked — 5.1 and go-import C10 | a real test result, not a setup step; running it first means the MANUAL state is never wasted |
| 2 | Tick 4H AUTO on your client card, as a kl_edit_plius user | this action is VDT-3384 §B2 step 7 — do it through the UI, never by SQL, or the ACL path that steps 15.1–15.3 test is skipped and nothing proves the checkbox saves |
| 3 | Run every auto-apply case — sections 1, 2, 3, 4, 6, 7, 9, 10, 13, 14 | the payer is now AUTO, so a 0 means a real defect rather than a MANUAL payer behaving correctly |
| 4 | Untick when finished; confirm a new qualifying shipment stays unflagged | VDT-3384 §B2 step 5 — the reversibility proof Sales relies on |
If a case returns siunt_4rp = 0 where you expected 1, check the payer’s switch before raising anything: a MANUAL payer returning 0 is the feature working, and it is indistinguishable from the feature being broken. SELECT kl_id, kl_pav, kl_4rp_auto FROM tbl_klientai WHERE kl_id = <your client id>;
Client ids are consistent between dev and uat — verified on demovenipak_new 2026-08-26 — so whatever id works locally is the same id on uat. On uat there are exactly 4 AUTO clients out of 166 561 (3464 JYSK BALTIC, 89398 ROMITEKA, 157673 ELECTRONIC TRADE, 158280 PRO TRADE), which is the seeded cutover set. Leave that count at 4 when the round ends.
Cron: pack_4rp sweeps yesterday+today nightly. Every case below that says “run the cron” means a manual trigger — ask the developer rather than waiting overnight.
Each suite was run against origin/uat + the branch, not the branch alone.
| repo | suite | result |
|---|---|---|
| pakas | PHPUnit in ss1-web vs venipak_dev | 144 tests, 297 assertions, 0 failures |
| siunta | PHPUnit, helper 0.9.2 + veniship-client 3.0.0 freshly resolved | 91 tests, 370 assertions, 0 failures |
| siunta | Playwright smoke + functional | 48 passed · 3 failed, all cleared below |
| go-import | PHPUnit | 70 tests, 78 assertions, 0 failures |
| go-import | veni-tests Jest API | 36 passed, 8 skipped |
| pakas | Playwright smoke + functional | 92 passed, 15 failed — identical 15 on plain origin/uat, 0 regressions |
| all four | veni-guard PR guards, permissive | 42 ran, 0 block-fails per repo |
| failure | verdict | proof |
|---|---|---|
siunta baltic-pickup-browse BALTIC-3 | pre-existing | fails identically on plain origin/uat; the spec guards VDT-3430, which is on neither branch |
siunta pudo-picker PUDO-UI-1 | pre-existing | same baseline run; the spec's own comment cites VDT-3430 |
siunta shipment TC10 | flaky | re-ran 3× on the merge result: 3/3 pass. The first run raced the stress-project shipments |
| pakas Playwright, 15 tests | environment gap | the same 15 fail on plain origin/uat — PAKAS_URL points at localhost:8001, absent from this stack |
Direct 4H functional evidence: all five four-hands-notice.spec.ts cases (VDT-3407 — notice, badges, the 25.99/26.00 kg inclusive floor, PL-receiver removal) passed on the siunta merge result.
npx playwright test tests/siunta/ sweeps three projects including siunta-stress, which creates 3000 shipments across 30 parallel sessions in the shared dev DB. It was stopped part-way and the two required projects (smoke, functional) were run explicitly instead. A partial set of stress shipments was created in venipak_dev and adds to the outstanding cleanup.Layout note 2026-08-28: the kl_view “4H AUTO” row moved mid-round from the left client card into the all-regions price table (PRs #634 uat / #635 dev, merged; live on gouat) — do not chase it in the left card; it shows only for kl_edit_plius viewers on clients that have a price list. Case 15.8 reworded accordingly; four-hands siunta specs unaffected.
Same veni-tests suites, this time run against the deployed uat apps on gouat (TEST_ENV=uat, .env.uat created 2026-08-27), plus three targeted 4H XML imports. Payer for API imports = the login’s uat client (1 · VENIPAK KAUNAS & MORE, MANUAL), so only MANUAL-side 4H behaviour is provable this way; AUTO-payer cases wait on a client-card ruling, cron cases on the P7 trigger.
| app | suite | result |
|---|---|---|
| siunta | Playwright smoke (gouat) | 20 passed |
| siunta | Playwright functional (gouat) | 28 passed · 3 failed + 1 skipped, all cleared below |
| go-import | Jest API (gouat, real XML posts) | 36 passed, 8 deliberately skipped — equals the dev baseline |
| pakas | Playwright smoke (gouat) | 8 passed |
| pakas | Playwright functional (gouat) | 99 passed — the 15 local “env fails” do not occur against the real host |
| failure | verdict | proof |
|---|---|---|
siunta four-hands-notice test 1 | spec artifact | the notice IS shown with the correct full English text (“…handed over to the recipient at the courier’s vehicle, without delivery to the door”); the spec asserts the Lithuanian substring and the uat session renders EN. Functionally 12.1 passes — and doubles as EN-translation evidence for 12.13 |
siunta baltic-pickup-browse BALTIC-3 | pre-existing | guards VDT-3430, ancestry-proven absent from the uat tip — same disposition as the 08-22/08-27 local runs |
siunta pudo-picker PUDO-UI-1 (+UI-2 serial-skipped) | pre-existing | same VDT-3430 gap (Helsinki auto-expand) |
| probe | scenario | expected | observed |
|---|---|---|---|
| T-A | LT→LT 40 kg, allowlisted postcode, no tag, MANUAL payer | 0 | 0 — siunt 24478867 (case 5.1, creation channel) |
| T-B | <four_hands>1</four_hands>, 10 kg | 1 | 1 — siunt 24478868 (case 8.2) |
| T-C | <four_hands>0</four_hands>, 40 kg | 0 | 0 — siunt 24478869 (case 8.1, MANUAL-payer variant) |
Post-run DB check (read-only): 26 suite shipments + 3 probes created today for payer 1 — 0 spuriously flagged; tbl_log_siunt_4rp still 0 rows (creation-time initial values do not write provenance; the log records changes). Environment traps hit and fixed exactly as the list predicted: uat GO_IMPORT_USER_ID is the login’s user_id 17802 (dev 20579 / stress 17766 are both wrong — manifest TITLE prefix must equal it), and PAKAS_URL needs a trailing slash. Both fixes live only in .env.uat.
With the owner’s say-so, client 1 (the shared uat login’s payer) was set 4H AUTO through the pakas client-card UI (that is case 15.4 itself), the §1–§8 creation-channel matrix was driven through real XML imports and the siunta form, and the card was unticked again (case 15.5, proven by siunt 24478914 staying 0). Final state re-verified: exactly the canonical 4 AUTO clients remain. All flags checked in the DB, read-only. The pack_4rp cron did not run during the window and payer-1 shipments are MANUAL again, so the nightly sweep will not disturb the evidence.
| scope | result |
|---|---|
| LT band boundaries 25.9 / 26.0 / 40 / 75.0 / 75.1 (§1) | 5/5 flags exact (siunt 24478870–74) |
| LV+EE boundaries 30.9 / 31.0 / 90.0 / 90.1 / EE 26 (§2) | 6/6 flags exact (24478875–76, 24478887–90) |
| Volume irrelevance (§3), multi-pack never-summed (§4) | 5/5 (24478877–81) |
| Gates: PL sender / PL receiver / pickup receiver / disabled postcode (§5) | 4/4 stay 0 (24478882, 24478892, 24478895, 24478891) |
| Explicit decision (§8): fh=0 under AUTO · empty tag · omitted tag | 3/3 — incl. the full VDT-3403 proof (24478886 stays 0) |
| siunta form: create 40 kg → immediate 1 (6.1) · edit 20→40 flips (6.3) | 2/2 (24478897, 24478899) |
| Client-card switch (§15): render / tick / untick / kl_view row | 15.1, 15.4, 15.5, 15.8 |
| # | observed | root cause | decision needed |
|---|---|---|---|
| F-1 (6.4) | Editing an auto-flagged shipment’s weight 40→20 kg keeps siunt_4rp=1; unticking the box during the edit does clear it (24478897 → 0) | the edit form pre-renders the box ticked from the existing flag; on save rp4=1 posts and isset($_POST['rp4']) reads it as a client order | accept (weight-drop clearing is the §10 re-weigh hook’s job) or ticket a siunta-side auto-clear |
| F-2 (8.5) | An AUTO payer cannot decline 4H through the siunta form — a qualifying 40 kg save with the box unticked still stores 1 (24478898) | an unchecked checkbox posts nothing; !isset($_POST['rp4']) then triggers auto-apply — “deliberately unticked” and “untouched” are one POST state (same family as the fixed go-import defect A, where the tag carries the distinction) | accept-and-document for Phase 1 (decline channels: API fh=0, or Sales flips the card) or ticket a tri-state UI |
Also observed, consistent with design: siunta-side flag changes (client edits) write no tbl_log_siunt_4rp rows — the provenance log’s writers are pakas-side (cron / siunt_edit / re-weigh); creation-time initial values do not log either. Log row count after the whole round: 0. Language note: the 4H notice rendered correct in EN in one session and LT in another (12.13 partial evidence).
Derived by diffing origin/uat against each merge result, not from ticket intent. The nine tickets’ own validation steps are subsets of this. The Result column is deliberately empty — it is filled by the QA round, not by the developer.
| env | url |
|---|---|
| pakas | https://gouat.venipak.lt/pakas2 |
| siunta | https://gouat.venipak.lt/siunta2 |
| go-import | https://gouat.venipak.lt/import — XML POST endpoint …/import/send_auth_basic.php |
| # | check | expected | ticket | result |
|---|---|---|---|---|
| P1 | SELECT posti_zone, COUNT(*), SUM(posti_service1='1') FROM tbl_post_info WHERE posti_salis='LT' GROUP BY posti_zone; | enabled == total on every LT zone | VDT-3413 | PASS probe 08-27 |
| P2 | SHOW COLUMNS FROM tbl_klientai LIKE 'kl_4rp_auto'; | exists, tinyint(1), default 0 | VDT-3384 | PASS probe 08-27 |
| P3 | SELECT kl_id, kl_pav, kl_4rp_auto FROM tbl_klientai WHERE kl_4rp_auto=1; | exactly 4 rows — 3464 JYSK BALTIC · 89398 ROMITEKA · 157673 ELECTRONIC TRADE · 158280 PRO TRADE | VDT-3384 | PASS probe 08-27 |
| P4 | SHOW CREATE TABLE tbl_log_siunt_4rp; | exists, MyISAM, s4rp_user_id int(8) unsigned, s4rp_source enum('auto','manual') | VDT-3395 | PASS probe 08-27, 0 rows |
| P5 | helper version resolved on uat | pakas ≥ 0.8.3, siunta ≥ 0.9.1 | VDT-3203 | git PASS deployed resolution unverified |
| P6 | siunta asset VERSION | 50 — otherwise the new JS is cached and section 12 fails for the wrong reason | VDT-3407 | PASS version=50 on a logged-in page 08-27 |
| P7 | pack_4rp cron scheduled, last run clean | no error in the cron log | VDT-3203 | OPEN awaiting dev |
| # | case | expected | result |
|---|---|---|---|
| 1.1 | one pack 25.9 kg | siunt_4rp stays 0 | PASS auto 08-27 (creation channel) |
| 1.2 | one pack 26.0 kg | becomes 1 | PASS auto 08-27 (creation channel) |
| 1.3 | one pack 40.0 kg | 1 | PASS auto 08-27 (creation channel) |
| 1.4 | one pack 50.0 kg | 1, single charge | |
| 1.5 | one pack 50.1 kg | 1, 2× charge | |
| 1.6 | one pack 75.0 kg | 1, 2× charge | flag PASS auto 08-27; charge = §16 manual |
| 1.7 | one pack 75.1 kg | 0 — above the ceiling there is no 4H at all | PASS auto 08-27 (creation channel) |
| # | case | expected | result |
|---|---|---|---|
| 2.1 | LV receiver, 30.9 kg | 0 | PASS auto 08-27 (creation channel) |
| 2.2 | LV receiver, 31.0 kg | 1 | PASS auto 08-27 (creation channel) — LV and EE both |
| 2.3 | LV receiver, 60.0 kg | 1, single charge | |
| 2.4 | LV receiver, 60.1 kg | 1, 2× | |
| 2.5 | LV receiver, 90.0 kg | 1, 2× | flag PASS auto 08-27; charge = §16 manual |
| 2.6 | LV receiver, 90.1 kg | 0 | PASS auto 08-27 (creation channel) |
| 2.7 | EE receiver, 26.0 kg | 0 — EE does not use the LT floor | PASS auto 08-27 (creation channel) |
| # | case | expected | result |
|---|---|---|---|
| 3.1 | LT pack 30 kg, volume below 0.179 m³ | 1 — the old rule required volume > 0.179 and would have said 0 | PASS auto 08-27 (creation channel) |
| 3.2 | LT pack 20 kg, volume above 0.179 m³ | 0 — weight alone decides | PASS auto 08-27 (creation channel) |
| # | case | expected | result |
|---|---|---|---|
| 4.1 | two packs, 20 kg + 20 kg (sum 40) | 0 — weights are never summed | PASS auto 08-27 (creation channel) |
| 4.2 | two packs, 10 kg + 30 kg | 1 — one pack in band is enough | PASS auto 08-27 (creation channel) |
| 4.3 | three packs, one 80 kg (over ceiling) + one 40 kg | 1 — the in-band pack qualifies | PASS auto 08-27 (creation channel) |
| 4.4 | one 40 kg pallet-flagged pack + one 40 kg normal pack | 1 — pallet pack stripped, the other qualifies |
| # | case | expected | result |
|---|---|---|---|
| 5.1 | MANUAL payer — card shows 4H AUTO unticked | 0 | PASS creation via go-import (siunt 24478867); cron variant pending trigger |
| 5.2 | sender PL | 0 | PASS auto 08-27 (creation channel) |
| 5.3 | receiver PL | 0 | PASS auto 08-27 (creation channel) |
| 5.4 | sender PL → receiver DE | 0 | |
| 5.5 | pallet — pack_paletes_tipas != '0' | 0 | |
| 5.6 | receiver name contains VENIPAK LOCKER | 0 | PASS by construction — import rejects >25 kg to a locker outright, an in-band locker 4H cannot exist on this channel; cron/siunta variants pending |
| 5.7 | receiver name contains VENIPAK PICKUP | 0 | PASS auto 08-27 — real pickup point, 40 kg, stays 0 (siunt 24478895) |
| 5.8 | receiver postcode with posti_service1 != '1' | 0 | PASS auto 08-27 (disabled LV postcode) |
| 5.9 | non-Venipak locker (e.g. an LP Express point) | 1 is acceptable — name-match only, out of scope. Record, do not fail |
| # | case | expected | result |
|---|---|---|---|
| 6.1 | siunta shipment form, AUTO payer, LT 40 kg | siunt_4rp = 1 immediately, before any cron | PASS auto 08-27 (siunt 24478897); the rp4 box does NOT auto-tick — the flag is server-side |
| 6.2 | siunta form, MANUAL payer | 0 immediately, and still 0 after the cron | |
| 6.3 | siunta shipment edit — create 20 kg, edit to 40 kg | flips to 1 on save | PASS auto 08-27 (siunt 24478899) |
| 6.4 | siunta edit downward — create 40 kg (auto-flagged), edit to 20 kg | flag cleared | FINDING F-1 stays 1 — the pre-ticked box re-posts as an order; unticking during the edit DOES clear it. See findings below |
| 6.5 | siunta XLS import, one qualifying row, AUTO payer | 1 on the imported shipment | |
| 6.6 | same via the inc.import.xls.2 path | 1 | |
| 6.7 | go-import API, qualifying shipment, AUTO payer | 1 | PASS auto 08-27 (siunt 24478872) |
| 6.8 | insert a qualifying shipment directly, bypassing the channels | the cron backstop flags it | |
| 6.9 | the same shipment data through 6.1, 6.5 and 6.7 | identical siunt_4rp in all three |
| # | case | expected | result |
|---|---|---|---|
| 7.1 | depot hand-off routing, true consignee in LT, 40 kg | evaluated against the consignee → 1 | |
| 7.2 | same but true consignee in PL | 0 — previously the depot's own LT address was read and this wrongly qualified |
| # | case | expected | result |
|---|---|---|---|
| 8.1 | go-import <four_hands>0</four_hands>, otherwise qualifying, AUTO payer | stays 0 — the VDT-3403 defect | PASS full — AUTO payer, fh=0, 40 kg stays 0 (siunt 24478886). The VDT-3403 fix holds on uat |
| 8.2 | go-import <four_hands>1</four_hands>, pack 10 kg | 1 — client ordered it | PASS siunt 24478868 |
| 8.3 | go-import empty <four_hands></four_hands> | treated as absent → auto-apply runs → 1 | PASS auto 08-27 (creation channel) |
| 8.4 | go-import tag omitted entirely | auto-apply runs → 1 | PASS auto 08-27 (creation channel) |
| 8.5 | siunta form, 4H box deliberately unticked on a qualifying pack | stays 0, and still 0 after the cron | FINDING F-2 AUTO payer: flagged 1 — an unticked box posts nothing, so a decline cannot be expressed; holds only for MANUAL payers. See findings below |
| 8.6 | siunta form, 4H box ticked on a non-qualifying 10 kg pack | 1, and the cron does not clear it |
| # | case | expected | result |
|---|---|---|---|
| 9.1 | cron flags a shipment | one row: source=auto, old 0, new 1, user id, timestamp | |
| 9.2 | operator sets 4H in siunt_edit | one row: source=manual, correct user id | |
| 9.3 | operator clears 4H in siunt_edit_all | row with old 1 → new 0 | |
| 9.4 | order of writes | the log INSERT precedes the flag UPDATE — never flagged without a log row | |
| 9.5 | log write fails (ask dev to simulate) | the flag change is aborted, loudly; no silent flip | |
| 9.6 | a cron run that flags nothing | writes nothing |
| # | case | expected | result |
|---|---|---|---|
| 10.1 | auto-flagged 40 kg pack re-weighed to 20 kg | flag cleared, log row records it | |
| 10.2 | unflagged 20 kg pack re-weighed to 40 kg, AUTO payer | flag set | |
| 10.3 | client-ordered 4H, pack re-weighed to 10 kg | flag kept — a client order is never cleared by re-weigh | |
| 10.4 | manually-set 4H, pack re-weighed out of band | flag kept | |
| 10.5 | re-weigh within band, 40 → 45 kg | flag unchanged, no duplicate log spam | |
| 10.6 | re-weigh to 80 kg (over ceiling), previously auto-flagged | flag cleared |
| # | case | expected | result |
|---|---|---|---|
| 11.1 | SplitLatePackageCommand moves a pack off a 4H shipment | provenance copied to the destination; source re-evaluated | |
| 11.2 | split leaves the origin with only sub-floor packs | origin's auto flag clears | |
| 11.3 | split moves a qualifying pack onto a non-4H shipment | destination gains the flag with correct provenance |
VERSION=50 and hard-reload first)| # | case | expected | result |
|---|---|---|---|
| 12.1 | MANUAL client, qualifying pack, 4H not ordered, press Skaičiuoti | notice appears — handed over at the vehicle, not to the door | PASS auto 08-27 (EN session; full EN text correct) |
| 12.2 | 4H ordered | no notice | PASS auto 08-27 |
| 12.3 | non-qualifying pack | no notice | PASS auto 08-27 |
| 12.4 | badge, 4H ordered | red 4H badge on that pack | PASS auto 08-27 |
| 12.5 | badge, qualifies but not ordered | black no 4H badge | PASS auto 08-27 |
| 12.6 | live repaint — weight 20 → 40 kg | badge appears without reload | PASS auto 08-27 |
| 12.7 | live repaint — flag as pallet | badge disappears | |
| 12.8 | live repaint — switch to envelope | badge disappears | |
| 12.9 | boundary in the UI | 25.99 kg → no badge; 26.00 kg → badge | PASS auto 08-27 |
| 12.10 | PL receiver | badges and notice removed | PASS auto 08-27 |
| 12.11 | over-ceiling pack, LT 80 kg | the over-ceiling message, not a 4H badge | |
| 12.12 | tooltip content | LT 26–75 doubling above 50; LV/EE 31–90 doubling above 60; the 22-city serviced list | |
| 12.13 | languages | LT, EN, LV, ET, RU, PL — notice, badges and tooltip all translated, no raw keys | partial: notice correct in EN and LT (two sessions); other languages + tooltip pending |
…cm.php, …cm_new.php, …_ee48.php)| # | case | expected | result |
|---|---|---|---|
| 13.1 | 4H kaina column | present, money per shipment, matches the payer's price list | |
| 13.2 | Kiek 4H footer | count equals the number of 4H rows shown | |
| 13.3 | Neužsakytos 4 rankos filter | only shipments that qualify and were not ordered | partial: the filter link renders on …cm.php; value-level check manual (multi-minute query) |
| 13.4 | filter off | full list returns, no rows lost | |
| 13.5 | row indicator, LT 60 kg | shows the 2×4H cue (over LT's 50) | |
| 13.6 | row indicator, LV 55 kg | single 4H — under LV's 60. A country-blind implementation passes 13.5 and fails only here | |
| 13.7 | sorting / paging with the new column | no layout break, no SQL error | |
| 13.8 | empty result | column and footer render cleanly with no rows |
| # | case | expected | result |
|---|---|---|---|
| 14.1 | stop with 4H ordered | green bubble | |
| 14.2 | stop that qualifies, not ordered | red bubble | |
| 14.3 | weights on red stops | rendered red | |
| 14.4 | side list | qualifying-unordered stops listed | |
| 14.5 | route aggregate count | counts only unordered qualifiers — ordered stops must not inflate it | |
| 14.6 | mixed stop, one ordered + one unordered shipment | per the documented rule; record actual behaviour | |
| 14.7 | jquery.routing.php payload | contains shipment_4rp and shipment_4rp_weight per shipment | |
| 14.8 | payload values | shipment_4rp matches siunt_4rp; shipment_4rp_weight matches the heaviest qualifying pack | |
| 14.9 | route with no 4H | bubbles normal, aggregate 0, payload keys present with 0 values | |
| 14.10 | scanner / driver view | consumes the new keys without error — courier-team owned, report, do not fix |
| # | case | expected | result |
|---|---|---|---|
| 15.1 | kl_edit_plius user, client edit | 4H AUTO checkbox renders, with the hint text | PASS auto 08-27 |
| 15.2 | non-plius user, client edit | checkbox not rendered | |
| 15.3 | non-plius user hand-posts kl_4rp_auto=1 | ignored — client stays MANUAL | |
| 15.4 | plius user ticks AUTO, saves | kl_4rp_auto = 1; a qualifying shipment then auto-flags | PASS auto 08-27 — ticked via UI; the whole §1–§5 matrix then flagged correctly |
| 15.5 | plius user unticks | back to 0; new qualifying shipments stay 0 | PASS auto 08-27 (siunt 24478914 stays 0 after untick) |
| 15.6 | new client via kl_add, non-plius creator | created MANUAL (0) | |
| 15.7 | new client via kl_add, plius creator, box ticked | created AUTO (1) | |
| 15.8 | kl_view | read-only “4H AUTO” row renders in the all-regions price table (between “COD keitimo mokestis” and “Siuntos duomenų įvedimas rankiniu būdu”), kl_edit_plius viewers only; non-plius viewers — and clients without a price list — see none. Moved 2026-08-28 (PR #634; supersedes the “visible to all” wording) | PASS live on gouat 2026-08-28 post-merge (client 3464: row once, ruled position, two-line taip/ne hint) |
| 15.9 | save an unrelated client field | kl_4rp_auto unchanged |
CONFIG_2X4RP_LOW_LIMIT 80 → 50)| # | case | expected | result |
|---|---|---|---|
| 16.1 | pre-existing siunt_4rp=1, LT pack 60 kg | now prices at 2× — it priced single before the merge | |
| 16.2 | LT pack 45 kg, flagged | single | |
| 16.3 | LV pack 55 kg, flagged | single — LV doubles above 60, not 50 | |
| 16.4 | LV pack 65 kg | 2× | |
| 16.5 | EE pack 65 kg | 2× | |
| 16.6 | print_courier_load_list | 2× shown at the country-correct threshold | |
| 16.7 | print_courier_load_out2 | same | |
| 16.8 | inc.siuntos.nurodymai indicator | 2× cue at the country-correct threshold | |
| 16.9 | parcel label direction | “2x4 hands” at the country-correct threshold (helper DirectionProvider) | |
| 16.10 | tariff sanity on a default price list | bills 50 € / 100 €. Expected — VDT-3415 has not run. Record the amount, do not raise a defect |
| # | case | expected | result |
|---|---|---|---|
| 17.1 | run the cron twice over the same day | second run flags 0 additional rows, no duplicate log rows | |
| 17.2 | pre-existing siunt_4rp=1 outside the new criteria | preserved — no retro-clearing of historical flags | |
| 17.3 | historical shipments | no mass recalculation | |
| 17.4 | shipment registration with no 4H involved | unchanged end-to-end | PASS smoke CRUD TC7–TC10 |
| 17.5 | XLS import of a non-4H file | imports exactly as before | |
| 17.6 | go-import of a non-4H shipment | unchanged response | PASS API suite 36/36 |
| 17.7 | terminal reports without the filter | same rows and totals as before the merge | pages render (31 report checks green); totals not compared |
| 17.8 | routing map for a non-4H route | unchanged | |
| 17.9 | error log across the whole round | no new PHP warnings/notices from 4H code paths | |
| 17.10 | client card save, unrelated client | no error |
| # | item | why |
|---|---|---|
| 18.1 | 4H bills 50 €/100 € on default price lists | VDT-3415 tariff seeding not run on any environment |
| 18.2 | only 4 clients on AUTO | by design — kl_4rp_auto defaults to 0 |
| 18.3 | non-Venipak lockers not excluded | receiver-name match only, Phase 1 scope |
| 18.4 | veniship shows no 4H behaviour | excluded from this window |
| 18.5 | venicourier / vpcourier-api / vpdriver-api | courier team, not in this release |
| 18.6 | bands duplicated in three repos + vpdriver-api | VDT-3396 deferred |
| 18.7 | go-import tests/ present in the web root | known deploy-hygiene item; they cannot run on uat |
| 18.8 | auto_weighed missing from s4rp_source | the re-weigh-origin change is not in this release |
| ticket | scope | sections |
|---|---|---|
| VDT-3203 | pakas cron + bands | 1, 2, 3, 4, 5, 6.8, 16, 17.1, 17.2 |
| VDT-3384 | client AUTO/MANUAL | P2, P3, 5.1, 15 |
| VDT-3395 | provenance + re-weigh | P4, 9, 10, 11 |
| VDT-3204 | siunta order/import paths | 1, 2, 4, 5, 6.1–6.6, 6.9, 7, 8.5, 8.6 |
| VDT-3202 | go-import validation | 2, 5, 6.7, 6.9 |
| VDT-3403 | explicit decline | 8.1–8.4 |
| VDT-3407 | client-facing UI | P6, 12 |
| VDT-3408 | reports & filters | 13, 14.5, 17.7 |
| VDT-3410 | route & stop visibility | 14 |
Test data needed: one AUTO client (3464), one MANUAL client, LT/LV/EE/PL sender and receiver addresses on posti_service1='1' postcodes, a pallet-flagged pack, receivers named VENIPAK LOCKER and VENIPAK PICKUP, one pre-existing siunt_4rp=1 shipment with a 60 kg LT pack (for 16.1 and 17.2), and a Baltic-domestic price list with kain_4rp still at 50.00.
Everything below predates the uat cut and was captured against godev.venipak.lt / venipak_dev. Kept as provenance for how the rules were proven; it is not UAT evidence.
Everything below ran against the merged dev branches (pakas 5572933 · siunta 0c0d90c9 · go-import 1ec026a) and the live venipak_dev database, after the dev-sync merges, the INT(8) DDL alignment and the pipeline auto-deploys.
| Suite | Scope | Result |
|---|---|---|
| pakas PHPUnit | unit + integration incl. PPSchedule, FourHandsReweigh, routing payload contracts | 136 / 136 |
| siunta PHPUnit | unit + integration, 4H bands/notice logic | 91 / 91 |
| go-import PHPUnit | import validation, declared-capture, auto-4H guard | 70 / 70 |
| go-import API (veni-tests, Jest) | Baltic-domestic contract vs godev | 36 / 36 (+8 FI/MH describe.skip — out of scope by ruling) |
| siunta Playwright (smoke + functional) | 47 UI tests incl. the 5-case 4H notice/badge spec | 45 ✓ + PUDO-UI-1 env-fail (MH picker :8004 down — VDT-3250, constant) + its dependent skip |
| pakas Playwright (smoke + functional) | back-office page coverage | 107 / 107 — run against godev with the siunta admin login (owner-directed; the two ss1 apps share tbl_user — .env.local's PAKAS_ADMIN_* keys are present but empty). First attempts failed for two reasons worth recording: the empty keys silently fall back to dummy creds, and godev itself was unreachable for ~1 h mid-window. |

Click the image to view full size

Click the image to view full size

Click the image to view full size

Click the image to view full size

Click the image to view full size

Click the image to view full size
Not captured: the 12-month status report carrying the "Neužsakytos 4 rankos" sort — the page exceeds even a 5-minute render budget on both godev and the local instance — the demo DB holds 61k "in-terminal" packs that never advance status, and the report runs a correlated subquery per row (a pre-existing dataset/report characteristic, unrelated to 4H); its live render was verified in the 2026-08-06 pass.
Two false alarms run down during this pass (documented so nobody re-chases them): (1) go-import API "credential failures" — TEST_ENV=dev loads .env.dev, which carries no import creds; the suite's real environment is TEST_ENV=local. (2) a 4H price-estimate timeout — a local-lab-only vendor inconsistency (pre-rename helper Price.php vs a 0.9.2-synced FourHandsWeightBands); server vendors are composer-resolved and consistent, dev was never affected.
Every rule that decides whether a client can order 4H (or whether the system flags it), driven through the real siunta form on the merged dev code by an authenticated Playwright session. Badges: black no 4H = pack weight-qualifies but the service is not ordered; red 4H = ordered.
| Gate / rule | Case driven | Observed |
|---|---|---|
| LT band floor (inclusive 26 kg) | 25.99 kg vs 26.00 kg | no badge → badge |
| LT hard ceiling (75 kg) | 80 kg pack | no badge — 4H impossible above ceiling |
| 2×4H tier (LT >50 kg) | 55 kg + 4H ticked + Skaičiuoti | red 4H badge · doubled price rendered (27.45 €) |
| LV band floor (inclusive 31 kg) | 30 kg vs 31 kg, LV receiver, enabled postcode | no badge → badge |
Postcode allowlist (posti_service1) | LV receiver on a 4H-disabled postcode (1053) | 4H checkbox disabled |
| Country gate (Baltic only) | eligible 40 kg, receiver switched LT → PL | checkbox disabled + badge removed |
| OOH receiver (pickup/locker) | receiver named "VENIPAK PICKUP …", 30 kg | badges suppressed |
| Late-window gate | delivery time 18–22 selected | 4H checkbox disabled |
| Pallet exclusion | per-pack row, pallet type selected | badge suppressed on that pack |
| Notice on Skaičiuoti | 30 kg, 4H not ordered | notice shown; suppressed once 4H ticked |

Click the image to view full size

Click the image to view full size

Click the image to view full size

Click the image to view full size

posti_service1=0 — the 4H checkbox greys out.Click the image to view full size

Click the image to view full size

Click the image to view full size

Click the image to view full size

Click the image to view full size

Click the image to view full size
Not UI-capturable on this account/dataset: the AUTO-client badge (badge pre-set to red "4H" for kl_4rp_auto payers — the test login is deliberately MANUAL; the cron/client-card halves of that flow are evidenced in sections 1–2 below) and the creation-time strip in go-import (API-level, covered by the XML matrix in section 4 and the 36/36 Baltic contract suite).
The real pack_4rp.php from the dev branch, executed against the dev database. Test shipments were created, their flags cleared, and the payer switched to AUTO — the cutover scenario Sales will actually perform.
| Run | Condition | Result | Verdict |
|---|---|---|---|
| 1 | Payer AUTO, 5 shipments, flags cleared | 30 kg → 1 · 55 kg → 1 · 20 kg → 0 · 80 kg → 0 | band + ceiling honoured |
| 2 | Immediate re-run, no changes | identical — no rows altered | idempotent |
| 3 | Payer switched back to MANUAL, flags cleared | nothing flagged | fails closed on the client switch |
Also learned: the cron only considers shipments whose siunt_priemimo_data falls in the last two days. Freshly created shipments carry 0000-00-00 until pickup is registered, so they are invisible to it — creation-time application in siunta/go-import and the cron cover different moments, and both are needed.
The switch is gated behind the kl_edit_plius right (a commercial field, per Product). Enabled and disabled through the real form, reloading each time to prove persistence — not written directly to the database.
Click the image to view full size
kl_4rp_auto = 1.Click the image to view full size
The switch was returned to MANUAL through the same form afterwards; dev is back to exactly 4 AUTO clients.
| Case | Payer | Weight | Expected | Stored |
|---|---|---|---|---|
| Default client | MANUAL | 30 kg | no 4H | 0 ✓ |
| In band | AUTO | 30 kg | 4H applied | 1 ✓ |
| Below LT floor 26 | AUTO | 20 kg | no 4H | 0 ✓ |
| 2×4H tier (>50) | AUTO | 55 kg | 4H applied | 1 ✓ |
| Above LT ceiling 75 | AUTO | 80 kg | no 4H | 0 ✓ |
Click the image to view full size
kl_4rp_auto=1 differs → 4H applied automatically.Click the image to view full size

Click the image to view full size

Click the image to view full size

Click the image to view full size
Each scenario posts real XML to /import/send_auth_basic.php on dev and the stored flag is read back from the database. Expand any row for the exact request and response.
| Key | Scenario | Expected | Actual | Verdict |
|---|---|---|---|---|
auto-30 | AUTO payer · 30 kg · four_hands not sent | auto-applied → siunt_4rp = 1 | siunt_4rp = 1 | as specified |
auto-20 | AUTO payer · 20 kg · four_hands not sent | not applied → 0 | siunt_4rp = 0 | as specified |
auto-55 | AUTO payer · 55 kg · four_hands not sent | auto-applied → 1 | siunt_4rp = 1 | as specified |
auto-80 | AUTO payer · 80 kg · four_hands not sent | not applied → 0 | siunt_4rp = 0 | as specified |
explicit-1 | Explicit four_hands=1 · 10 kg | honoured → 1 | siunt_4rp = 1 | as specified |
explicit-0 | Explicit four_hands=0 · 30 kg | declined → 0 | siunt_4rp = 1 | deviates |
bad-postcode | Explicit four_hands=1 → 4H-disabled postcode | silently stripped → 0, import still succeeds | siunt_4rp = 0 | as specified |
lv-35 | AUTO payer · LV destination · 35 kg | auto-applied → 1 | import rejected | blocked |
lv-25 | AUTO payer · LV destination · 25 kg | not applied → 0 | import rejected | blocked |
ooh-name | AUTO payer · 30 kg · receiver named VENIPAK PICKUP | excluded by the cron → 0 | siunt_4rp = 1 | deviates |
auto-30 — AUTO payer · 30 kg · four_hands not sent as specifiedClient is opted in and the weight is inside the LT band 26–75 kg, so the system decides.
<description type="1"> <manifest title="20579260808300"> <shipment> <consignee> <name>4H XML auto-30</name> <country>LT</country> <city>Vilnius</city> <address>Gedimino pr. 1</address> <post_code>01103</post_code> <contact_tel>+37061234567</contact_tel> </consignee> <pack> <pack_no>V20579E2546731</pack_no> <weight>30</weight> </pack> </shipment> </manifest> </description>
<?xml version="1.0" encoding="UTF-8"?>
<answer type="ok">
<text>V20579E2546731</text></answer>auto-20 — AUTO payer · 20 kg · four_hands not sent as specifiedBelow the LT floor of 26 kg.
<description type="1"> <manifest title="20579260808301"> <shipment> <consignee> <name>4H XML auto-20</name> <country>LT</country> <city>Vilnius</city> <address>Gedimino pr. 1</address> <post_code>01103</post_code> <contact_tel>+37061234567</contact_tel> </consignee> <pack> <pack_no>V20579E2546732</pack_no> <weight>20</weight> </pack> </shipment> </manifest> </description>
<?xml version="1.0" encoding="UTF-8"?>
<answer type="ok">
<text>V20579E2546732</text></answer>auto-55 — AUTO payer · 55 kg · four_hands not sent as specifiedInside the band and above the 50 kg doubling threshold (2×4H tier).
<description type="1"> <manifest title="20579260808302"> <shipment> <consignee> <name>4H XML auto-55</name> <country>LT</country> <city>Vilnius</city> <address>Gedimino pr. 1</address> <post_code>01103</post_code> <contact_tel>+37061234567</contact_tel> </consignee> <pack> <pack_no>V20579E2546733</pack_no> <weight>55</weight> </pack> </shipment> </manifest> </description>
<?xml version="1.0" encoding="UTF-8"?>
<answer type="ok">
<text>V20579E2546733</text></answer>auto-80 — AUTO payer · 80 kg · four_hands not sent as specifiedAbove the LT ceiling of 75 kg — too heavy for the service.
<description type="1"> <manifest title="20579260808303"> <shipment> <consignee> <name>4H XML auto-80</name> <country>LT</country> <city>Vilnius</city> <address>Gedimino pr. 1</address> <post_code>01103</post_code> <contact_tel>+37061234567</contact_tel> </consignee> <pack> <pack_no>V20579E2546734</pack_no> <weight>80</weight> </pack> </shipment> </manifest> </description>
<?xml version="1.0" encoding="UTF-8"?>
<answer type="ok">
<text>V20579E2546734</text></answer>explicit-1 — Explicit four_hands=1 · 10 kg as specifiedClient declares 4H on a light parcel. A declared request is always honoured, regardless of weight.
<description type="1"> <manifest title="20579260808304"> <shipment> <consignee> <name>4H XML explicit-1</name> <country>LT</country> <city>Vilnius</city> <address>Gedimino pr. 1</address> <post_code>01103</post_code> <contact_tel>+37061234567</contact_tel> </consignee> <attribute> <four_hands>1</four_hands> </attribute> <pack> <pack_no>V20579E2546735</pack_no> <weight>10</weight> </pack> </shipment> </manifest> </description>
<?xml version="1.0" encoding="UTF-8"?>
<answer type="ok">
<text>V20579E2546735</text></answer>explicit-0 — Explicit four_hands=0 · 30 kg deviatesClient explicitly declines on an otherwise eligible parcel — the client decision must win over the auto rule.
<description type="1"> <manifest title="20579260808305"> <shipment> <consignee> <name>4H XML explicit-0</name> <country>LT</country> <city>Vilnius</city> <address>Gedimino pr. 1</address> <post_code>01103</post_code> <contact_tel>+37061234567</contact_tel> </consignee> <attribute> <four_hands>0</four_hands> </attribute> <pack> <pack_no>V20579E2546736</pack_no> <weight>30</weight> </pack> </shipment> </manifest> </description>
<?xml version="1.0" encoding="UTF-8"?>
<answer type="ok">
<text>V20579E2546736</text></answer>bad-postcode — Explicit four_hands=1 → 4H-disabled postcode as specifiedDestination postcode has posti_service1 = 0, so the service cannot be performed there.
<description type="1"> <manifest title="20579260808306"> <shipment> <consignee> <name>4H XML bad-postcode</name> <country>LT</country> <city>Vilnius</city> <address>Gedimino pr. 1</address> <post_code>99999</post_code> <contact_tel>+37061234567</contact_tel> </consignee> <attribute> <four_hands>1</four_hands> </attribute> <pack> <pack_no>V20579E2546737</pack_no> <weight>30</weight> </pack> </shipment> </manifest> </description>
<?xml version="1.0" encoding="UTF-8"?>
<answer type="ok">
<text>V20579E2546737</text></answer>lv-35 — AUTO payer · LV destination · 35 kg blockedLV band is 31–90 kg, so 35 kg qualifies where the LT rule would also pass. Confirms the per-country floor is applied, not a single global one.
Rejected: code 24 — Consignee POST_CODE has to be in digits and exact legth -> 01981.
<description type="1"> <manifest title="20579260808307"> <shipment> <consignee> <name>4H XML lv-35</name> <country>LV</country> <city>Riga</city> <address>Gedimino pr. 1</address> <post_code>01981</post_code> <contact_tel>+37061234567</contact_tel> </consignee> <pack> <pack_no>V20579E2546738</pack_no> <weight>35</weight> </pack> </shipment> </manifest> </description>
<?xml version="1.0" encoding="UTF-8"?><answer type="error"><error shipment="0" pack="" code="24"><text>Consignee POST_CODE has to be in digits and exact legth -> 01981.</text></error> </answer>
lv-25 — AUTO payer · LV destination · 25 kg blockedBelow the LV floor of 31 kg — and also below LT 26, so it must not qualify under either.
Rejected: code 24 — Consignee POST_CODE has to be in digits and exact legth -> 01981.
<description type="1"> <manifest title="20579260808308"> <shipment> <consignee> <name>4H XML lv-25</name> <country>LV</country> <city>Riga</city> <address>Gedimino pr. 1</address> <post_code>01981</post_code> <contact_tel>+37061234567</contact_tel> </consignee> <pack> <pack_no>V20579E2546739</pack_no> <weight>25</weight> </pack> </shipment> </manifest> </description>
<?xml version="1.0" encoding="UTF-8"?><answer type="error"><error shipment="0" pack="" code="24"><text>Consignee POST_CODE has to be in digits and exact legth -> 01981.</text></error> </answer>
ooh-name — AUTO payer · 30 kg · receiver named VENIPAK PICKUP deviatesOut-of-home delivery: a parcel handed to a pickup point is never carried by two couriers, so it must be excluded.
<description type="1"> <manifest title="20579260808309"> <shipment> <consignee> <name>VENIPAK PICKUP Vilnius</name> <country>LT</country> <city>Vilnius</city> <address>Gedimino pr. 1</address> <post_code>01103</post_code> <contact_tel>+37061234567</contact_tel> </consignee> <pack> <pack_no>V20579E2546740</pack_no> <weight>30</weight> </pack> </shipment> </manifest> </description>
<?xml version="1.0" encoding="UTF-8"?>
<answer type="ok">
<text>V20579E2546740</text></answer>four_hands=0 cannot be expressed through the APIA client on AUTO who explicitly sends <four_hands>0</four_hands> on an eligible parcel still gets the service applied and billed. Verified on dev: scenario explicit-0, 30 kg, returned siunt_4rp = 1.
Cause: the auto-apply guard is !empty($shipmentData['attribute']['four_hands']), and in PHP empty("0") is true. The validator also defaults an absent field to '0', so “not sent” and “explicitly declined” are indistinguishable by the time the guard runs. siunta handles the same situation correctly because it tests isset() on the posted field instead, which separates blank from an explicit zero.
Impact: billing-relevant, but limited to opt-in AUTO clients who actively send a zero.
FIXED — 2026-08-05, go-import PR #57 (VDT-3403, open to dev, 4 reviewers assigned). Presence is now captured at parse time (four_hands_declared, before the coercion) and the auto-apply is guarded on declaration, not value — siunta's isset() approach. Empty tags (<four_hands/>) and non-scalars count as absent, so integrations that always emit the tag keep auto-apply. Proven end-to-end through the real /import/send_auth_basic.php route: pre-fix, scenario explicit-0 stored siunt_4rp = 1 (shipment 32820245); on the fixed code the identical request stores 0 (shipment 32820247), while absent-tag and explicit-1 controls are unchanged. 70 phpunit tests green (regression pinned — reverting the guard fails the new tests, and one pin runs without DB credentials); veni-guard 42 guards / 0 blocking. Pickup/locker (error 136), pallets and the Baltic-domestic-only gates are untouched.
A 30 kg parcel addressed to a receiver named VENIPAK PICKUP Vilnius was flagged for 4H at import (siunt_4rp = 1). The cron excludes exactly this pattern by receiver name; the import path does not replicate that check.
Corrected analysis (2026-08-05, ticketed as VDT-3404): both paths turn out to be name heuristics — there is no structured pickup-point field in the XML import contract at all. The import detects OOH with a comma-anchored match (isPickup() = "venipak pickup,", the canonical generated form; likewise isLocker()), while the cron matches the substring anywhere (%VENIPAK PICKUP%). A canonical pickup order is excluded at import (and an explicit 4H on it errors with 136); only non-canonical free-text names like VENIPAK PICKUP Vilnius (no comma) slip through. Blocked on P11: Evelina confirmed the business rule (no OOH shipment may ever get 4H/2×4H/HF) but explicitly delegated the detection mechanism decision to PO / sys arch — no ruling exists yet, so no code is written. Options on the ticket: align import to the cron's looser match, tighten the cron to canonical forms, or introduce a structured field.
Both LV scenarios were rejected before any 4H logic ran, with error 24 — Consignee POST_CODE has to be in digits and exact legth. The validator requires LV postcodes to be 4 digits, and the table appeared to hold only 5-digit codes.
Downgraded (2026-08-05, ticketed as VDT-3405): tbl_post_info.posti_kodas is MEDIUMINT(5) UNSIGNED ZEROFILL — the "786 five-digit LV rows" are 4-digit values zero-padded for display only. Real LV consignee postcodes in tbl_siuntos are 4-digit at massive scale (e.g. 1067 ×344 832) and pass the validator, which matches both the published import contract ("LV – 4 digits") and the official LV-NNNN format. The test scenarios failed because they sent the padded form 01981; the harness will be corrected to 4-digit codes. LV 4H is therefore NOT blocked on the API route for canonical input. Residual question on the ticket: whether prod error-24 logs show real clients sending LV--prefixed or zero-padded forms — if so, the fix is input normalisation at the validator boundary, never a change to the 4-digit rule (six call sites depend on it).
Evidence captured against https://godev.venipak.lt · dev database venipak_dev · 2026-08-05. All test data and the client switch were restored afterwards; dev holds exactly the 4 seeded AUTO clients.