# Architecture & Business Rules

## Folder structure

```text
softedgelab-finance-api/
├── database/schema.sql          MySQL 8 reference schema
├── docs/                        API, architecture, security, checklist
├── scripts/                     DB initializer, admin bootstrap, syntax checks
├── src/
│   ├── config/                  Environment contract and MySQL pool
│   ├── lib/                     Validation, encryption, payroll, periods, errors
│   ├── middleware/              JWT role authorization
│   ├── modules/                 Domain-specific REST routers and business logic
│   ├── workers/                 SMTP notification queue processing
│   ├── app.js                   Express wiring / security middleware
│   └── server.js                Database health check and graceful shutdown
├── storage/private/             Non-public PDF/image uploads, gitignored
├── tests/                       Payroll/validation unit tests
├── .env.example
├── package.json
└── README.md
```

Domain router modules contain SQL and business services together for readability at this size. As transaction logic grows, split them into `*.service.js`, `*.repository.js`, `*.schema.js` under each module without changing API contracts.

## Data ownership

- Admin: full HR + financial writes; **no API endpoint** returns password hashes.
- Employee: token identity bound to `employee_id`; self profile, contact, bank details, personal leave and own payslips only.
- Shareholder: read-only shared summary, allocations, distributions; **no access** to employee salary rows or bank account personal data.
- No role or employee dropdown governs backend permission—only authenticated JWT and a current database status check.

## Financial invariants

1. Monthly data is linked to one `financial_periods` row; closed/locked periods block new records.
2. Income and expense `INSERT` + linked bank ledger `INSERT` occur in a single transaction. This prevents half-posted ledger states.
3. Pending receivables are **not** bank credits; receipts must include amount, receipt date and bank account.
4. Monthly closing snapshots are historical point-in-time values. **Never sum** month-end balances as a cash balance. Current ledger balance = opening balance + credits − debits.
5. Do not sum amounts across multiple currencies. Current reports use **LKR-only** transaction values; currency exchange requires separate FX transactions/conversion policy.
6. Employee EPF is **deducted** from gross; employer EPF and ETF **add to employer cost**. Gross salary itself is not automatically paid out from a bank account by approving a payroll run.
7. Share distribution records are approval artifacts, **not** automatic bank payments.
8. Financial rows cannot be deleted through API. Use properly audited reversal entries in a future accounting module.
9. If a payroll amount already exists as an `expenses` row, the reporting view may double count it. **Do not enter salaries/EPF as operating expenses** when recording them in payroll. Later add a normalized accounting ledger for statutory expense posting.

## Leave workflow

- Employee creates a pending request (email notification row inserted in the same transaction).
- Admin approves or rejects. Rejection requires reason; each decision is appended to `leave_decisions` (old decisions are not erased).
- Notification is queued for employee contact email, if present.
- Run `npm run worker:email` under cron/worker scheduler after configuring SMTP. Failure creates a failed notification record for operational attention.
- Leave balances, conflict checks, public holidays and employee-specific entitlements are a **future requirement**; they are not silently assumed.

## Error handling and validation

- Zod validates request shape, field length, date correctness, decimals, statuses, IDs and strong passwords.
- MySQL UNIQUE, CHECK, and FK constraints are second-line defenses.
- Business rules are checked inside transactions for payroll approval, transfers and leave decision changes.
- Errors return a stable status/code; raw SQL errors are not returned to clients.

## Database expectations

Schema is `database/schema.sql`, copied unchanged from the previous MySQL deliverable. It must be run on an **empty** MySQL 8.0.16+ database. `db:init` uses a separate one-time multi-statement connection; the app pool disables multi-statements. No migration runner is included yet.

## Recommended future work

Introduce Flyway/Knex migrations; build idempotency tokens; transaction reversal model; account reconciliation; payroll-to-bank payouts; leave balances and policies; per-user audit trails; outbox queue with locking/retries; token refresh and revocation; banking identity verification; production attachment scanning; production integration and stress tests.
