# Acceptance Criteria

# Acceptance Criteria: Bilko

> **Project:** Bilko — Balkan Accounting SaaS
> **Version:** 0.1
> **Date:** 2026-02-23
> **Author:** John (AI Director)
> **Status:** Draft
> **Reviewers:** Alem Bašić (CEO)

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1     | 2026-02-23 | John (AI Director) | Initial draft — Phase 1 Serbia MVP |

---

## 1. Purpose & Methodology

### 1.1 What Are Acceptance Criteria?

Acceptance criteria are the conditions that the Bilko system must satisfy to be accepted as working correctly. They answer: **"How will we know when this feature is done?"**

For financial software, acceptance criteria are especially critical because errors have direct legal and financial consequences for users (SEF fines, incorrect PDV filings, balance sheet errors).

Good acceptance criteria are:
- **Testable** — Can be verified with a specific test procedure
- **Clear** — Unambiguous; no room for interpretation
- **Complete** — Cover happy path, error paths, and edge cases
- **Financially accurate** — For accounting features: verified against Balkan GAAP and tax law

### 1.2 Format: Given / When / Then (Gherkin-Style)

```
Given [an initial context / precondition that is true]
When  [an action or event occurs]
Then  [the expected outcome is observed]
And   [additional expected outcomes, chained]
```

### 1.3 Categories of Acceptance Criteria

| Category | Description | Example |
|----------|-------------|---------|
| **Positive (Happy Path)** | System works as expected with valid inputs | Invoice submitted to SEF successfully |
| **Negative (Sad Path)** | System handles invalid inputs gracefully | SEF rejects invoice — error shown |
| **Edge Case** | Boundary conditions | Zero-PDV invoice; zero-balance period |
| **Integration** | System works with external services | SEF API, exchange rate API |
| **Financial Accuracy** | Calculations correct per tax law | PDV = base × 0.20 exact to 4 decimal places |
| **Non-Functional** | Performance, accessibility, security | Invoice creation < 5s end-to-end |

---

## 2. Feature Acceptance Criteria

---

### Module: Authentication & User Management

---

#### Feature: User Registration (FR-001)

**Feature Description:** New users register with email/password and automatically get an organization with Serbian Chart of Accounts.
**Business Requirement:** BR-014
**Linked User Stories:** US-001

**Positive Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-001 | Successful registration | A new user with valid email "marko@firma.rs" and strong password | User submits form with org name "Firma d.o.o." | Account created; org created with Kontni Okvir pre-populated; verification email sent within 2 minutes |
| AC-002 | Email verification | Account just created | User clicks verification link (valid within 48h) | Email confirmed; user redirected to organization dashboard |
| AC-003 | Chart of Accounts pre-populated | New organization created | User opens /settings/accounts | All 10 account classes (0-9) visible with standard Serbian accounts |

**Negative Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-004 | Duplicate email | Account already exists for marko@firma.rs | User submits registration with same email | Error "Nalog sa ovim emailom već postoji"; no account created |
| AC-005 | Weak password | User is on registration form | User submits password "abc123" (no uppercase/special) | Inline error shown before form submission; form not submitted |
| AC-006 | Invalid email format | User is on registration form | User submits "notanemail" as email | Inline validation error shown |

**Edge Cases:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-007 | Verification link expiry | Verification link generated 49+ hours ago | User clicks expired link | Error "Link je istekao"; option to resend verification shown |
| AC-008 | Double registration attempt | User submits form twice rapidly | Two identical POST requests within 1 second | Only one account created; second returns appropriate error |

**Non-Functional Acceptance Criteria:**

| # | Category | Criterion |
|---|----------|-----------|
| AC-009 | Performance | Registration + org creation + CoA seeding completes in < 3 seconds |
| AC-010 | Security | Password stored as bcrypt hash (cost 12+); never in plaintext logs |

---

#### Feature: User Login (FR-002)

**Feature Description:** Authenticated users log in with email and password.
**Business Requirement:** BR-014
**Linked User Stories:** US-002

**Positive Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-020 | Successful login | Verified user with valid credentials | User submits login form | Authenticated; access token (15min) + refresh token (30d) set; redirected to dashboard |
| AC-021 | Silent token refresh | User's access token expired; refresh token valid | User makes API call | New access token issued silently; user not logged out; original request succeeds |

