Bilko — Payment-event obaveze (JOPPD): dizajn, scope odluke, runbook — MC #105742
Bilko — Payment-event obaveze (JOPPD): dizajn, scope odluke, runbook
MC: #105671 (dizajn) + #105741 (Task A) + #105742 (Task B) + #105744 (Finverge BA verifikacija) + #105747 (RS redizajn, otvoreno)
Status (2026-07-15): Task A merged (PR 164, b1417252, azdo/main). Task B PR 165 open (6b9ebe5c), NOT merged — Proveo P2P adversarial verify još u toku (mesh thread otvoren, guard-rail auto-BLOCKED nije pravi verdikt). HR-07/RS-07 katalog i dalje enabled: false.
1. Šta feature radi
Payment-event obaveza je Bilko-ova podsjetnik logika koja se okida na stvarnu isplatu plaće, ne na kalendarski datum kao postojeći mjesečni podsjetnici (npr. MIP-1023, Obrazac 1002).
Tok (HR, implementirano):
POST /payroll/organizations/{orgId}/payslip-runs/mark-paid
{ periodYear, periodMonth, paymentDate }
│ (V123: payslips.payment_date, SERIALIZABLE transakcija)
▼
PayrollService.markPayrollRunPaid()
│ postavlja payment_date na SVE payslip redove org/period
│ (ista transakcija, samo na write-grani, nikad na idempotent no-op)
▼
PaymentEventObligationService.onPayrollRunPaid(orgId, periodYear, periodMonth, paymentDate)
│ HR org → čita HR-07 (JOPPD) iz kataloga SAMO za title/form metadata
│ due_date = resolveNextBusinessDay(paymentDate)
│ RS/BA org → no-op (vidi §2b, §2c)
▼
upsert ComplianceDeadlines (keyed: org, year, deadlineType, period)
│ korekcija datuma → UPDATE istog reda, ne duplikat
▼
notifyIfNeeded() — JEDNA in-app + email notifikacija na kreiranje, VAN transakcije
(bez T-7/T-1 kadence — nema lead-time koncepta, rok = dan isplate)
resolveNextBusinessDay(date) — čista funkcija, semantika je roll-forward SAMO ako datum pada na neradni dan, ne "uvijek sljedeći radni dan":
- Pon–Pet → passthrough (utorak isplata ostaje utorak rok)
- Subota → ponedjeljak (+2)
- Nedjelja → ponedjeljak (+1)
Ovo je vikend-only MVP (bez praznik-tabele — vidi §2d).
2. Scope odluke s razlozima (ključno za budućnost)
(a) BA_FED (FBiH) i BA_RS isključeni iz payment-event tipa — Finverge verifikacija #105744, verdikt NE
MIP-1023 (FBiH) i Obrazac 1002 (RS-BiH) su fiksni mjesečni agregatni izvještaji, ne payment-event:
- MIP-1023: rok = 15. u mjesecu za prethodni mjesec (čl. 31 st. 5 Pravilnika o primjeni Zakona o porezu na dohodak FBiH), period-tag se odnosi na obračunski mjesec, ne na mjesec isplate.
- Obrazac 1002: rok = 10. u mjesecu za sve isplate iz prethodnog mjeseca (Zakon o poreskom postupku RS).
Oba entiteta su konfirmisana sa 7+ (FBiH) i 6+ (RS-BiH) nezavisnih izvora (FEB, paragraf.ba, advokat-prnjavorac.com, fineks.ba, poreskaupravars.org, vladars.rs). Već su ispravno modelovani u postojećem fiksno-mjesečnom katalogu (PR 157, #105641, CEO-odobreno #105640) — Task B ih namjerno NE dira. Ne dodavati BA_FED/BA_RS u payment-event tip triger.
(b) RS (Srbija) isključena iz Task B builda — red-zone FAIL, #105742 redzone-tax-verdict.md
Red-zone porezna verifikacija (persona porez-hr-105742, web-verified 5+ nezavisnih izvora: porezionline.rs, aktivasistem.com, paragraf.rs, purs.gov.rs) je dala FAIL verdikt na originalni HR+RS dizajn:
- PPP-PD (RS Srbija) se podnosi PRIJE isplate, ne poslije. Mehanizam: prijava → PURS kontrola → BOP broj (Broj Odobrenja za Plaćanje) → BOP se unosi u nalog za prenos → tek onda isplata.
- Mark-paid-poslije-isplate model (kao HR-07) je konceptualno pogrešan za RS: ako je korisnik već kliknuo "isplaćeno" bez prethodno podnesene PPP-PD/BOP-a, klijent je već u prekršaju — podsjetnik koji stigne nakon toga je zakašnjeo po definiciji, ne samo "kasni".
- Dodatno flagovano: repo dokument
PAYROLL-PHASE2-PLAN.md§2b sadrži interno neusklađen opis roka ("15th of month following payment") koji je u sukobu i sa web-verified pravilom i sa dizajn-katalogom RS-07 (PER_PAYMENT_EVENT). Fix isporučen odvojeno u #105747 — nije popravljeno u Task B.
Posljedica za build: Task B je implementiran HR-only. RS org u onPayrollRunPaid() je dokumentovan no-op — nema deadline red, nema notifikaciju, log linija referencira #105747. RS-07 katalog entry ostaje netaknut (enabled: false).
(c) HR-07/RS-07 katalog enabled: false do praznik-tabele (#105743, Lexicon gate)
ObligationEngine.generateDeadlines() i dalje ne emituje HR-07/RS-07 (potvrđeno testom ObligationEngineUnaffectedTest — 3/3 PASS, uključujući provjeru protiv REALNOG učitanog kataloga). Payment-event insertion je potpuno odvojen kod-put od kalendarskog generatora — katalog služi samo kao metadata SSOT (title/form), ne kao evaluator za ovaj tip roka. enabled: true čeka:
- Praznik-tabela (#105743, MVP je trenutno vikend-only)
- Lexicon sign-off na
requires_expert_validationkonvenciju (isti obrazac kao postojeći RS-07 flag)
(d) Vikend-only MVP — praznici su Task C (odvojen, ne blokira A/B)
Dizajn (#105671 §4) eksplicitno je priznao ovaj gap kao MVP-known-limitation, ne skriveni nedostatak: puni praznik-kalendar zahtijeva holidays referentnu tabelu po jurisdikciji/godini + Lexicon/tax-expert sign-off (Zakon o blagdanima HR vs Zakon o praznicima RS se razlikuju). Cost/risk ako se ship-a bez praznika: pogrešan rok kad je isplata 1-2 radna dana prije praznika (npr. dan prije Uskrsnog ponedjeljka — vikend-only logika kaže "sljedeći dan" = sam praznik, što je netačno). Task C nosi ovu tabelu odvojeno, ne blokira A/B ship.
Bitna napomena (red-zone stavka 1): AC1 pseudokod (Mon-Fri passthrough, Sat/Sun roll-forward) je ispravan izvor istine za JOPPD pravilo. Prozna formulacija u originalnom dizajn-sažetku ("next business day after payment date") je dvosmislena i može zavesti na pogrešnu "uvijek +1" implementaciju — kod treba pratiti AC1, ne prozni opis.
3. Tehnika
SERIALIZABLE transakcija (Proveo P2P race nalaz + fix)
Proveo adversarial P2P review na #105741 (proveo-angie-105741, verdikt PASS s jednim MEDIUM nalazom) je našao da markPayrollRunPaid() radi read-then-conditionally-write pod default TRANSACTION_READ_COMMITTED izolacijom, bez SELECT...FOR UPDATE i bez SERIALIZABLE. Konkretan race: dva konkurentna mark-paid poziva za isti org+period sa RAZLIČITIM datumima mogu oba pročitati pre-write stanje, oba računati changed=true, i kasniji UPDATE tiho pobjeđuje (lost update) — "gubitnik" dobija 200/changed=true iako je njegov upis pregažen.
Fix (commit 3aa0ece2): markPayrollRunPaid() sada koristi
orgTransaction(
organizationId = organizationId,
transactionIsolation = java.sql.Connection.TRANSACTION_SERIALIZABLE,
) { ... }
1:1 kopija postojećeg repo presedana za isti read-then-derive-then-write oblik (ExpenseService.kt:267-269, InvoiceService.kt:661-664 — broj-generacija). Bez retry-on-serialization-failure wrappera (repo-wide grep potvrdio: nijedan presedan ga nema).
Novi konkurentni test (2 real threads, CountDownLatch, isti pattern kao FiscalDeviceSequenceTest): provjerava da nakon dva istovremena poziva sa različitim datumima svi payslip redovi u run-u nose ISTI konačni payment_date — nema split-state. 7/7 PASS, ponovljeno 3x, 0 flake.
Upsert keying
ComplianceDeadlines upsert je keyed na (organizationId, year, deadlineType, period) — korekcija datuma (npr. payroll admin ispravi pogrešnu isplatu) ažurira POSTOJEĆI red umjesto da duplira. Potvrđeno testom "correction updates the existing deadline row instead of duplicating" (assertion na isti deadline id kroz korekciju).
Notifikacija van transakcije
notifyIfNeeded() se zove IZ route handler-a, NAKON što dbQuery{} vrati rezultat — izvan markPayrollRunPaid()-ove SERIALIZABLE transakcije. Fires samo ako result.obligationResult nije null (tj. nikad na idempotent no-op granu).
RS/BA no-op s testovima
onPayrollRunPaid() grana na country != HR → no-op, pokriveno testovima "RS org mark-paid does not create any compliance deadline" i "does not fire any notification". Isti no-op put pokriva i BA_FED/BA_RS (§2a).
Migracija
Task A: V123__payslip_payment_date.sql — ALTER TABLE payslips ADD COLUMN IF NOT EXISTS payment_date DATE NULL + indeks na (organization_id, period_year, period_month, payment_date). Flyway-replay test (V121 lekcija): migracija privremeno uklonjena → test PADA (3/3 FAILED), vraćena → PASS — dokazuje da test stvarno testira migraciju, ne samo Kotlin model.
Task B: Nema nove migracije — ComplianceDeadlines (V9) je već imala sve potrebne kolone, potvrđeno čitanjem postojeće Exposed definicije prije pisanja koda.
PR / commit trag
| Task | Repo | PR | Commit | Status |
|---|---|---|---|---|
| A (#105741) | Bilko | PR 164 | d7d27d0c + fix 3aa0ece2 |
merged (b1417252) |
| B (#105742) | Bilko | PR 165 | 6b9ebe5c |
open, adversarial verify u toku |
4. Runbook / gotchas
Kako se aktivira za prave klijente
- #105743 — sagraditi
holidaysreferentnu tabelu (jurisdikcija, datum, naziv), seed za HR + RS za tekuću + narednu godinu. resolveNextBusinessDay()proširiti da konsultuje tabelu kad postoji za org-ovu jurisdikciju, fallback na vikend-only ako nema podataka za tu godinu (isti defanzivni pattern kaoOrgComplianceProfile.default()).- Lexicon sign-off zabilježen po
requires_expert_validationkonvenciji — TEK ONDA flipenabled: truena HR-07 (i RS-07 nakon #105747 redizajna, vidi ispod). - Bez ovog koraka HR-07/RS-07 ostaju
enabled: falseu katalogu — payment-event insertion put radi nezavisno od tog flaga (flag utiče samo naObligationEngine-ov kalendarski generator, ne naPaymentEventObligationService), ali production-ready gate je i dalje ovaj sign-off.
RS (Srbija) redizajn — #105747, otvoreno
RS-07 (PPP-PD) NE smije se implementirati po istom "mark-paid → deadline poslije → jedna notifikacija" šablonu kao HR-07. Minimalne opcije za redizajn (iz red-zone verdikta):
- UX kao "upozorenje PRIJE potvrde isplate — PPP-PD mora biti podnesena i BOP dobijen prije nego označite kao isplaćeno", ili
- Ako MVP ne može modelovati prijava-prije-isplate tok, notifikacioni tekst mora eksplicitno reći da rok prethodi isplati, ne "X dana poslije".
Ne kopirati HR-07 resolveNextBusinessDay(paymentDate) šablon direktno na RS-07 bez ove korekcije.
PAYROLL-PHASE2-PLAN.md §2b — zastario opis
Repo dokument docs/regulatory/PAYROLL-PHASE2-PLAN.md §2b sadrži netačan/zastario opis PPP-PD roka ("Due by the 15th of the month following payment") koji je u sukobu i sa web-verified pravilom i sa RS-07 katalog tipom (PER_PAYMENT_EVENT). Isti tekst identifikovan identičan u više worktree kopija (angie-105568, codecraft-105355, codecraft-105193/4, codecraft-105192, proveo-105276, codecraft-105687). Fix pripada #105747 — sinhronizovati sve kopije nakon RS redizajna, ne prije.
Prije mc.js ready/done na #105742
- PR 165 mora biti merged (trenutno open).
- Proveo adversarial P2P verdikt na #105742 mora doći kroz stvarni verifier, ne mesh guard-rail auto-BLOCKED poruku (poznat lažni pozitiv, viđen i na #105741 i #105742 threadovima —
evalagent eksplicitno kaže da nije pravi verifier response). - #105747 (RS redizajn) je odvojen task, ne blokira HR-only #105742 merge.
Izvori (evidence)
~/system/evidence/105671/design-proposal.md— Petter Graff arhitekturni dizajn, MC #105671~/system/evidence/105741/build-evidence-2026-07-15.md— Task A build (codecraft-hadi-105741), SERIALIZABLE fix~/system/evidence/105741/proveo-p2p-verdict.md— Proveo adversarial P2P (proveo-angie-105741), PASS + MEDIUM race nalaz~/system/evidence/105742/build-evidence-2026-07-15.md— Task B build (codecraft-hadi-105742), HR-only scope~/system/evidence/105742/redzone-tax-verdict.md— red-zone porezna verifikacija (porez-hr-105742), FAIL na RS~/system/evidence/105744/finverge-ba-payment-event.md— Finverge BA verifikacija (finverge-105744), NE za BA_FED/BA_RS
No comments to display
No comments to display