# User Stories

# User Stories: 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. Epic Overview

| Epic ID | Epic Name | Business Goal | Story Count | Status | Target Release |
|---------|----------|--------------|-------------|--------|---------------|
| EP-01 | Authentication & Organization Setup | Secure multi-tenant access | 4 | Backlog | Phase 1 (Serbia) |
| EP-02 | Invoicing + SEF E-Invoicing | SEF-compliant invoice creation and submission | 6 | Backlog | Phase 1 (Serbia) |
| EP-03 | Expense Tracking | Record and categorize business expenses | 3 | Backlog | Phase 1 (Serbia) |
| EP-04 | Double-Entry Bookkeeping | Accurate accounting per Balkan GAAP | 4 | Backlog | Phase 1 (Serbia) |
| EP-05 | Bank Reconciliation | Import and match bank transactions | 3 | Backlog | Phase 1 (Serbia) |
| EP-06 | VAT/PDV Management | Auto-calculate and report PDV | 3 | Backlog | Phase 1 (Serbia) |
| EP-07 | Financial Reports | P&L, Balance Sheet, Cash Flow | 4 | Backlog | Phase 1 (Serbia) |
| EP-08 | Multi-Currency | BAM, RSD, EUR, USD support | 3 | Backlog | Phase 1 (Serbia) |

---

## 2. Epic Summaries

### Epic EP-01 — Authentication & Organization Setup

**Epic Statement:**
As an **SMB owner**, I need **to securely register, log in, and manage my team's access** so that **only authorized people can view and modify my financial data**.

**Business Goal:** BR-014 (secure multi-tenancy), BR-007 (RBAC)
**Priority:** Must Have | **Target Sprint:** Sprint 1

**Acceptance Criteria at Epic Level:**
- [ ] User can register, verify email, and log in
- [ ] Organization is created on registration with Serbian Chart of Accounts pre-populated
- [ ] Owner can invite accountant with Accountant role; accountant has full financial access but cannot manage billing

---

### Epic EP-02 — Invoicing + SEF E-Invoicing

**Epic Statement:**
As a **Serbian business owner**, I need **to create invoices and submit them to SEF automatically** so that **I am compliant with the 2023 mandatory e-invoicing law without using the SEF portal manually**.

**Business Goal:** BR-001 (SEF), BR-002 (PDV), BR-008 (PDF delivery)
**Priority:** Must Have | **Target Sprint:** Sprint 2-3 | **Estimated Size:** 34 story points

---

### Epic EP-03 — Expense Tracking

**Epic Statement:**
As a **business owner**, I need **to record business expenses with categories and receipts** so that **my P&L is accurate and I have documentation for tax purposes**.

**Business Goal:** BR-009
**Priority:** Must Have | **Target Sprint:** Sprint 2

---

## 3. Story Format Guide

**Standard Story Format:**
```
As a [persona/role],
I want [feature/action],
So that [benefit/outcome].
```

**Acceptance Criteria Format (Given/When/Then):**
```
Given [a precondition that must be true],
When [the user performs an action],
Then [the expected outcome occurs].
```

---

## 4. Story Backlog

### Epic EP-01: Authentication & Organization Setup

---

#### US-001: Register and Create Organization

| Attribute | Value |
|-----------|-------|
| Epic | EP-01: Authentication & Organization Setup |
| Priority | Must Have |
| Story Points | 5 |
| Sprint | Sprint 1 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-001 |
| BR Reference | BR-014 |

**Story:**
As a **new Bilko user**,
I want **to register with my email and create my first organization**,
So that **I can start managing my business finances immediately after signing up**.

**Context:**
On registration, a default Organization is automatically created named after the user's business (or their name if not provided). Serbian Chart of Accounts (Kontni Okvir 2021) is pre-populated. The user becomes the Owner of this organization.

