# UAT Phase 1 Findings

# UAT Phase 1 Findings

**MC:** [#10487](https://github.com/johnatbasicas/bilko/issues/10487)  
**Date:** 2026-05-02  
**Stage web:** https://bilko-web-stage-dh4m46blja-lz.a.run.app  
**Stage API:** https://bilko-api-stage-dh4m46blja-lz.a.run.app  
**Verdict:** ACCEPT-WITH-FOLLOWUP

---

## Methodology

Three-agent pure UAT discovery on Bilko stage environment (post-Kotlin migration, pre-Express deletion). No build work — observational only.

**Team:**
- **maria-santos** — Real-user SMB persona walkthrough (mobile UX + time-to-first-invoice)
- **petter-graff** — Architecture gap analysis (docs vs code vs schema)
- **angie-jones** — Functional smoke per epic (INCOMPLETE — deferred to MC #10500)

---

## Top P0 Findings (Blocking Go-Live)

### 1. Registration Fails — Dual Bug
**Root causes:**
- **DB ENUM type mismatch:** Prisma migrations created PostgreSQL ENUM `"UserRole"` but Kotlin Flyway V1 declares `VARCHAR(50)`. Kotlin INSERT with string `"owner"` → PostgreSQL rejects with type error.
- **API field name mismatch:** Web sends `organizationName`, Kotlin API expects `orgName`.

**Impact:** Zero users can register. Product completely inaccessible to new users.

**Evidence:**
```
curl -X POST https://bilko-api-stage-dh4m46blja-lz.a.run.app/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"test@test.ba","password":"test1234","fullName":"Test User","orgName":"Test DOO","country":"BA","baseCurrency":"BAM"}'

→ HTTP 500: {"error":"PSQLException: column \"role\" is of type \"UserRole\" 
   but expression is of type character varying","code":"INTERNAL_ERROR"}
```

**Followup:** MC #10494

---

### 2. Invoice Email Send — UI Only, No Dispatch
**What:** Invoice wizard Step 6 shows email compose fields (To, Subject, Message, "Send me a copy"). `InvoiceService.sendInvoice()` changes status to "sent" but **does NOT send email**. The `emailData` state is never passed to API.

**Impact:** Customer never receives invoice. User believes invoice was sent. Creates chargeback disputes and relationship damage when client claims "nisam dobio nista."

**Evidence:** `apps/api/src/main/kotlin/no/alai/bilko/services/InvoiceService.kt` — no email service injection in DI container. `sendInvoice()` returns success without SMTP call.

**Followup:** MC #10495 (Sprint 1 P0)

---

### 3. Dead UI Buttons — Receipt Scan + Attach
**What:**
- "Skeniraj racun" button (`/expenses/new`) has no `onClick` handler. No camera access, no OCR, no file picker.
- "Prikaci racun" (Paperclip) also has no `onClick` and no backing `<input type="file">`.

**Impact:** The mobile expense entry feature — marketed as a selling point — is non-functional. Field users will abandon immediately. RS/BA tax law requires receipt documentation for deductible expenses; without attachment capability, Bilko cannot support compliance.

**Evidence:** 
- `apps/web/app/(dashboard)/expenses/new/page.tsx` — button is styled `<button>` with Camera icon but zero interactivity
- maria-santos UX report: iPhone Safari VoiceOver announces "Skeniraj racun, button" — activating it does nothing

**Followup:** MC #10495 (Sprint 1 P0)

---

### 4. Bank CSV Format Incompatibility
**What:** CSV import expects exact format: `date,description,amount,reference` (ISO 8601 date, comma-separated). Real Bosnian/Serbian banks export semicolon-separated with local date formats: Raiffeisen BA (`DD.MM.YYYY`), UniCredit RS (local thousand separators), Intesa RS (multi-line headers).

**Impact:** User exports from Raiffeisen online banking, uploads CSV, gets 0 imported rows. Feature reads as broken. Bank reconciliation is effectively non-functional for Balkan users.

**Evidence:** `apps/api/src/main/kotlin/no/alai/bilko/services/BankingService.kt:303-354` — generic parser, no named bank format presets

**Followup:** MC #10496 (Sprint 2)

---

## Mobile UX Score: 5/10

**Why not lower:** Clean UI, readable fonts, BS/SR/HR localization present, adequate color contrast.

**Why not higher:**
- No mobile sidebar Sheet pattern (raw div overlay, no animation)
- Invoice wizard desktop-optimized (4-column grid → 70px inputs on 390px iPhone)
- Only 3 responsive breakpoint uses in ~1,200-line wizard
- Step labels hidden on mobile (`hidden sm:inline` → numbered dots with no context)
- Date picker label misalignment on iOS Safari small screens
- No service worker / localStorage draft persistence (network loss = lost work)

---

## Localization Status

**Good news:** BS, SR-Latn, SR-Cyrl, HR, EN all have complete translation files. Default `sr-Latn`. Language switcher (globe icon) present.

**Issues:**
- **sr-Latn dialect inconsistency:** Mixes Bosnian and Serbian forms ("dospijeva" [BS] alongside "dospeva" [RS/SR], "mjesec" [BS] vs "mesec" [RS]). File appears half-written for RS, half for BA.
- No Cyrillic variant shown to BA users (but some BA users from RS prefer Cyrillic).
- "Skeniraj racun" label is actively misleading — button does nothing.

---

## Friction Summary

| Severity | Count |
|----------|-------|
| P0 (blocks all use) | 2 |
| P1 (core workflow broken) | 4 |
| P2 (significant friction/trust damage) | 6 |
| P3 (polish/consistency) | 4 |

**Full list:** 16 friction points documented in `/tmp/bilko-uat-ux-10487.md`

---

## Missing Features (vs SMB Expectations)

1. **No email delivery of invoices** (Step 6 UI is cosmetic)
2. **No OCR receipt scanning** (button exists, zero capability)
3. **No Bosnian/Serbian bank CSV format support**
4. **No SEF integration UI for new users** (Settings "Integracije" present but no SEF config screen)
5. **No registration completion flow** (blocked by P0 bugs)
6. **No offline/draft persistence** (wizard loses data on refresh or connectivity loss)
7. **No payment link/QR code on invoice**
8. **No onboarding validation** (flow exists, untestable due to P0 block)
9. **No mobile-optimized dashboard** (charts overflow/compress at 390px)
10. **No forgot-password email confirmation** (shows "Provjerite vas email" but no evidence of dispatch)

---

## Architecture Findings (petter-graff)

### Backend Disambiguation: KOTLIN/KTOR

**Evidence:**
- Stage health check: `{"status":"ok","service":"bilko-api","version":"1.0.0"}`
- Matches `apps/api/src/main/kotlin/.../routes/HealthRoutes.kt:14-19` exactly
- Express includes `uptime`, `timestamp`, `db` fields (absent from stage)
- DEPLOY-MAP.md line 34: `Dockerfile.api-kotlin`, image `bilko/api:stage-1f48fdc`

**Conclusion:** Stage is confirmed Kotlin. Express docs are stale.

---

### User Stories Implementation Matrix

15 stories tracked (not 17 — task brief was imprecise). IDs: US-001..004, US-010..012, US-020, US-030..031, US-040, US-050, US-060..061, US-070.

**Summary:** 
- 0 fully implemented
- 8 partial
- 7 with critical gaps

**P0 gaps (3):**
- US-001: Email verification absent; Serbian CoA seeding not called
- US-003: Invite endpoint not mounted in Kotlin routing
- US-011: SEF stub only (`SEF-STUB-<id>`), no real efaktura.gov.rs HTTP

**P1 gaps (4):**
- US-002: Lockout message English (not Serbian)
- US-004: No multi-org support
- US-012: No automated overdue detection scheduler
- US-050: No ePorezi export format, no PDV reminder

---

### Schema Reality (Prisma vs Kotlin Exposed vs Flyway)

**Model count drift:**
- Prisma: 22 models
- Kotlin Exposed: ~18 table objects
- Flyway: 18 CREATE TABLE (V1)
- DEPLOY-MAP.md: claims 24 tables on stage DB

**Specific drifts:**
| Item | Prisma | Kotlin Exposed | Flyway | Gap |
|------|--------|----------------|--------|-----|
| `pausal_rates` | PausalRate model | No Exposed Table (data class only) | V4 | GAP: Kotlin may use raw SQL |
| `archive_jobs` | ArchiveJob model | Comment: "Round 2" | V4 | GAP: ArchiveService uses in-memory, DB table unused |
| `sefDocumentId`, `sefAcceptedAt` | Present in Prisma Invoice | Deferred to "Round 2" in Kotlin | Not in Flyway | DRIFT: Prisma has fields, Flyway/Kotlin defer |
| Organization `address` | Absent | Comment: "Round 2" | Absent | Missing from all three |

---

### Production Readiness Gaps (12 observational)

1. **No CI pipeline for Kotlin API** (Cloud Build deploys web only)
2. **DB public IP, no SSL/IAM** (TD-3, MC #10241 blocker)
3. **Compliance endpoints auth-gated** (`/pausal/rates` returns 401 — should be public)
4. **No Prometheus metrics endpoint** (Kotlin has no `/metrics` route)
5. **No automated overdue invoice scheduler**
6. **Serbian CoA seeding absent from registration**
7. **No SLOs defined or measured**
8. **Rollback runbook references Vercel** (wrong platform)
9. **Email verification not implemented**
10. **SEF stub in production path** (legal compliance risk)
11. **X-Powered-By header absent** (consistency gap, not security issue)
12. **Invite acceptance endpoint missing**

---

### Top 5 Architectural Risks

1. **Split-backend contract drift with zero CI enforcement** — Stage Kotlin, prod Express (or nothing). Web pipeline only. Contract drift invisible.
2. **SEF stub in production** — Serbian e-invoicing law mandates real SEF submission. Stub invoices = legally non-compliant.
3. **Public IP database, no IAM auth** — `postgres-socket-factory` absent, no SSL enforcement (TD-2/TD-3)
4. **No automated schema validation** — Prisma/Exposed/Flyway independently maintained; confirmed drifts exist
5. **Compliance public endpoint regression** — Pausal calculator returns 401, blocks landing-page GTM feature

---

## AC1 Followup: angie-jones Functional Smoke

**Status:** INCOMPLETE  
**Evidence:** `/tmp/bilko-uat-bugs-10487.json` = 2 bytes (`{}`)  
**Expected:** Structured JSON with ≥8 epic entries, each with screenshots + HAR + repro steps

**Followup:** MC #10500 (re-run with HAR/screenshots for 8 epics AFTER Sprint 0 lands so login works)

---

## References

**Source MCs:**
- MC #10487 — UAT Phase 1 (this page)
- MC #10493 — Express deletion
- MC #10494 — Sprint 0 P0 registracija
- MC #10495 — Sprint 1 P0 (SEF/email/receipt)
- MC #10500 — angie-jones re-run

**Evidence files:**
- `/tmp/bilko-uat-ux-10487.md` (22KB, maria-santos)
- `/tmp/bilko-uat-gap-10487.md` (21KB, petter-graff)
- `/tmp/sentinel-verify-10487.md` (sentinel verdict)
- `/tmp/proveo-10487-postflight.json`

**Bilko repo:** https://github.com/johnatbasicas/bilko