**Negative Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-022 | Wrong password | Registered user exists | User submits wrong password | Generic error "Pogrešan email ili lozinka" (no user enumeration) |
| AC-023 | Non-existent email | No account for this email | User submits login | Same generic error "Pogrešan email ili lozinka" |
| AC-024 | Account locked | 5 failed attempts within 15 minutes | 6th attempt | Error "Nalog zaključan. Pokušajte za 15 minuta." |
| AC-025 | Unverified account | Account created but email not verified | User attempts login | Error "Molimo vas potvrdite email adresu" with option to resend |

**Edge Cases:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-026 | Session expiry | User idle for 30+ minutes; access token expired; refresh token also expired | User attempts action | Redirected to login; message "Vaša sesija je istekla" |

---

### Module: Invoicing

---

#### Feature: Create Invoice with PDV (FR-010)

**Feature Description:** Create invoices with auto-PDV calculation. Financial accuracy is critical — NUMERIC(19,4) precision required.
**Business Requirement:** BR-001, BR-002
**Linked User Stories:** US-010

**Positive Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-030 | Invoice with 20% PDV | Line item: 1000.0000 RSD base, 20% PDV rate | Invoice created and viewed | PDV amount = 200.0000 RSD; total = 1200.0000 RSD exactly (NUMERIC precision) |
| AC-031 | Invoice with 10% PDV | Line item: 500.0000 RSD base, 10% PDV rate | Invoice created | PDV = 50.0000 RSD; total = 550.0000 RSD |
| AC-032 | Multi-line invoice with mixed PDV rates | 3 line items: 1000 @ 20%, 500 @ 10%, 200 @ 0% | Invoice created | PDV breakdown shown per rate; total PDV = 250.0000 RSD; grand total = 1950.0000 RSD |
| AC-033 | Invoice saved as Draft | All required fields filled | User clicks "Save Draft" | Invoice in Draft status; not submitted to SEF; can be edited |
| AC-034 | Invoice number sequential | Previous invoice number: INV-2026-001 | New invoice created | Invoice number: INV-2026-002 (no gaps) |

**Negative Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-035 | Missing required field | Invoice wizard open | User tries to advance without selecting client | Inline error "Odaberite klijenta"; cannot advance to next step |
| AC-036 | Zero-amount invoice | User enters 0 for all line item prices | User tries to save | Validation error "Iznos fakture mora biti veći od 0" |
| AC-037 | Due date before invoice date | Invoice date: 2026-03-01, due date: 2026-02-28 | User saves invoice | Validation error "Rok plaćanja mora biti nakon datuma fakture" |

**Edge Cases:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-038 | PDV rounding edge case | Line item: 333.3333 RSD, 20% PDV | Invoice calculated | PDV = 66.6667 RSD (ROUND to 4 decimal places, not truncate); total = 400.0000 RSD |
| AC-039 | PDV-exempt invoice (0%) | Business in PDV exemption regime | Invoice created with 0% PDV | PDV amount = 0.0000; total = base amount; PDV field shows "PDV 0%" with exemption reason |

**Financial Accuracy Criteria:**

| # | Category | Criterion |
|---|----------|-----------|
| AC-040 | NUMERIC precision | All PDV and total amounts stored as NUMERIC(19,4) — verified in DB; no floating point |
| AC-041 | PDV law compliance | PDV calculation matches Zakon o PDV Art. 17 — base × rate formula verified for 20 test cases |

---

#### Feature: SEF E-Invoice Submission (FR-011)

**Feature Description:** Automatic submission of Serbian B2B invoices to efaktura.gov.rs.
**Business Requirement:** BR-001
**Linked User Stories:** US-011

**Positive Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-050 | Successful SEF submission | Serbian B2B invoice, organization has SEF credentials | User clicks "Pošalji fakturu" | UBL 2.1 XML generated and submitted to SEF within 10 seconds; SEF status = "Prihvaćeno"; SEF invoice ID stored |
| AC-051 | SEF status visible | Invoice submitted to SEF | User views invoice detail | SEF status shown: "Prihvaćeno" + SEF invoice ID |

**Negative Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-052 | SEF rejects — missing buyer PIB | Invoice created without buyer's PIB (tax ID) | Submission attempted | SEF rejection error shown in Serbian: reason from SEF response; invoice status = "SEF Odbijeno"; user can edit and resubmit |
| AC-053 | SEF API unavailable (503) | SEF platform returns 503 | Submission attempted | Invoice queued for retry; user notified "Faktura je u redu čekanja za SEF. Prosleđivanje za max 30 minuta."; max 3 retries |

**Edge Cases:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-054 | Duplicate SEF submission attempt | Invoice already submitted and Accepted | User somehow triggers send again | System detects SEF invoice ID exists; blocks resubmission; shows "Faktura je već prosleđena SEF-u" |
| AC-055 | B2C invoice (no SEF required) | Invoice for individual (no PIB) | User sends | PDF emailed; SEF submission skipped; no SEF status shown |