**Acceptance Criteria:**
- [ ] **Given** a valid email and strong password, **when** user submits registration form with full name and organization name, **then** account + organization created; verification email sent within 2 minutes
- [ ] **Given** user clicks verification link (valid for 48h), **when** link is valid, **then** email confirmed; user redirected to dashboard of their new organization
- [ ] **Given** duplicate email submitted, **when** user registers, **then** error "An account with this email already exists" shown; no new account created
- [ ] **Given** organization created, **when** user first views Chart of Accounts, **then** all standard Serbian Kontni Okvir accounts (Classes 0-9) are present

**Technical Notes:**
- Organization seeding: call `seedChartOfAccounts(organizationId, 'RS')` on org creation
- JWT: access token 15min TTL, refresh token 30d rolling

**UI/UX Notes:**
- Screen: `/auth/register` + email verification page
- Responsive: full-screen centered card on mobile + desktop

**Dependencies:**
- Blocked by: None
- Blocks: US-003 (invite users), US-010 (create invoice)

---

#### US-002: Login with JWT

| Attribute | Value |
|-----------|-------|
| Epic | EP-01 |
| Priority | Must Have |
| Story Points | 3 |
| Sprint | Sprint 1 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-002 |
| BR Reference | BR-014 |

**Story:**
As a **registered Bilko user**,
I want **to log in securely and have my session persist for a reasonable time**,
So that **I don't have to re-authenticate every time I open the app**.

**Acceptance Criteria:**
- [ ] **Given** valid credentials, **when** user submits login form, **then** authenticated; access + refresh tokens set; redirected to last-used organization dashboard
- [ ] **Given** invalid credentials, **when** user submits, **then** generic error "Pogrešan email ili lozinka" (Serbian) shown; no user enumeration
- [ ] **Given** 5 failed attempts within 15 minutes, **when** another attempt made, **then** error "Nalog je privremeno zaključan. Pokušajte za 15 minuta."
- [ ] **Given** access token expires (15min idle), **when** user makes API call, **then** refresh token used to silently issue new access token; user not logged out

**Dependencies:** Blocked by: US-001

---

#### US-003: Invite Accountant to Organization

| Attribute | Value |
|-----------|-------|
| Epic | EP-01 |
| Priority | Must Have |
| Story Points | 5 |
| Sprint | Sprint 1 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-003 |
| BR Reference | BR-007 |

**Story:**
As an **organization Owner**,
I want **to invite my accountant to access my organization with appropriate permissions**,
So that **my accountant can manage my books without being able to change billing or delete the organization**.

**Acceptance Criteria:**
- [ ] **Given** owner is in Settings > Team, **when** they enter accountant@email.com and select Accountant role, **then** invitation email sent with 48h-valid accept link
- [ ] **Given** invited user accepts and is already registered, **when** link clicked, **then** added to organization with Accountant role; no new registration required
- [ ] **Given** invited user is new, **when** link clicked, **then** registration flow with role pre-assigned
- [ ] **Given** Accountant logs in, **when** they navigate to organization, **then** full financial access; billing and org-deletion options hidden

**Dependencies:** Blocked by: US-001

---

#### US-004: Switch Between Organizations

| Attribute | Value |
|-----------|-------|
| Epic | EP-01 |
| Priority | Must Have |
| Story Points | 2 |
| Sprint | Sprint 1 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-003 |
| BR Reference | BR-007 |

**Story:**
As an **accountant managing multiple client organizations**,
I want **to switch between my clients' organizations without logging out**,
So that **I can efficiently manage multiple clients from one session**.

**Acceptance Criteria:**
- [ ] **Given** user belongs to 3 organizations, **when** they click the org switcher in the sidebar, **then** all 3 organizations shown with current one highlighted
- [ ] **Given** user selects a different organization, **when** switched, **then** all data (invoices, expenses, reports) immediately reflects the new organization; URL updates to reflect active org

**Dependencies:** Blocked by: US-001, US-003

---

### Epic EP-02: Invoicing + SEF E-Invoicing

---

#### US-010: Create Invoice with PDV Auto-Calculation

