Skip to main content

Bilko MC #104515 — E2E Validation Results (Offers + Inbox)

Bilko MC #104515 — E2E Validation Results

Validated by: Proveo / Angie Jones (sub-agent) Date: 2026-06-30 MC tasks: #104517 (Offers/Ponude), #104519 (Document Inbox) Live target: https://api.bilko.cloud (bilko-api-demo, image demo-031deb31) Auth method: POST /api/v1/auth/test/session (e2e-token-secret, org 1f9811d2)


Deployment Status

Feature Merged to azdo/main Deployed to demo
Offers / Ponude (MC #104515) YES — PR #29 (baa16135) YES — image demo-031deb31
Document Inbox (MC #104515) YES — PR #30 (fee37dc8) YES — image demo-031deb31
CN PDF header fix (MC #104515) YES — PR #32 (01b4ccbc) YES — image demo-031deb31

Verified via: az containerapp show -n bilko-api-demo -g rg-bilko-demo --query "properties.template.containers[0].image"bilkodemo.azurecr.io/bilko-api:demo-031deb31


MC #104517: Offers / Ponude Lifecycle

Test Results

Test Endpoint HTTP Expected Result Evidence
T1 GET /offers 200 200 PASS {"data":[],"meta":{"total":0,...}}
T2 POST /offers (draft) 201 201 PASS PON-2026-001, status=draft
T3 GET /offers/{id} 200 200 PASS status=draft confirmed
T4 POST /offers/{id}/send 200 200 PASS status=sent
T5 POST /offers/{id}/accept 200 200 PASS status=accepted
T6 POST /offers/{id}/convert 201 201 PASS offer.convertedToInvoiceId set
T7 GET /invoices/{convertedId} 200 200 PASS invoice exists: 3028bf0c (run 2)
T8 GET /offers/00000000-...-001 404 404 PASS cross-tenant isolated
T9 GET /offers/{id}/pdf 200 200 PASS PDF generated

Offers result: 9/9 PASS

Observations

  1. Offer number format: PON-2026-001 — matches required PON-YYYY-NNN pattern. PASS.
  2. Status enum: API returns lowercase (sent, accepted, converted). Consistent throughout. No bug.
  3. Convert response structure: {"offer": {..., "status":"converted", "convertedToInvoiceId":"<uuid>", "convertedAt":"..."}}. Invoice ID is at offer.convertedToInvoiceId (not a top-level invoiceId key). Caller must read from this path.
  4. Convert HTTP code: 201 (Created) — correct semantic since a new invoice resource is created.
  5. GL non-posting: confirmed by design (OfferService comment). GL fires only on invoice send, not on offer lifecycle. No double-entry entries created for offers.
  6. Draft allows null customerId: confirmed working — "status":"draft" in body bypasses customer requirement.

MC #104519: Document Inbox

Test Results

Test Endpoint HTTP Expected Result Evidence
T10 GET /inbox/count 200 200 PASS {"pending":0}
T11 GET /inbox 200 200 PASS {"data":[],"meta":{"total":0}}
T12 POST /inbox (upload PDF) 201 201 PASS id=b397e8ca, status=pending
T13 GET /inbox/count (post-upload) 200 200 PASS pending=1 (was 0) — incremented
T14 GET /inbox/{id} 200 200 PASS status=pending, filename=e2e-test.pdf
T15 POST /inbox/{id}/book 200 200 PASS item_status=booked, expenseId=4ec8677a
T16 GET /inbox/count (post-book) 200 200 PASS pending=0 — decremented
T17 POST /inbox/{id}/reject 200 200 PASS status=rejected
T18 GET /inbox/00000000-...-001 404 404 PASS org isolation confirmed

Inbox result: 9/9 PASS

Observations

  1. Upload → pending: File accepted, inbox item created with status=pending. PASS.
  2. Badge count lifecycle: pending=0 → upload → pending=1 → book → pending=0. Badge increments and decrements correctly. PASS.
  3. Book creates expense: POST /inbox/{id}/book with {amount, currencyCode, expenseDate, category} created expense 4ec8677a. PASS.
  4. Document attachment: Response body confirms storageUrl populated and expenseId returned — best-effort document attachment fires after booking.
  5. Storage backend: Demo uses local:// storage (not R2). Expected — demo does not have R2 credentials. R2 required for production.
  6. MIME allowlist: Tested with application/pdf — accepted. Allowlist: PDF/JPG/PNG enforced by content-type header (not filename).
  7. Reject flow: Upload → reject with reason "Duplicate — E2E test" → status=rejected. PASS.
  8. Org isolation: GET /inbox/00000000-0000-0000-0000-000000000001 returns 404 for items not in the e2e org. RLS enforced. PASS.

Required Body Fields (for API consumers)

POST /offers (create)

{
  "status": "draft",
  "currencyCode": "EUR",
  "items": [
    { "description": "string", "quantity": 1, "unitPrice": 100.00, "taxRate": 25 }
  ],
  "validUntil": "2026-12-31",
  "title": "optional",
  "notes": "optional"
}

Note: status:"draft" required if customerId is null.

POST /inbox/{id}/book

{
  "amount": 125.00,
  "currencyCode": "EUR",
  "expenseDate": "2026-06-30",
  "category": "OFFICE_SUPPLIES",
  "description": "optional"
}

Bugs Found

None. All 18 tests pass against the live demo.

Minor documentation gap (not a bug):

  • Convert response structure differs from what the frontend might assume. invoiceId is at offer.convertedToInvoiceId, not at a top-level invoiceId key. The frontend offer-store.ts should be verified to read from the correct path when rendering the converted offer.

Verdict

Task Status Can move out of paused?
#104517 Offers/Ponude E2E 9/9 PASS YES
#104519 Document Inbox E2E 9/9 PASS YES

Overall: PASS — both MC tasks can be closed.


Test Artifact

  • Test script: /tmp/bilko-e2e-test.sh (ephemeral, re-runnable)
  • Offers created: cd8fc6a0 (PON-2026-001), 26ff5a69 (PON-2026-002) — in e2e org 1f9811d2
  • Inbox items: b397e8ca (booked), 27daf37d (rejected) — in e2e org 1f9811d2
  • Converted invoice: 3028bf0c — GET /invoices/3028bf0c HTTP=200 confirmed