---

#### Feature: Invoice Payment Tracking (FR-012)

**Linked User Stories:** US-012

**Positive Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-060 | Mark invoice paid | Invoice status = Sent | User marks as paid with date 2026-03-15 and amount 1200 RSD | Status = Paid; double-entry: Debit 1200 (110 — tekući račun), Credit 1200 (200 — potraživanja); payment date recorded |
| AC-061 | Overdue detection | Invoice due 2026-03-14; today is 2026-03-15; status = Sent | System daily check runs | Status automatically changes to Overdue; in-app notification sent |

**Negative Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-062 | Mark paid with wrong amount | Invoice total = 1200 RSD | User marks paid with amount 1000 RSD | Warning: "Plaćeni iznos (1.000 RSD) je manji od iznosa fakture (1.200 RSD). Potvrdi parcijalno plaćanje." |

---

### Module: Expense Tracking

---

#### Feature: Create Expense (FR-020)

**Feature Description:** Record business expenses with double-entry auto-creation.
**Business Requirement:** BR-009
**Linked User Stories:** US-020

**Positive Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-070 | Record expense with double-entry | Expense: 5000 RSD, category "Kirija" (account 480), paid from tekući račun (110) | User submits expense | Expense saved; Transaction: Debit 5000 (480 — Expenses), Credit 5000 (110 — Bank); LoggedAction entry created |
| AC-071 | Receipt attachment | Expense saved | User attaches JPEG receipt (3MB) | Receipt stored; accessible from expense record; thumbnail shown |

**Negative Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-072 | Receipt too large | User attaches 15MB PDF | Upload attempted | Error "Dokument ne može biti veći od 10MB" |
| AC-073 | Future date expense | User enters expense date 2027-01-01 | Form submitted | Warning "Datum troška je u budućnosti. Potvrdi unos." — requires explicit confirmation |

---

### Module: VAT/PDV Management

---

#### Feature: PDV Report Generation (FR-050)

**Feature Description:** Monthly PDV report aggregating all sales and purchase PDV. Critical for legal compliance.
**Business Requirement:** BR-002, BR-006
**Linked User Stories:** US-050

**Positive Scenarios:**

| # | Scenario | Given | When | Then |
|---|----------|-------|------|------|
| AC-080 | January PDV report | January 2026: 3 invoices (PDV 600+400+200=1200 RSD), 2 expenses (input PDV 240+120=360 RSD) | User generates January PDV report | Output PDV = 1200 RSD; Input PDV = 360 RSD; Net PDV payable = 840 RSD |
| AC-081 | PDV report PDF export | PDV report generated | User clicks "Exportuj PDF" | PDF with header "PDV Prijava — Januar 2026", org name, PIB, all amounts per official format |
| AC-082 | Zero PDV period | December: no PDV transactions | User generates December PDV report | Zero-value report generated with all fields = 0; exportable (still legally required) |

**Financial Accuracy Criteria:**

| # | Category | Criterion |
|---|----------|-----------|
| AC-083 | PDV accuracy | PDV report totals verified against sum of all invoice/expense PDV fields in DB — must match to 4 decimal places |
| AC-084 | PDV law compliance | Output PDV = sum of all 20% standard + sum of all 10% reduced PDV from outgoing invoices per Zakon o PDV |

---

## 3. Integration Scenarios

| # | Integration | Scenario | Expected Behavior | Test Environment |
|---|------------|---------|------------------|-----------------|
| INT-001 | SEF API (efaktura.gov.rs) | Valid invoice submission | SEF returns invoice ID + "Accepted" status within 30s | SEF sandbox |
| INT-002 | SEF API | Invalid invoice (missing buyer PIB) | SEF returns rejection with specific reason code | SEF sandbox |
| INT-003 | SEF API | SEF unavailable (503) | Bilko queues submission; retries 3× with exponential backoff; user notified | Mocked 503 |
| INT-004 | Email provider | Invoice PDF delivery | Recipient receives PDF invoice within 2 minutes of send | Mailtrap / staging |
| INT-005 | Exchange rate API (ECB) | EUR/RSD rate fetch | Rate fetched and cached; used for multi-currency invoice | ECB test endpoint |
| INT-006 | Exchange rate API | API unavailable | Cached rate (< 24h) used; if no cache, user prompted for manual entry | Mocked timeout |
| INT-007 | SEF API | SEF credentials invalid | Error shown to user: "SEF akreditivi su nevalidni. Proveri podešavanja." | SEF sandbox |