| Attribute | Value |
|-----------|-------|
| Epic | EP-02: Invoicing + SEF E-Invoicing |
| Priority | Must Have |
| Story Points | 8 |
| Sprint | Sprint 2 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-010 |
| BR Reference | BR-001, BR-002 |

**Story:**
As a **Serbian business owner**,
I want **to create an invoice with automatic PDV calculation**,
So that **I don't have to manually calculate PDV and risk incorrect tax amounts**.

**Context:**
Serbia uses PDV (Porez na dodatu vrednost) — equivalent to VAT. Standard rate 20%, reduced rate 10%. Bilko must auto-calculate and display both the base amount and PDV amount per line item, and total PDV payable.

**Acceptance Criteria:**
- [ ] **Given** authenticated user, **when** they open the invoice creation wizard and complete all 6 steps, **then** invoice saved as Draft with correct PDV (20% standard or 10% reduced per item)
- [ ] **Given** line item with unit_price 1000 RSD and PDV rate 20%, **when** user views invoice, **then** PDV = 200.0000 RSD, total = 1200.0000 RSD (NUMERIC precision)
- [ ] **Given** invoice with 3 line items at different PDV rates, **when** invoice created, **then** PDV breakdown shown per rate; total PDV is sum of all line-item PDV
- [ ] **Given** invoice saved as Draft, **when** user edits a line item quantity, **then** PDV and totals immediately recalculate

**Technical Notes:**
- All amounts: NUMERIC(19,4) — Prisma Decimal type
- PDV calculation: `pdv_amount = ROUND(base_amount * pdv_rate, 4)` — server-side only

**UI/UX Notes:**
- Screen: `/invoices/create` — 6-step wizard
- Step 3: line items with inline PDV calculation
- Step 5: invoice preview with all amounts

**Dependencies:**
- Blocked by: US-001 (auth)
- Blocks: US-011 (SEF submission)

---

#### US-011: Submit Invoice to SEF (Serbia)

| Attribute | Value |
|-----------|-------|
| Epic | EP-02 |
| Priority | Must Have |
| Story Points | 8 |
| Sprint | Sprint 2-3 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-011 |
| BR Reference | BR-001 |

**Story:**
As a **Serbian B2B business owner**,
I want **Bilko to automatically submit my invoice to SEF when I send it**,
So that **I am compliant with Serbia's mandatory e-invoicing law without using the SEF portal**.

**Context:**
Since 2023, all B2B invoices in Serbia must be submitted to SEF (efaktura.gov.rs). UBL 2.1 XML format required. SEF assigns an invoice ID and returns acceptance/rejection status.

**Acceptance Criteria:**
- [ ] **Given** user clicks "Send Invoice" on a Serbian B2B invoice, **when** send action executes, **then** UBL 2.1 XML generated and submitted to SEF within 10 seconds
- [ ] **Given** SEF accepts, **when** response received, **then** invoice SEF status = "Accepted"; SEF invoice ID stored; user notified in UI
- [ ] **Given** SEF rejects, **when** rejection received, **then** user sees rejection reason in Serbian; invoice status = "SEF Rejected"; user can correct and resubmit
- [ ] **Given** SEF API returns 503, **when** submission attempted, **then** queued for retry; user informed "Faktura će biti prosleđena SEF-u u roku od 30 minuta"; max 3 retries with exponential backoff

**Technical Notes:**
- SefService: abstract all SEF calls behind `apps/api/src/services/sef.service.ts`
- SEF credentials: per-organization, stored encrypted in DB

**Dependencies:** Blocked by: US-010

---

#### US-012: Track Invoice Payment

| Attribute | Value |
|-----------|-------|
| Epic | EP-02 |
| Priority | Must Have |
| Story Points | 3 |
| Sprint | Sprint 2 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-012 |
| BR Reference | BR-001, BR-008 |

**Story:**
As a **business owner**,
I want **to mark invoices as paid and track overdue invoices**,
So that **I know which clients have paid and which need follow-up**.

