Acceptance Criteria
Acceptance Criteria: Bilko{{PROJECT_NAME}}
Project: Bilko — Balkan Accounting SaaS{{PROJECT_NAME}}
Version: 1.0{{VERSION}}
Date: 2026-02-25{{DATE}}
Author: John (AI Director){{AUTHOR}}
Status: FinalDraft | In Review | Approved
Reviewers: Alem Bašić (CEO){{REVIEWERS}}
Document History
| Version |
Date |
Author |
Changes |
| 0.1 |
2026-02-23{{DATE}} |
John (AI Director){{AUTHOR}} |
Initial draft — Phase 1 Serbia MVP |
1.0 |
2026-02-25 |
John (AI Director) |
Finalized for v1.0 release |
1. Purpose & Methodology
1.1 What Are Acceptance Criteria?
Acceptance criteria are the conditions that the Bilkoa system must satisfy to be accepted asby workinga correctly.stakeholder.
They answer:answer the question: "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 accurateAgreed — ForSigned accountingoff features:by verifiedthe againstbusiness BalkanBEFORE GAAPdevelopment and tax lawbegins
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]
Example — User Login:
Given a registered user with valid credentials
When the user submits the login form
Then the user is redirected to the dashboard
And a session token is created and stored in a secure cookie
And the last login timestamp is updated in the database
1.3 Categories of Acceptance Criteria
| Category |
Description |
Example |
| Positive (Happy Path) |
System works as expected with valid inputs |
InvoiceSuccessful submitted to SEF successfullylogin |
| Negative (Sad Path) |
System handles invalid inputs gracefully |
SEFWrong rejects invoice —password error shown |
| Edge Case |
Boundary conditions and unusual but valid scenarios |
Zero-PDVLogin invoice;at zero-balanceexact periodsession timeout |
| Integration |
System works correctly with external services |
SEFPayment API,processed exchangevia rate API |
Financial Accuracy |
Calculations correct per tax law |
PDV = base × 0.20 exact to 4 decimal placesStripe |
| Non-Functional |
Performance, accessibility, security criteria |
InvoicePage creationloads in < 5s2 end-to-endseconds |
| Data |
Correct data storage, retrieval, and validation |
Fields saved with correct types |
2. Feature Acceptance Criteria
Module: Authentication & User Management{{MODULE_1_NAME}}
Feature: User Registration{{FEATURE_1_NAME}} (FR-001){{XXX}})
Feature Description: New users register with email/password and automatically get an organization with Serbian Chart of Accounts.{{BRIEF_DESCRIPTION}}
Business Requirement: BR-014{{XXX}}
Linked User Stories: US-001{{XXX}}, US-{{XXX}}
Positive Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-001 |
Successful registration{{SCENARIO_NAME}} |
A new user with valid email "[email protected]" and strong password{{PRECONDITION}} |
User submits form with org name "Firma d.o.o."{{ACTION}} |
Account created; org created with Kontni Okvir pre-populated; verification email sent within 2 minutes{{EXPECTED_RESULT}} |
| 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-004003 |
Duplicate email{{INVALID_INPUT_SCENARIO}} |
Account already exists for [email protected]{{PRECONDITION_WITH_BAD_DATA}} |
User submits registration with same email{{ACTION}} |
Error "Nalog sa ovim emailom već postoji"; no account created{{ERROR_MESSAGE_OR_BEHAVIOR}} |
AC-005004 |
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-007005 |
Verification link expiry{{BOUNDARY_CONDITION}} |
Verification link generated 49+ hours ago{{EDGE_CONDITION}} |
User clicks expired link{{ACTION}} |
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{{EXPECTED_RESULT}} |
Non-Functional Acceptance Criteria:
| # |
Category |
Criterion |
AC-009006 |
Performance |
Registration + org creation + CoA seedingFeature completes in < 3{{X}} seconds under normal load |
| AC-007 |
Accessibility |
Feature is fully operable by keyboard; ARIA labels present |
| AC-008 |
Security |
{{SECURITY_CRITERION_IF_APPLICABLE}} |
Feature: User Registration (FR-020)
Feature Description: New users can create accounts using email and password.
Business Requirement: BR-{{XXX}}
Linked User Stories: US-{{XXX}}
Positive Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-010 |
SecuritySuccessful registration |
PasswordA storeduser visits /register with a valid, unregistered email |
User submits form with valid email, strong password, and required fields |
Account is created; confirmation email sent; user redirected to email verification page |
| AC-011 |
Email verification sent |
Account was just created |
System processes registration |
Verification email arrives within 2 minutes with unique, expiring link |
| AC-012 |
Verification link works |
User receives verification email |
User clicks verification link |
Email verified; user redirected to login; account marked as bcryptverified |
hash
(cost
12+);
neverNegative Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-013 |
Duplicate email |
An account already exists with [email protected] |
User submits registration with [email protected] |
Error "An account with this email already exists" shown; no new account created |
| AC-014 |
Weak password |
User is on registration form |
User submits password "abc123" |
Inline error shown: "Password must be at least 8 characters and include uppercase, number, and special character" |
| AC-015 |
Invalid email format |
User is on registration form |
User submits "notanemail" in plaintextemail logsfield |
Inline validation error shown before form submission |
| AC-016 |
Empty required field |
User is on registration form |
User submits with required field empty |
Inline error shown; form not submitted |
Edge Cases:
| # |
Scenario |
Given |
When |
Then |
| AC-017 |
Email with plus addressing |
User submits [email protected] |
Registration submitted |
Account created; treated as valid email |
| AC-018 |
Verification link expiry |
Verification link generated 25+ hours ago |
User clicks expired link |
Error "Verification link has expired"; option to resend verification email shown |
| AC-019 |
Double registration attempt |
User submits form; due to slow network submits twice |
Two identical POST requests sent |
Only one account created; second request returns appropriate error |
Feature: User Login (FR-002)021)
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 |
VerifiedA verified user with valid credentials |
User submits login form |
Authenticated; access token (15min) + refresh token (30d) set; redirected to dashboarddashboard; session created |
| AC-021 |
SilentRemember token refreshme |
User'sA accessverified tokenuser expired;checks refresh"Remember token validMe" |
User makessubmits APIlogin callform |
NewSession accesspersists tokenfor issued30 silently;days; user notstays logged out;in originalafter requestbrowser succeedsclose |
Negative Scenarios:
| # |
Scenario |
Given |
When |
Then |
| AC-022 |
Wrong password |
RegisteredA registered user exists |
User submits wrongincorrect password |
Generic error "PogrešanInvalid email ilior lozinka"password" (no userenumeration); enumeration)login not granted |
| AC-023 |
Non-existent email |
No account for this email |
User submits login |
Same generic error "PogrešanInvalid email ilior lozinka"password" (prevent user enumeration) |
| AC-024 |
Account locked |
User has made 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 again |
ErrorMessage: "MolimoAccount vastemporarily potvrditelocked. emailTry adresu"again within option15 to resendminutes." |
Edge Cases:
| # |
Scenario |
Given |
When |
Then |
AC-026025 |
Session expiry |
User has been idle for 30+31 minutes; access token expired; refresh token also expiredminutes |
User attempts actionto navigate |
Redirected to login; messagesession cleared; unsaved work warning shown |
| AC-026 |
Login on unverified account |
Account created but email not verified |
User attempts to log in |
Error "VašaPlease sesijaverify jeyour istekla"email before logging in" with option to resend |
Module: Invoicing
Feature: Create Invoice with PDV{{NEXT_FEATURE_NAME}} (FR-010){{XXX}})
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{{NEXT}} |
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{{NEXT}} |
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{{NEXT}} |
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){{EXTERNAL_SERVICE}} |
Valid invoice submission{{SCENARIO}} |
SEF returns invoice ID + "Accepted" status within 30s{{EXPECTED}} |
SEFSandbox sandbox/ Mock |
| 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 |
InvoiceTransactional PDFemail delivery |
RecipientEmail receives PDF invoicereceived within 2 minutes of send |
Mailtrap / staging |
INT-005003 |
Exchange rate API (ECB){{PAYMENT_PROVIDER}} |
EUR/RSDSuccessful rate fetchpayment |
RateTransaction fetchedrecorded; andconfirmation cached;shown; usedwebhook for multi-currency invoicereceived |
ECBStripe test endpointmode |
INT-006004 |
Exchange{{PAYMENT_PROVIDER}} |
ratePayment APIdeclined |
User sees friendly error; no order created; no double-charge |
Stripe test mode |
| INT-005 |
{{THIRD_PARTY_API}} |
API unavailabletimeout |
CachedSystem rateshows (<user-friendly 24h)error; used;request iflogged; no cache,data user prompted for manual entrycorruption |
Mocked timeout |
INT-007006 |
SEF API{{THIRD_PARTY_API}} |
SEFAPI credentials invalidunavailable |
ErrorSystem showndegrades togracefully; user:non-dependent "SEFfeatures akreditivistill su nevalidni. Proveri podešavanja."work |
SEFService sandboxunavailable mock |
4. Non-Functional Acceptance Criteria
| # |
Criterion |
Target |
Test Method |
| NF-AC-001 |
DashboardAll initialpages load within target time |
< 33s secondsinitial, (4G)< 1.5s subsequent |
Lighthouse on staging |
| NF-AC-002 |
DashboardAll subsequentAPI navigationendpoints respond within target |
< 1500ms secondat p95 under normal load |
Lighthousek6 warmload cachetest |
| NF-AC-003 |
InvoiceCore creationWeb (fullVitals wizard + API save)pass |
LCP < 52.5s, secondsCLS end-to-end< 0.1, FCP < 1.8s |
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 dataLighthouse |
4.2 Accessibility
| # |
Criterion |
Target |
Test Method |
| NF-AC-010 |
No critical accessibility violations |
0 critical violations |
axe-core onautomated all pagesscan |
| NF-AC-011 |
Invoice wizard keyboardKeyboard navigation complete |
CompleteAll wizardfeatures operable without mouse |
Manual keyboard test |
| NF-AC-012 |
Color contrast compliant |
≥ 4.5:1 (normal text), ≥ 3:1 (large text)text/background |
Contrast checker — Bilko #00E5A0 on dark verified |
4.3 Security
| # |
Criterion |
Target |
Test Method |
| NF-AC-020 |
OrganizationNo dataOWASP isolationTop 10 vulnerabilities |
User0 fromcritical/high Org A cannot access Org B datafindings |
APIOWASP test:ZAP send request with Org A token for Org B resource — must return 403scan |
| NF-AC-021 |
NoAll financialuser datainputs in client-side logssanitized |
No amounts,XSS/injection invoices, or PII in browser consolevulnerabilities |
ManualSAST browser+ DevToolsmanual reviewtesting |
| NF-AC-022 |
InputNo injectionsensitive preventiondata in client-side code |
No SQL/XSSAPI injectionkeys, vulnerabilitiestokens in browser |
OWASPCode ZAPreview + Snykbrowser 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 unchangedDevTools |
5. UAT Scenario Mapping
| AC ID |
AC Description |
UAT Scenario ID |
UAT Tester |
Status |
AC-001010 |
Successful registration |
UAT-001 |
Beta SMB owner{{TESTER}} |
Not Started |
AC-030011 |
InvoiceEmail PDVverification 20%sent |
calculationUAT-002 |
|
|
| AC-020 |
Successful login |
UAT-003 |
|
|
| INT-003 |
Payment success |
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 |
DashboardPage load < 3sperformance |
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{{CRITERION}} |
FR-001{{XXX}} |
BR-014{{XXX}} |
US-001{{XXX}} |
AC-020010 |
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 expenseregistration |
FR-020 |
BR-009{{XXX}} |
US-020 |
AC-080 |
PDV report January |
FR-050 |
BR-002, BR-006 |
US-050{{XXX}} |
Full traceability: RTM.md[requirements-traceability-matrix.md](requirements-traceability-matrix.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 |
|
CEOClient (Alem)Representative |
Alem Bašić |
|
|