---

## 4. Non-Functional Acceptance Criteria

### 4.1 Performance

| # | Criterion | Target | Test Method |
|---|-----------|--------|-------------|
| NF-AC-001 | Dashboard initial load | < 3 seconds (4G) | Lighthouse on staging |
| NF-AC-002 | Dashboard subsequent navigation | < 1 second | Lighthouse warm cache |
| NF-AC-003 | Invoice creation (full wizard + API save) | < 5 seconds end-to-end | Manual timing + k6 |
| NF-AC-004 | SEF submission (Bilko to SEF confirmation) | < 30 seconds | E2E test in SEF sandbox |
| NF-AC-005 | PDV report generation (1 year of data) | < 5 seconds | Load test with synthetic data |

### 4.2 Accessibility

| # | Criterion | Target | Test Method |
|---|-----------|--------|-------------|
| NF-AC-010 | No critical accessibility violations | 0 critical violations | axe-core on all pages |
| NF-AC-011 | Invoice wizard keyboard navigation | Complete wizard without mouse | Manual keyboard test |
| NF-AC-012 | Color contrast | ≥ 4.5:1 (normal text), ≥ 3:1 (large text) | Contrast checker — Bilko #00E5A0 on dark verified |

### 4.3 Security

| # | Criterion | Target | Test Method |
|---|-----------|--------|-------------|
| NF-AC-020 | Organization data isolation | User from Org A cannot access Org B data | API test: send request with Org A token for Org B resource — must return 403 |
| NF-AC-021 | No financial data in client-side logs | No amounts, invoices, or PII in browser console | Manual browser DevTools review |
| NF-AC-022 | Input injection prevention | No SQL/XSS injection vulnerabilities | OWASP ZAP + Snyk SAST |
| NF-AC-023 | SEF credentials encrypted | SEF API keys not stored in plaintext in DB | DB inspection + code review |

### 4.4 Financial Accuracy

| # | Criterion | Target | Test Method |
|---|-----------|--------|-------------|
| NF-AC-030 | Debit = Credit invariant | Sum of all debits = sum of all credits across ALL transactions | Automated DB check in CI: `SELECT SUM(debit_amount) - SUM(credit_amount) FROM transactions` must = 0 |
| NF-AC-031 | NUMERIC precision | Zero floating point errors in PDV and totals | 1000 PDV calculations with known expected values; compare to NUMERIC result |
| NF-AC-032 | Exchange rate immutability | Changing today's rate does not affect historical transactions | Create transaction, change rate, verify transaction amount unchanged |

---

## 5. UAT Scenario Mapping

| AC ID | AC Description | UAT Scenario ID | UAT Tester | Status |
|-------|---------------|-----------------|-----------|--------|
| AC-001 | Successful registration | UAT-001 | Beta SMB owner | Not Started |
| AC-030 | Invoice PDV 20% calculation | UAT-010 | Beta accountant | Not Started |
| AC-050 | SEF submission success | UAT-020 | Beta SMB owner | Not Started |
| AC-080 | PDV report generation | UAT-030 | Beta accountant | Not Started |
| NF-AC-030 | Debit = Credit invariant | UAT-ACC-001 | Beta accountant | Not Started |
| NF-AC-001 | Dashboard load < 3s | UAT-P01 | Any beta user | Not Started |

---

## 6. Traceability to Requirements

| AC ID | Acceptance Criterion | FR Reference | BR Reference | US Reference |
|-------|---------------------|-------------|-------------|-------------|
| AC-001 | Successful registration | FR-001 | BR-014 | US-001 |
| AC-020 | Successful login | FR-002 | BR-014 | US-002 |
| AC-030 | Invoice PDV 20% | FR-010 | BR-002 | US-010 |
| AC-050 | SEF submission success | FR-011 | BR-001 | US-011 |
| AC-060 | Mark invoice paid | FR-012 | BR-001 | US-012 |
| AC-070 | Record expense | FR-020 | BR-009 | US-020 |
| AC-080 | PDV report January | FR-050 | BR-002, BR-006 | US-050 |

> Full traceability: [`RTM.md`](RTM.md)

---

## Approval

| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | John (AI Director) | 2026-02-23 | |
| Reviewer | | | |
| Business Analyst | John | 2026-02-23 | |
| Product Owner | John | 2026-02-23 | |
| QA Engineer | validator agent | | |
| AI Director (John) | John | 2026-02-23 | |
| CEO (Alem) | Alem Bašić | | |