**Acceptance Criteria:**
- [ ] **Given** invoice is Sent, **when** user marks as Paid with payment date, **then** status = Paid; double-entry transaction created (Debit bank, Credit accounts receivable)
- [ ] **Given** invoice due_date has passed and invoice is still Sent, **when** system runs daily check, **then** status = Overdue; user notified via in-app notification
- [ ] **Given** invoice list, **when** user filters by Overdue, **then** only overdue invoices shown

**Dependencies:** Blocked by: US-010

---

### Epic EP-03: Expense Tracking

---

#### US-020: Record Business Expense

| Attribute | Value |
|-----------|-------|
| Epic | EP-03: Expense Tracking |
| Priority | Must Have |
| Story Points | 5 |
| Sprint | Sprint 2 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-020 |
| BR Reference | BR-009 |

**Story:**
As a **business owner or accountant**,
I want **to record a business expense with category and receipt**,
So that **my expenses are tracked for P&L accuracy and tax documentation**.

**Acceptance Criteria:**
- [ ] **Given** valid expense data (vendor, amount, category, date), **when** submitted, **then** expense saved; double-entry transaction created (Debit expense account, Credit payment account)
- [ ] **Given** expense in EUR with org base currency RSD, **when** created, **then** EUR amount stored + RSD equivalent calculated at locked exchange rate
- [ ] **Given** JPEG receipt (≤10MB) attached, **when** expense saved, **then** receipt stored and accessible from expense record
- [ ] **Given** expense list, **when** filtered by category "Putni troškovi", **then** only matching expenses shown

**Dependencies:** Blocked by: US-001

---

### Epic EP-04: Double-Entry Bookkeeping

---

#### US-030: View and Navigate Chart of Accounts

| Attribute | Value |
|-----------|-------|
| Epic | EP-04: Double-Entry Bookkeeping |
| Priority | Must Have |
| Story Points | 3 |
| Sprint | Sprint 1 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-030 |
| BR Reference | BR-003, BR-010 |

**Story:**
As an **accountant**,
I want **to view the pre-populated Serbian Chart of Accounts and add custom sub-accounts**,
So that **I can organize transactions per Balkan GAAP standards and my client's specific needs**.

**Acceptance Criteria:**
- [ ] **Given** new Serbian org, **when** accountant opens `/settings/accounts`, **then** all 10 account classes (0-9) with Serbian standard accounts shown in hierarchical tree
- [ ] **Given** accountant adds sub-account "4111 — Dobavljači u zemlji" under class 411, **when** saved, **then** appears in account tree and is selectable in manual journal entries
- [ ] **Given** account has posted transactions, **when** user attempts to delete it, **then** deletion blocked; message explains account has transactions

**Dependencies:** Blocked by: US-001

---

#### US-031: View General Ledger (Knjiga)

| Attribute | Value |
|-----------|-------|
| Epic | EP-04 |
| Priority | Must Have |
| Story Points | 5 |
| Sprint | Sprint 3 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-031 |
| BR Reference | BR-003 |

**Story:**
As an **accountant**,
I want **to view all journal entries (knjiženja) in the general ledger**,
So that **I can verify every debit/credit entry is correctly recorded**.

**Acceptance Criteria:**
- [ ] **Given** authenticated accountant, **when** opening journal ledger, **then** all transactions shown with date, description, debit account, credit account, amount
- [ ] **Given** journal entry, **when** expanded, **then** shows audit log (LoggedAction) of who created it and when
- [ ] **Given** any filter period, **when** sum of all debits calculated, **then** exactly equals sum of all credits (invariant display)

**Dependencies:** Blocked by: US-020, US-010

---

### Epic EP-05: Bank Reconciliation

---

#### US-040: Import Bank Statement CSV

| Attribute | Value |
|-----------|-------|
| Epic | EP-05: Bank Reconciliation |
| Priority | Must Have |
| Story Points | 5 |
| Sprint | Sprint 3 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-040 |
| BR Reference | BR-005 |

**Story:**
As a **business owner or accountant**,
I want **to import my Serbian bank statement as a CSV file**,
So that **I can reconcile bank transactions with invoices and expenses without manual entry**.

**Acceptance Criteria:**
- [ ] **Given** valid Serbian bank CSV (Raiffeisen format), **when** uploaded, **then** all transactions parsed and listed for review with amount, date, description
- [ ] **Given** bank transaction of 1200 RSD on 2026-03-05 matches an open invoice for 1200 RSD due on 2026-03-04, **when** parsed, **then** match suggested with 90% confidence
- [ ] **Given** unmatched transaction, **when** user categorizes manually (e.g., "Kirija — account 480"), **then** double-entry transaction created

**Dependencies:** Blocked by: US-010

---

### Epic EP-06: VAT/PDV Management

---

#### US-050: Generate Monthly PDV Report

| Attribute | Value |
|-----------|-------|
| Epic | EP-06: VAT/PDV Management |
| Priority | Must Have |
| Story Points | 8 |
| Sprint | Sprint 3 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-050 |
| BR Reference | BR-002, BR-006 |

**Story:**
As a **Serbian business owner or accountant**,
I want **to generate the monthly PDV report in one click**,
So that **I can file PDV with Poreska Uprava by the 15th without spending hours on calculations**.

**Context:**
Serbian VAT (PDV) is filed monthly by the 15th. The report must show: output PDV (on sales), input PDV (on purchases), and net PDV payable/refundable. Must export in format compatible with ePorezi portal.

**Acceptance Criteria:**
- [ ] **Given** January 2026 selected, **when** PDV report generated, **then** all January invoices and expenses with PDV correctly aggregated; output PDV, input PDV, and net position shown
- [ ] **Given** standard PDV period, **when** export as PDF clicked, **then** PDF generated with all required fields per Poreska Uprava format
- [ ] **Given** zero PDV period (no PDV transactions), **when** report generated, **then** zero-value report generated (still legally required)
- [ ] **Given** reminder setting enabled, **when** 14th of month arrives, **then** in-app notification "PDV rok sutra — generiši izveštaj"

**Dependencies:** Blocked by: US-010, US-020, US-040

---

### Epic EP-07: Financial Reports

---

#### US-060: View Profit & Loss Statement

| Attribute | Value |
|-----------|-------|
| Epic | EP-07: Financial Reports |
| Priority | Must Have |
| Story Points | 5 |
| Sprint | Sprint 3 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-060 |
| BR Reference | BR-006 |

**Story:**
As a **business owner**,
I want **to see my Profit & Loss statement for any period**,
So that **I can understand if my business is profitable without waiting for my accountant's monthly report**.

**Acceptance Criteria:**
- [ ] **Given** any date range (e.g., Q1 2026), **when** P&L generated, **then** all revenue accounts summed, all expense accounts summed, net profit calculated; matches double-entry ledger totals
- [ ] **Given** multi-currency org, **when** P&L generated in RSD, **then** all EUR/BAM transactions converted using locked historical exchange rates; totals in RSD
- [ ] **Given** P&L viewed, **when** user clicks on a revenue line, **then** drill-down shows individual transactions contributing to that amount

**Dependencies:** Blocked by: US-010, US-020

---

#### US-061: View Balance Sheet

| Attribute | Value |
|-----------|-------|
| Epic | EP-07 |
| Priority | Must Have |
| Story Points | 5 |
| Sprint | Sprint 4 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-061 |
| BR Reference | BR-006 |

**Story:**
As an **accountant**,
I want **to view the Balance Sheet for any date**,
So that **I can verify the accounting is correct and prepare the annual financial statements**.

**Acceptance Criteria:**
- [ ] **Given** any date (e.g., 2026-03-31), **when** Balance Sheet generated, **then** Total Assets = Total Liabilities + Total Equity (double-entry invariant)
- [ ] **Given** any imbalance detected (should never happen), **when** Balance Sheet calculated, **then** alert shown to accountant and John notified via monitoring

**Dependencies:** Blocked by: US-030, US-031

---

### Epic EP-08: Multi-Currency

---

#### US-070: Create Invoice in Foreign Currency

| Attribute | Value |
|-----------|-------|
| Epic | EP-08: Multi-Currency |
| Priority | Must Have |
| Story Points | 5 |
| Sprint | Sprint 2 |
| Assigned To | builder agent |
| Status | Backlog |
| FR Reference | FR-070 |
| BR Reference | BR-004 |

**Story:**
As a **Serbian exporter invoicing European clients**,
I want **to create invoices in EUR with automatic RSD conversion**,
So that **my financial reports are accurate in my base currency and I comply with exchange rate locking requirements**.

**Acceptance Criteria:**
- [ ] **Given** invoice created in EUR with 1 EUR = 117.5 RSD on 2026-03-01, **when** invoice saved, **then** EUR amount and 117.5 RSD rate locked; cannot be edited retroactively
- [ ] **Given** payment received 1 month later, **when** marked paid, **then** original locked rate used for P&L; exchange gain/loss calculated if payment at different rate
- [ ] **Given** ECB rate API unavailable, **when** user creates EUR invoice, **then** prompted to enter exchange rate manually; rate stored with "manual" flag

**Dependencies:** Blocked by: US-010

---

## 5. Story Estimation Guide

| Points | Complexity | Bilko Examples |
|--------|-----------|---------|
| 1 | Trivial | Update label text in Serbian, fix CSS spacing |
| 2 | Simple | Add filter to invoice list, format currency display |
| 3 | Moderate | Invoice status state machine, email notification |
| 5 | Complex | Invoice create wizard, expense form with double-entry |
| 8 | Very Complex | SEF submission service, PDV report generation |
| 13+ | Too Large | Break into sub-stories |

---

## 6. Definition of Ready Checklist

Before a story can enter a sprint:
- [ ] Story in As a / I want / So that format
- [ ] At least 2 acceptance criteria in Given/When/Then format
- [ ] Estimated in story points
- [ ] Dependencies identified and not blocking
- [ ] FR reference documented
- [ ] Accounting logic verified (for financial stories: double-entry, PDV, SEF)
- [ ] Story size ≤ 8 points

---

## 7. Story Mapping Visualization

```
USER JOURNEY: [Register] → [Create Invoice] → [Submit to SEF] → [Track Payment] → [Monthly PDV] → [P&L Report]

Phase 1 MVP:   US-001        US-010           US-011           US-012            US-050            US-060
               US-002        US-020 (Expense)                  US-040 (Bank)

Phase 2:       US-003        Croatian          eRačun            —                 HR VAT           Analytics
               (multi-org)   eRačun invoice    auto-submit
```

---

## 8. Backlog Summary

| Epic | Total Stories | Estimated Points | In Sprint | Done | Remaining |
|------|-------------|-----------------|-----------|------|-----------|
| EP-01: Auth & Org Setup | 4 | 15 | 0 | 0 | 15 |
| EP-02: Invoicing + SEF | 3 | 19 | 0 | 0 | 19 |
| EP-03: Expense Tracking | 1 | 5 | 0 | 0 | 5 |
| EP-04: Bookkeeping | 2 | 8 | 0 | 0 | 8 |
| EP-05: Bank Reconciliation | 1 | 5 | 0 | 0 | 5 |
| EP-06: VAT/PDV | 1 | 8 | 0 | 0 | 8 |
| EP-07: Reports | 2 | 10 | 0 | 0 | 10 |
| EP-08: Multi-Currency | 1 | 5 | 0 | 0 | 5 |
| **Total** | **15** | **75** | **0** | **0** | **75** |

**Velocity target:** 20 story points/sprint
**Projected completion:** Sprint 4 (8 weeks from 2026-02-23)

---

## 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 | |
| AI Director (John) | John | 2026-02-23 | |