# Accounting Module — Phase 3 Tasks (General Ledger, Period ও Reports)

> এখানকার প্রতিটা task আলাদা — একজন বা একটা টিম নিজেরটা নিয়ে এগোতে পারে।
> Phase 3 শেষ মানে post হওয়া data থেকে GL, Trial Balance, P&L, Balance Sheet, Cash Flowসহ financial report বের হবে; Excel/PDF-এ নামানো যাবে; period বন্ধ ও আবার খোলা যাবে; হিসাবের ভেতরের মিল (Reconciliation) নিজে নিজেই চেক হবে। Client-এর accountant হাতে মিলিয়ে সই না করলে Phase 4 শুরু হয় না।
> ক্রম: ledger query → cache → GL → Trial Balance → P&L → Balance Sheet → Cash Flow mapping → অন্য report → Cash Flow → reconciliation → export → period বন্ধ → period lock → performance ও test → accountant-এর সই।
> Estimate, Owner, Sprint ইচ্ছা করে খালি — planning শেষ হলে ভর্তি হবে। View Order master build sequence (Pack ১০.১, ক্রম ২২ থেকে ৩২) অনুযায়ী, Phase 2-এর পর ধারাবাহিক (৩৮ থেকে ৫২)।

## সব Phase 3 task-এ প্রযোজ্য সাধারণ নিয়ম (প্রতিটা task নিজের DoD-তে এগুলো ধরে নেয়)

- Database: MySQL, InnoDB, `utf8mb4`। Table prefix `acc_`।
- Primary key ধরন `ACC-P0-02`-এর Convention Sheet অনুযায়ী (অনুমান: bigint unsigned বা ULID)। সব `*_id` FK একই ধরনের।
- প্রতিটা business table-এ `company_id NOT NULL`, `companies(id)`-এর FK, আর প্রতিটা composite index-এ `company_id` সবার আগে।
- টাকা `DECIMAL(20,4)`। Enum `VARCHAR(32)` + `CHECK`, MySQL enum নয়।
- বদলানো যায় এমন row-এ `version INT UNSIGNED NOT NULL DEFAULT 1`; update-এ `WHERE version = ?` আর `version = version + 1`।
- Posted বা লেজারের row কখনো মোছা হয় না। `ON DELETE RESTRICT` ডিফল্ট; লেজারের table-এ `CASCADE` নয়।
- Migration: প্রতি table-এ আলাদা, প্রয়োগ হওয়া migration সম্পাদনা নয়, পিছনে সামঞ্জস্যপূর্ণ (আগে যোগ, পরে বদল, শেষে মোছা), `down()` নিরাপদ। এই Phase-এ একটা column-যোগ (expand) migration আছে: `acc_periods.balance_version` (P3-02)।
- **account_id বা journal_id বা period_id রাখা table-এ composite FK** `(company_id, account_id)` / `(company_id, journal_id)` / `(company_id, period_id)`, একা `*_id`-র FK নয়।
- **Ledger-এ কোন journal আসে (R3.1, R3.2, AC3.5):** `acc_journals.status IN ('posted','reversed')`। Pack-এর স্পষ্টীকরণ: Blueprint-এর "শুধু posted journal report-এ আসে"-র মানে এটাই। `reversed` অবস্থার journal আসলে posted-ই, শুধু তার reversal-ও posted হয়েছে। দুটো একসাথে ধরলে নিট প্রভাব শূন্য। `reversed` বাদ দিলে হিসাব ভুল হবে। `draft`, `submitted`, `approved`, `rejected`, `cancelled` কখনো কোনো report-এ আসে না। এই Phase-এর **প্রতিটা** query এই নিয়ম মানে।
- **তারিখ (R3.3):** `journal_date`-এর ওপর, শুরু ও শেষ দিন অন্তর্ভুক্ত।
- **চিহ্ন (R3.4):** Debit-প্রধান type (asset, expense): balance = Debit − Credit। Credit-প্রধান (liability, equity, revenue): Credit − Debit।
- **টাকার অঙ্ক string:** PHP-র float কোথাও নয়। BCMath বা decimal library, API response-এ string।
- **Snapshot:** যে report একাধিক query চালায় সেটা একটা read-only `START TRANSACTION WITH CONSISTENT SNAPSHOT` (REPEATABLE READ) transaction-এ, যাতে চলার মাঝে নতুন Post এলেও সংখ্যা নিজের সাথে মেলে।
- **সব report ও export একই `company_id` scope-এ** (AC3.6)। অন্য company-র account/journal চাইলে `ACC_COMPANY_FORBIDDEN`। Report চালানো `accounting.report.run` audit-এ যায়।
- **Report দেখার permission:** `accounting.report.view`। নামানো `accounting.report.export` (আলাদা)।
- **সব report test MySQL-এ চলবে** (SQLite-এ snapshot, window function আর generated column আলাদা আচরণ করে)।
- **DoD-র সাধারণ অংশ:** Service–Repository architecture (interface সহ), Form Request, API Resource; `company_id` tenant scope + IDOR test; Feature test (happy path, validation, unauthorized/forbidden); audit event `AccountingAudit` দিয়ে; error `AccountingError` enum-এর code দিয়ে (নতুন code পরে যোগ হলে শুধু enum-এ); flag বন্ধ রেখে merge নিরাপদ; বিদ্যমান ERPFlow regression সবুজ; Project/Payroll/Asset/Commissioning-এর code অপরিবর্তিত।

**Phase 3 নির্ভরতা-ছবি:**

```
Phase 2 (P2-16 সবুজ) + P0-05 (OD-14) ─► P3-01 ─┬─► P3-02 (+P1-08) ──┐
                                                 ├─► P3-03 ───────────┤
                                                 ├─► P3-05 ─► P3-06 ──┤
                                    P3-01+P3-02 ─► P3-04 ─────────────┤
                       P1-07+P1-04 ─► P3-07 ─┐                         │
                            P3-01+P3-06+P3-07 ─► P3-08 ───────────────┤
                       P3-01+P2-15 ─► P3-09 ──────────────────────────┤
                                                                       ▼
                     P3-03..06 + P3-08 ─► P3-13 (Reconciliation) ─┬─► P3-11 (Period বন্ধ, +P1-08,P2-10,P3-01) ─► P3-12 (+P2-12)
                     P3-03..09 ─► P3-10 (Export)                  │
                                                                   ▼
                     P3-01..P3-13 সব ─► P3-14 (Performance ও test) ─► P3-15 (সই) ─► Phase 4 (P4-01), P5-01, P5-02
```

**আগের Phase থেকে Phase 3 যা পায় (প্রতিটা card এগুলো ধরে নেয়):**

| উৎস | কী পায় | কোথায় ব্যবহার |
| --- | --- | --- |
| `P1-06` | `acc_accounts` (type, `parent_id` গাছ, `is_group`, `is_active`, `cashflow_class`, `is_cash_equivalent`), `uq_acc_accounts_company_id` | সব report, P3-07 |
| `P1-08` | `acc_periods` (`status` `open`/`soft_closed`/`closed`, `closed_at/by`, `reopened_at/by`, `version`), `PeriodResolver::forDate`, `PeriodGenerator` | P3-02, P3-06, P3-11, P3-12 |
| `P1-09` | `acc_journals`, `acc_journal_lines`, index `idx_acc_journals_company_status_date`, `idx_acc_jl_company_account`, `PostedJournalProbe` আসল implementation | P3-01, P3-13 (RC6) |
| `P1-10` | DB guard trigger, `Schema::appendOnly('table')` helper, `acc_journals.posted_hash` column | P3-13 (RC9, `acc_reconciliation_runs`), P3-11 |
| `P1-12` | `AccountingJob`/`RunsInCompanyContext`, `AccountingAudit`, `AccountingError` enum, correlation id | সব job, error, audit |
| `P1-03`/`P1-04` | `SettingsService`, `SettingRegistry`, second approval (change request), `acc_settings_history` | P3-07, সব setting পড়া |
| `P2-10` | `SodGuard::assert(...)` | P3-11 |
| `P2-12` | `PostingService`, `PeriodGuard::check` hook (Phase 2-এ শুধু `warn`), `JournalPosted` afterCommit event | P3-02, P3-12 |
| `P2-13` | Reversal/Adjustment (reversal Post হলে মূল journal `reversed`) | সব report-এর `reversed` নিয়ম, P3-12 |
| `P2-15` | Journal Register ও Approval Register (filter সহ) | P3-09 |
| `P2-16` | Phase 2 test suite সবুজ, Golden suite-এর প্রথম ৬টা | P3-01 শুরুর শর্ত |

---

## ACC-P3-01 — Ledger query layer

Project
ERPFlow
Module
Accounting Module
Type
Feature (service)
Title
Ledger query layer — Backend
Branch Name
`feature/accounting-ledger-query-layer`
View Order
38
Description
**Summary**
Report বানানোর সবাই একই query layer থেকে সংখ্যা তুলবে — Trial Balance, GL, P&L, Balance Sheet, Cash Flow যেখানেই দেখো, একই ফল আসবে; কেউ আলাদা SQL লিখলে পরে মিল মেলানোর ঝামেলা হবে।

**Task serial**
`ACC-P3-01 — Ledger query layer` · Feature

**Depends on**
- `ACC-P2-16` (Phase 2 exit: Phase 2 test suite সবুজ, Golden suite-এর প্রথম ৬টা সবুজ)
- `ACC-P0-05` (OD-14 spike-এর ফল)
- OD-14 (ধরা হয়েছে: Option A, posted `acc_journal_lines` নিজেই ledger, আলাদা projection নয়)

**Relation: আগের task**
- `P2-16` প্রমাণ করেছে Post, Reversal, Opening post, Register সব সঠিক। তাই `status IN ('posted','reversed')` data নির্ভরযোগ্য, এই task সেটার ওপর পড়ে।
- `P2-12` (Posting service) ledger-এ বদল আনার একমাত্র পথ। এটা `posted` journal বানায়, `period_id` আর `voucher_no` বসায়। `P2-13` reversal Post হলে মূল journal `reversed` করে। এই দুই অবস্থাই এই task-এর ছাঁকনি।
- `P1-09` দিয়েছে `acc_journals`, `acc_journal_lines` আর দুটো index `idx_acc_journals_company_status_date (company_id, status, journal_date)` ও `idx_acc_jl_company_account (company_id, account_id, journal_id)`। এই index-দুটোই এই task-এর query-র গতি।
- `P1-06` দিয়েছে `acc_accounts` (type, `parent_id` গাছ, সর্বোচ্চ গভীরতা 10)। `account_group_id` filter-এর descendant খোঁজা এই গাছ ধরে।
- `P0-05` OD-14 নিয়ে prototype-এ বড় data-য় GL query-র গতি মেপে Option A-কে সমর্থন করেছে বলে ধরা হয়েছে। Option B (আলাদা projection) এই task-এ নেই।
- `P1-12` দিয়েছে `AccountingError`, `AccountingAudit`, company context। এই task-এ নিজস্ব business error নেই (শুধু পড়া), তাই এগুলো সরাসরি ব্যবহার হয় না, কিন্তু query-র `company_id` সবসময় context থেকে আসে।

**Relation: পরের task (কে অপেক্ষা করছে)**
- **P3-02 থেকে সব report card** (`P3-03`, `P3-04`, `P3-05`, `P3-06`, `P3-08`, `P3-09`) এই task-এর চারটা method ছাড়া কিছু ডাকে না। কেউ নিজের SQL লিখবে না। এটাই "সংখ্যা সবখানে এক" নিশ্চিত করার উপায়।
- `P3-02` cache `LedgerQueryService::balancesAsOf`-এর ভেতরে বসে: শুরুর তারিখের আগের সম্পূর্ণ period cache থেকে, বাকিটুকু সরাসরি line থেকে। তাই এই task-এ cache-হীন সরল রূপ আগে আসে, `P3-02` সেটাকে ভেতরে বদলায়, বাইরের contract বদলায় না।
- `P3-11` period বন্ধের যাচাই (`tb_balanced` ইত্যাদি) এই layer ব্যবহারকারী service-ই ডাকে।
- `P3-13` check-গুলোর মধ্যে RC6 ইচ্ছা করে এই layer-কে **পার না হয়ে** raw SQL দিয়ে একই সংখ্যা হিসাব করে, যাতে এই task-এর ভুল ধরা পড়ে। তাই এই task-এর লেখা query আর RC6-এর query দুটো আলাদা path হতে হবে।
- `P3-14` (performance): ধীর হলে নিচের contingency চালু হবে কি না সেখানে ঠিক হয়।
- Phase 6-এর `P6-09`, `P6-12`, `P6-13` (dimension) পরে এই layer-এ `withDimensions` বসাবে। এই task-এ সেটা নেই।
- **খোলা প্রশ্ন OP-1:** চার method-এর return-এর সঠিক আকার (array/DTO), আর `account_group_id`-এর descendant খোঁজার কৌশল (গাছ PHP-তে নাকি recursive query) Pack-এ নির্ধারিত নয়। শর্ত শুধু: টাকার অঙ্ক string, গাছ একবার পড়া, গভীরতা ≤ 10। Tech lead PR-এর আগে ঠিক করবে।
- **খোলা প্রশ্ন OP-2:** Pack বলছে "AC3.10 (পরে)": cache-সংক্রান্ত AC `P3-02`-এ প্রমাণ হবে, এই task-এ নয়।

**Related tables / schema**
কোনো নতুন table নেই। শুধু পড়া:
- `acc_journal_lines` (`company_id`, `journal_id`, `line_no`, `account_id`, `debit`, `credit`, `cost_center_id`)
- `acc_journals` (`status`, `journal_date`, `period_id`, `voucher_no`, `narration`, `reference_no`)
- `acc_accounts` (`type`, `parent_id`, `is_group`, `is_active`)

মূল query (নমুনা, সব report-এর ভিত্তি):

```sql
SELECT l.account_id, SUM(l.debit) AS d, SUM(l.credit) AS c
FROM acc_journal_lines l
JOIN acc_journals j ON j.company_id = l.company_id AND j.id = l.journal_id
WHERE l.company_id = :c
  AND j.status IN ('posted','reversed')
  AND j.journal_date <= :asof
GROUP BY l.account_id
```

**Business rules (R3.1 থেকে R3.5, R3.11)**
- Ledger = `status IN ('posted','reversed')` journal-এর line (উপরের সাধারণ নিয়ম)। `draft`, `submitted`, `approved`, `rejected`, `cancelled` কখনো আসে না।
- তারিখ `journal_date`-এর ওপর, শুরু ও শেষ দিন অন্তর্ভুক্ত (R3.3)।
- চিহ্ন নিয়ম (R3.4): Debit-প্রধান type (asset, expense) balance = Debit − Credit। Credit-প্রধান (liability, equity, revenue) balance = Credit − Debit।
- সব query `company_id` দিয়ে শুরু। Join-এও `company_id` মেলানো (`j.company_id = l.company_id`), যাতে composite index কাজ করে।
- সব টাকার অঙ্ক **string** হিসেবে ফেরত, PHP-র float কোথাও নয় (BCMath বা decimal library)।
- যে report একাধিক query চালায় সে একটাই `START TRANSACTION WITH CONSISTENT SNAPSHOT` (REPEATABLE READ, read-only) transaction-এ চালাবে, যাতে চলার মাঝে নতুন Post এলেও সংখ্যা নিজের সাথে মেলে। এই wrapper এই task বানায়, সব report ব্যবহার করে।
- Filter: `account_ids`, `account_group_id` (সব descendant সহ), `cost_center_id`, `type`।
- Ledger-এর উৎস posted line নিজেই (OD-14 Option A)। আলাদা projection table নেই।

**DB flow**
কোনো লেখা নেই। শুধু পড়া:
1. (যে report একাধিক query চালায় তার জন্য) snapshot transaction শুরু।
2. `balancesAsOf(companyId, date, filters)`: `journal_date <= :asof` পর্যন্ত account ধরে Debit/Credit যোগফল।
3. `movements(companyId, from, to, filters)`: `journal_date BETWEEN from AND to` ধরে একই।
4. `accountRows(companyId, accountId, from, to, cursor, limit)`: এক account-এর line, `ORDER BY j.journal_date, j.id, l.line_no`, keyset cursor।
5. `netProfit(companyId, from, to)`: `revenue` ও `expense` type-এর movement, নিট লাভ = Revenue (Credit − Debit) − Expense (Debit − Credit)।
6. Snapshot শেষ।

**Task scope**
1. `LedgerQueryService`-এর চারটা method: `balancesAsOf`, `movements`, `accountRows`, `netProfit`।
2. চিহ্ন নিয়ম প্রয়োগ (type ধরে)।
3. Snapshot wrapper (read-only, consistent snapshot, REPEATABLE READ)।
4. Decimal helper (string in, string out, BCMath বা decimal library)।
5. Filter বাস্তবায়ন: `account_ids`, `account_group_id` (descendant সহ), `cost_center_id`, `type`।
6. Fixture data (হাতে হিসাব করা মান সহ) আর test।

**Out of scope**
- কোনো report endpoint বা UI (সেগুলো `P3-03` থেকে)।
- Derived cache (`P3-02`)। এই task-এ `balancesAsOf` শুধু সরাসরি line থেকে।
- আলাদা ledger projection (OD-14 Option B)।
- **Performance contingency এখন করা হচ্ছে না।** `P3-14`-এর পরিমাপে join ধীর হলে দুটো বিকল্প: (১) `acc_journal_lines`-এ `entry_date` কপি রাখা (draft-এ journal-এর তারিখের সাথে সিঙ্ক, post-এর পর অপরিবর্তনীয়) আর `(company_id, account_id, entry_date)` index, (২) OD-14-এর Option B। দুটোর কোনোটাই এখন করা হচ্ছে না, মেপে ঠিক হবে।

**Contract**
```
LedgerQueryService::balancesAsOf(companyId, date, filters)
LedgerQueryService::movements(companyId, from, to, filters)
LedgerQueryService::accountRows(companyId, accountId, from, to, cursor, limit)
LedgerQueryService::netProfit(companyId, from, to)
```
সব টাকার অঙ্ক string। কোনো HTTP endpoint নেই।

**Permission**
কোনো নতুন permission নেই। এই service HTTP থেকে সরাসরি ডাকা হয় না, ডাকে report controller (যাদের `accounting.report.view` লাগে)।

**Error**
কোনো business error নেই (শুধু পড়া)।

**Audit**
এই service নিজে audit লেখে না। `accounting.report.run` লেখে report endpoint (`P3-03` থেকে)।

**Acceptance**
AC3.1, AC3.5, AC3.10 (পরে, `P3-02`-এ), AC3.12

**Test**
- নির্দিষ্ট fixture data দিয়ে প্রতিটা method-এর ফল হাতে হিসাব করা সংখ্যার সাথে মেলে।
- `reversed` journal ধরা না হলে test ব্যর্থ হয় (মূল ও reversal একসাথে ধরলে নিট শূন্য)।
- Snapshot test: query-র মাঝে অন্য connection থেকে Post করে দেখা সংখ্যা বদলায়নি।
- Draft, submitted, approved, rejected, cancelled কখনো আসে না।
- Debit-প্রধান ও Credit-প্রধান type-এর চিহ্ন ঠিক।
- `account_group_id` filter descendant সহ।
- টাকার অঙ্ক string, float নয়।

**Deliverable**
`LedgerQueryService`, snapshot wrapper, decimal helper, fixture, test।

**Sub-tasks**
চারটা method, চিহ্ন নিয়ম, snapshot wrapper, decimal helper, test

**Definition of done**
- সাধারণ DoD (উপরে) পূরণ।
- AC3.1, AC3.5 এই layer-এর স্তরে সবুজ (`LedgerStatusFilterTest`-এর ভিত্তি)।
- কোনো float ব্যবহার নেই (code review-র পরীক্ষা)।
- MySQL-এ test চলেছে।

**Architecture constraints**
- Report গুলো এই service ছাড়া `acc_journal_lines` সরাসরি query করতে পারবে না (RC6 ছাড়া, যেটা ইচ্ছাকৃত স্বাধীন path)।
- সব query `company_id` দিয়ে শুরু।
- `reversed` কখনো বাদ নয়।

Owner
Dev A
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-02 — Derived balance cache

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service + job)
Title
Derived balance cache — Backend
Branch Name
`feature/accounting-balance-cache`
View Order
39
Description
**Summary**
পুরোনো period-এর মোট বারবার গণনা না করে cache দিয়ে report দ্রুত চলবে; cache কখনোই ভুল সংখ্যা দেখাবে না — stale হলে সরাসরি line থেকে হিসাব, পেছনে rebuild।

**Task serial**
`ACC-P3-02 — Derived balance cache` · Feature

**Depends on**
- `ACC-P3-01` (`LedgerQueryService`)
- `ACC-P1-08` (`acc_periods`)

**Relation: আগের task**
- `P3-01` দিয়েছে `movements(from, to)`, যা `rebuild` এই period-র পরিসরে ডাকে, আর `balancesAsOf`, যার ভেতরে cache বসানো হবে।
- `P1-08` দিয়েছে `acc_periods` (`id`, `start_date`, `end_date`, `status`, `version`) আর `uq_acc_periods_company_id (company_id, id)`, যেটা `acc_period_balances`-এর composite FK-র লক্ষ্য। এই task `acc_periods`-এ `balance_version` column যোগ করে (expand migration)।
- `P2-12` Post-এর শেষে (ধাপ 17, commit-এর পরে `afterCommit`) `JournalPosted` event ছাড়ে। Phase 2-এ কেউ শোনে না। এই task-এ listener বসে।
- `P1-12` দিয়েছে company context-সহ job base (`AccountingJob`/`RunsInCompanyContext`) আর audit।
- `P1-06` থেকে `acc_accounts` (composite FK `(company_id, account_id)`-এর লক্ষ্য `uq_acc_accounts_company_id`)।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-04` (Trial Balance): গতির জন্য cache পড়ে (opening balance)।
- `P3-13` RC7: প্রতিটা **বৈধ** cache row মূল line-এর যোগফলের সাথে মেলায়।
- `P3-11` (Period বন্ধ): `acc_periods.balance_version` পড়ে লম্বা যাচাই-র আগে ও পরে তুলনা করে, আর "Post আটকে না রেখে বন্ধ" নকশা (Pack ৯.৫) এই column-এর ওপর দাঁড়িয়ে। তাই `balance_version` কেবল commit-এর পরে বাড়ে।
- `P3-14`: cache থাকা-না-থাকা অবস্থায় performance পরিমাপ আর `CacheStalenessTest` (AC3.10)।
- Phase 6-এর `P6-13` একই `balance_version` ধারণা অন্য table-এ নেবে ("P3-02-এর মতো"), এই task-এর নকশা পরিবর্তন করতে গেলে সেটা মনে রাখা।
- **খোলা প্রশ্ন OP-3:** `P3-03`, `P3-05`, `P3-06` card-গুলোর `নির্ভর করে` তালিকায় `P3-02` নেই, কিন্তু build sequence-এ `P3-02` আগে (ক্রম ২৩) আর `balancesAsOf` cache ব্যবহার করে। এখানে ধরা হলো: তারা শুধু `P3-01`-এর contract দেখে, cache ভেতরে স্বচ্ছভাবে বসে, কোনো report কোড বদলাতে হয় না।

**Related tables / schema**

**`acc_period_balances`** (period ধরে নড়াচড়া, জমা নয়)

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `period_id` | FK | না | Composite FK `(company_id, period_id)` → `acc_periods (company_id, id)` |
| `account_id` | FK | না | Composite FK `(company_id, account_id)` → `acc_accounts (company_id, id)` |
| `debit_total`, `credit_total` | DECIMAL(20,4) | না | ওই period-এর Debit ও Credit-এর যোগফল |
| `line_count` | INT UNSIGNED | না |  |
| `period_version` | BIGINT UNSIGNED | না | হিসাবের সময়ের `acc_periods.balance_version` |
| `computed_at` | TIMESTAMP | না |  |

Constraint ও index:
- Unique `uq_acc_pb (company_id, period_id, account_id)`
- Index `idx_acc_pb_period (company_id, period_id)`

**Schema যোগ (expand migration):** `acc_periods.balance_version BIGINT UNSIGNED NOT NULL DEFAULT 0`।

**Business rules (R3.12)**
- এটা কখনো উৎস নয়, শুধু দ্রুত করার জন্য। ভুল হলে ফেলে দিয়ে আবার বানানো যায়।
- **Cache কখনো ভুল সংখ্যা দেবে না।** `stale` হলে ব্যবহার হয় না।
- বৈধ কি না: একটা cache row বৈধ হলে `row.period_version = acc_periods.balance_version`। না মিললে `stale`।
- বাতিল (invalidate) Post-এর transaction-এ নয়, commit-এর **পরে** (`afterCommit`), তাই Post-এ hot row (এক period-এর row-এ সব Post-এর লড়াই) তৈরি হয় না।
- Post এবং balance_version বাড়ার মাঝের ফাঁকে কেউ cache পড়লে ক্ষণিকের জন্য পুরোনো সংখ্যা দেখার ঝুঁকি: Pack এটাকে "বৈধ" row ধরে, কারণ `balance_version` তখনো বদলায়নি। (এই ছোট ফাঁকের জন্য `P3-11` অতিরিক্ত `MAX(posted_at)` তুলনা রাখে।)

**DB flow: invalidate**
1. Post commit হয় (`P2-12`)।
2. `JournalPosted` event `afterCommit`-এ listener-এ আসে।
3. `UPDATE acc_periods SET balance_version = balance_version + 1 WHERE company_id = ? AND id = ?` (ওই journal-এর period)।
4. ব্যর্থ হলে আবার চেষ্টা; Post-কে প্রভাবিত করে না (Pack ৯.২ ধাপ 12)।

**DB flow: `PeriodBalanceCache::rebuild(companyId, periodId)`**
1. `balance_version` পড়া (`v1`)।
2. `LedgerQueryService::movements(period-র পরিসর)` (`P3-01`)।
3. Transaction-এ ওই period-এর পুরোনো row মুছে নতুন row বসানো, `period_version = v1`।
4. নোট: rebuild-এর মাঝে কোনো Post এলে `balance_version` `v2` হয়ে যাবে আর এই row আপনা-আপনি `stale`; ক্ষতি নেই।

**DB flow: ব্যবহার**
`LedgerQueryService::balancesAsOf` রিপোর্টের শুরুর তারিখের আগের **সম্পূর্ণ** period-গুলো cache থেকে, বাকিটুকু সরাসরি line থেকে। কোনো period `stale` পেলে সেটা সরাসরি line থেকে হিসাব, আর পেছনে `RebuildPeriodBalancesJob` queue-তে।

**Task scope**
1. Migration ×2: `acc_period_balances` আর `acc_periods.balance_version`।
2. `PeriodBalanceCache` service: `rebuild`, বৈধতা পরীক্ষা, পড়া।
3. `JournalPosted` listener (commit-এর পরে, retry সহ)।
4. `LedgerQueryService::balancesAsOf`-এ cache ব্যবহার (`stale` হলে সরাসরি + rebuild queue)।
5. `RebuildPeriodBalancesJob(companyId, periodId)`: idempotent, company context (`P1-12`) সহ।
6. রাতের sweep: সব `stale` period-এর জন্য rebuild।
7. Test।

**Out of scope**
- RC7 check class নিজে (`P3-13`)।
- Cache কোনো report-এর উৎস হিসেবে নয়, শুধু গতি।
- `balance_version`-কে Post-এর transaction-এ বাড়ানো (ইচ্ছাকৃতভাবে নিষিদ্ধ)।

**Contract**
- `PeriodBalanceCache::rebuild(companyId, periodId)`
- Event listener: `JournalPosted` → `acc_periods.balance_version + 1`
- Job: `RebuildPeriodBalancesJob(companyId, periodId)` (idempotent, company context সহ), আর রাতের sweep job।
- কোনো public HTTP endpoint নেই।

**Permission**
প্রযোজ্য নয় (HTTP নেই, job)।

**Error**
কোনো user-মুখী error নেই।

**Audit**
`accounting.cache.rebuilt`

**Acceptance**
AC3.10

**Test**
- Post-এর পর cache `stale`।
- Rebuild-এর পর বৈধ।
- Rebuild-এর মাঝে Post (row `stale` হয়ে যায়)।
- Cache ও সরাসরি query-র ফল সবসময় এক।
- Listener ব্যর্থ হলে Post প্রভাবিত হয় না।
- `RebuildPeriodBalancesJob` দুইবার চালালে একই ফল (idempotent)।
- Job company context ছাড়া চলে না।

**Deliverable**
Migration ×2, cache service, listener, job।

**Sub-tasks**
migration, `rebuild`, listener, ব্যবহার, job, রাতের sweep, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.10 সবুজ (`CacheStalenessTest`-এর ভিত্তি)।
- Post-এর transaction-এ `acc_periods` row বদলানো হয় না (প্রমাণিত)।
- Expand migration `down()` নিরাপদ।

**Architecture constraints**
- Cache কখনো উৎস নয়। `stale` row কখনো report-এ ব্যবহার হয় না।
- `balance_version` বাড়ানো শুধু commit-এর পরে।
- Lock-ক্রম (Pack ৯.১) ভাঙা যাবে না: rebuild কোনো ledger row lock করে না।

Owner
Dev A
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-03 — General Ledger report

Project
ERPFlow
Module
Accounting Module
Type
Feature (api + ui)
Title
General Ledger report — Backend ও Frontend
Branch Name
`feature/accounting-report-general-ledger`
View Order
40
Description
**Summary**
একটা account-এর সব লেনদেন চলতি balance সহ দেখতে পারবে, আর যেকোনো সংখ্যায় ক্লিক করলে মূল journal পর্যন্ত নামতে পারবে।

**Task serial**
`ACC-P3-03 — General Ledger report` · Feature

**Depends on**
- `ACC-P3-01`
- `ACC-P1-02` (permission slug, middleware)

**Relation: আগের task**
- `P3-01`: `balancesAsOf` (opening), `accountRows` (পৃষ্ঠার row), `movements`/aggregate (মোট), snapshot wrapper, চিহ্ন নিয়ম।
- `P1-02`: `accounting.report.view` slug ও route middleware।
- `P1-06`/`P1-07`: account গাছ (group account ও descendant), UI-র account picker।
- `P2-02`, `P2-08`, `P2-04`: drill-down-এর গন্তব্য: journal-এর পাতা (`P2-02`), তার approval ইতিহাস (`P2-08`), status history (`P2-04`)।
- `P1-12`: `ACC_COMPANY_FORBIDDEN`, audit।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-13`: RC2 প্রতিটা account-এর GL closing = Trial Balance closing মেলায়। তাই GL service-এর ফল আলাদা, পুনঃব্যবহারযোগ্য class-এ থাকতে হবে (controller-এ লেখা হিসাব RC2 ডাকতে পারবে না)।
- `P3-04` (TB) এবং `P3-05`, `P3-06`: প্রতিটা account-এ click → এই GL-এর পাতা।
- `P3-10`: GL-এর Excel ও PDF export। UI-র বাটন `P3-10` তে সংযোগ হবে। `report` নাম `general-ledger`।
- `P3-14`: GL, ১ লক্ষ row-এর account, এক পৃষ্ঠা: লক্ষ্য এক সেকেন্ডের কাছাকাছি।
- `P3-15`: accountant এই report নিজের হিসাবের সাথে মেলায়।
- **খোলা প্রশ্ন OP-4:** Response-এর `reconciliation` (RC2 ফল) পাশে দেখানোর কথা, কিন্তু RC2 check class-টা `P3-13`-এ (ক্রম ২৮), আর TB (RC2-র অপর অংশ) `P3-04` একই ক্রমে (২৪)। Pack স্পষ্ট করেনি `P3-03` রিলিজে `reconciliation` field কী থাকবে। ধরা হলো: field ও UI-র লাল ব্যানার এখন তৈরি থাকবে, ফল `P3-13` যুক্ত হলে (inline, সংরক্ষণ ছাড়া) ভরবে; তার আগে `null`। Tech lead নিশ্চিত করবে।

**Related tables / schema**
কোনো নতুন table নেই। পড়া: `acc_journals`, `acc_journal_lines`, `acc_accounts` (`P3-01` ছাড়া সরাসরি নয়)।

**Business rules (R3.1 থেকে R3.4, RC2)**
- Ledger-এর অবস্থা `posted` ও `reversed` (সাধারণ নিয়ম)।
- `closing = opening + period_debit − period_credit` (debit-প্রধান account হলে), নইলে উল্টো (`opening + period_credit − period_debit`)।
- **RC2:** ওই account-এর closing Trial Balance-এর closing-এর সাথে মেলে। না মিললে UI-তে লাল সতর্কতা (`RPT_RECON_FAILED` তথ্য হিসেবে, error নয়, report তবু দেখায়)।
- Group account বেছে নিলে সব descendant leaf-এর row একসাথে, একটা বাড়তি "account" কলাম সহ। Opening আর closing হলো group-এর যোগফল।
- চলতি balance পৃষ্ঠা বদলালেও ধারাবাহিক থাকতে হবে।

**DB flow**
1. Snapshot transaction (`P3-01`)।
2. `opening = balancesAsOf(from - 1 day)` ওই account-এর জন্য।
3. পৃষ্ঠার row: `ORDER BY j.journal_date, j.id, l.line_no`, keyset cursor `(journal_date, journal_id, line_no)`।
4. প্রতিটা পৃষ্ঠার শুরুর চলতি balance = `opening` + আগের সব row-এর (cursor পর্যন্ত) নিট যোগফল, একটা অতিরিক্ত aggregate query দিয়ে।
5. পৃষ্ঠার ভেতরে চলতি balance window function: `SUM(delta) OVER (ORDER BY ... ROWS UNBOUNDED PRECEDING) + :start`।
6. মোট `period_debit`, `period_credit`, `closing` আলাদা একটা aggregate দিয়ে (পৃষ্ঠা-নিরপেক্ষ)।
7. Audit `accounting.report.run`।

**Task scope**
1. Opening হিসাব।
2. পৃষ্ঠার query (keyset cursor)।
3. চলতি balance (পৃষ্ঠার শুরুর মান + window function)।
4. মোট (`period_debit`, `period_credit`, `closing`)।
5. Group account: descendant-এর row ও যোগফল, "account" কলাম।
6. Drill-down লিংক।
7. API ও UI।
8. Test।

**Out of scope**
- Excel/PDF তৈরি (`P3-10`)।
- RC2 check class (`P3-13`)।
- Dimension filter (Phase 6)।

**Contract**
`GET /accounting/reports/general-ledger?account_id=&from=&to=&cursor=&limit=&cost_center_id=`

Response:
- `opening` (`from`-এর আগের দিন পর্যন্ত balance)
- `rows[]`: তারিখ, voucher নম্বর, journal id, line নম্বর, narration, reference, Debit, Credit, চলতি balance
- `period_debit`, `period_credit`, `closing`
- `next_cursor`
- `reconciliation` (RC2 ফল)

UI:
- Account বাছাই (অনুসন্ধান সহ, group সহ), তারিখ পরিসর, cost center filter।
- তালিকা, চলতি balance কলাম, শেষে মোট ও closing।
- RC2 না মিললে লাল সতর্কতা।
- Excel ও PDF বাটন (`P3-10`)।
- প্রতিটা row-এর voucher নম্বরে click → journal-এর পাতা (`P2-02`) ও তার approval ইতিহাস (`P2-08`) ও status history (`P2-04`)।

**Permission**
`accounting.report.view`

**Error**
`ACC_COMPANY_FORBIDDEN` (অন্য company-র account), `ACC_FORBIDDEN`; `RPT_RECON_FAILED` শুধু তথ্য হিসেবে।

**Audit**
`accounting.report.run`

**Acceptance**
AC3.1, AC3.5, AC3.6

**Test**
- নমুনা account-এ হাতে হিসাব করা মান।
- Group account।
- পৃষ্ঠা পরিবর্তনে চলতি balance ঠিক।
- `reversed` journal ধরা।
- Drill-down লিংক।
- অন্য company-র account (`ACC_COMPANY_FORBIDDEN`)।

**Deliverable**
API, UI

**Sub-tasks**
opening, পৃষ্ঠার query, চলতি balance, মোট, group, drill-down, API, UI, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.1 (GL অংশ), AC3.5, AC3.6 সবুজ।
- পৃষ্ঠার সীমানায় চলতি balance না ভাঙার test আছে।
- GL service আলাদা class, যাতে `P3-13` ডাকতে পারে।

**Architecture constraints**
- সব হিসাব `LedgerQueryService` (`P3-01`) দিয়ে, নিজের SQL নয়।
- Offset pagination নয়, keyset cursor।
- Float নয়, সব অঙ্ক string।

Owner
Dev C
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-04 — Trial Balance

Project
ERPFlow
Module
Accounting Module
Type
Feature (api + ui)
Title
Trial Balance — Backend ও Frontend
Branch Name
`feature/accounting-report-trial-balance`
View Order
41
Description
**Summary**
সব account-এর opening, period-এর নড়াচড়া আর closing এক পাতায়; মোট Debit আর Credit মিলছে কি না একনজরে বোঝা যাবে (Trial Balance)।

**Task serial**
`ACC-P3-04 — Trial Balance` · Feature

**Depends on**
- `ACC-P3-01`
- `ACC-P3-02`

**Relation: আগের task**
- `P3-01`: `balancesAsOf`, `movements`, snapshot wrapper, চিহ্ন নিয়ম।
- `P3-02`: cache (শুরুর balance দ্রুত আনতে `balancesAsOf`-এর ভেতরে স্বচ্ছভাবে)। `stale` হলে সরাসরি line থেকে, ফল একই।
- `P1-06`: CoA-র গাছ (group/leaf, `is_active`), group roll-up এই গাছ ধরে।
- `P3-03` (একই ক্রম ২৪, সমান্তরাল): প্রতিটা account-এ click → GL। GL-এর URL কাঠামো সেখান থেকে।
- `P1-03`: `report_show_zero_balance` setting (default `false`)।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-13`: RC1 (মোট closing Debit = মোট closing Credit), RC2 (GL closing = TB closing), RC5 (P&L নিট লাভ = TB-র Revenue ও Expense পার্থক্য)। তাই TB service পুনঃব্যবহারযোগ্য class।
- `P3-10`: `trial-balance` export।
- `P3-11`: period বন্ধের `tb_balanced` যাচাই period-শেষ তারিখে এই TB-র RC1-এর ওপর।
- `P3-14` ও `P3-15` (accountant হাতে মেলাবে)।
- `P5-02` (Cutover মহড়া): ধাপ ৫-এ পুরোনো system-এর সাথে account ধরে TB মেলানো এই report দিয়ে।
- **খোলা প্রশ্ন OP-5:** RC1 এই report-এর ভেতরে লাল ব্যানার হিসেবে আসে, আবার `P3-13`-এ আলাদা check class। ধরা হলো: `balanced` flag ও পার্থক্য এই task নিজেই হিসাব করে (সরল যোগফল), `P3-13` সেটা স্বাধীনভাবে আবার চালায় ও সংরক্ষণ করে।

**Related tables / schema**
কোনো নতুন table নেই। `P3-01` ও `acc_accounts` গাছ।

**Business rules (R3.1 থেকে R3.7, RC1)**
- প্রতিটা account-এ: `opening_debit`, `opening_credit`, `period_debit`, `period_credit`, `closing_debit`, `closing_credit`।
- **Presentation:** Closing balance Debit-কলামে (নিট Debit হলে) বা Credit-কলামে (নিট Credit হলে), **দুটোতে নয়**।
- Group account-এ descendant-এর যোগফল।
- `report_show_zero_balance` false হলে শূন্য balance আর শূন্য নড়াচড়ার account বাদ।
- Inactive account-এর balance থাকলে দেখায় (R3.7)।
- `as_of` মোডে opening শূন্য, closing সেই তারিখ পর্যন্ত জমা।
- Closing = opening + movement।
- **RC1:** মোট closing Debit = মোট closing Credit। না মিললে লাল ব্যানার, পার্থক্য সহ, report তবু দেখায় (R3.9, আটকায় না)।

**DB flow**
1. Snapshot।
2. `balancesAsOf(from - 1)` (cache সহ)।
3. `movements(from, to)`।
4. Leaf account ধরে গুনে CoA-র গাছ বেয়ে group-এ যোগ (PHP-তে, গাছ একবার পড়া)।
5. Closing = opening + movement।
6. Net balance চিহ্ন অনুযায়ী Debit বা Credit কলামে (একটাতেই)।
7. মোট সারি আর `balanced` flag।
8. Audit `accounting.report.run`।

**Task scope**
1. Movement ও opening সংগ্রহ।
2. গাছের roll-up।
3. Debit বা Credit কলাম নির্ধারণ।
4. RC1 (`balanced`, পার্থক্য)।
5. `report_show_zero_balance` filter।
6. `from/to` ও `as_of` দুই মোড।
7. API ও UI।
8. Test।

**Out of scope**
- RC1 সংরক্ষণ ও রাতের চালানো (`P3-13`)।
- Export (`P3-10`)।
- Dimension (Phase 6)।

**Contract**
`GET /accounting/reports/trial-balance?from=&to=` বা `?as_of=`

Response: প্রতিটা account-এ `opening_debit`, `opening_credit`, `period_debit`, `period_credit`, `closing_debit`, `closing_credit`। Group account-এ descendant-এর যোগফল। শেষে মোট ও `balanced` flag (RC1)।

UI:
- তারিখ বা পরিসর বাছাই।
- গাছের মতো সাজানো তালিকা (expand ও collapse)।
- শূন্য দেখানোর toggle।
- মোট সারি ও `balanced` চিহ্ন।
- প্রতিটা account-এ click → GL (`P3-03`)।

**Permission**
`accounting.report.view`

**Error**
`ACC_FORBIDDEN`; `RPT_RECON_FAILED` শুধু তথ্য (RC1 না মিললে, error নয়)।

**Audit**
`accounting.report.run`

**Acceptance**
AC3.1, AC3.5, AC3.6

**Test**
- নমুনা data-য় হাতে হিসাব।
- Group roll-up।
- উল্টো balance-এর account (যেমন Asset-এ Credit balance)।
- শূন্য toggle।
- `as_of`।
- RC1 ব্যর্থ করানো (ইচ্ছা করে ভাঙা data)।
- Inactive account-এর balance দেখায়।

**Deliverable**
API, UI

**Sub-tasks**
movement ও opening, গাছের roll-up, Debit বা Credit কলাম, RC1, API, UI, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.1, AC3.5, AC3.6 সবুজ।
- TB service আলাদা class (`P3-13` ও `P3-11` ডাকবে)।
- Float নেই।

**Architecture constraints**
- সব হিসাব `LedgerQueryService` দিয়ে।
- Leaf থেকে group-এ যোগ, group-এর নিজের line নেই (`is_group = true` account-এ line বসানো যায় না)।
- Closing একই account-এ Debit ও Credit দুই কলামে দেখানো যাবে না।

Owner
Dev C
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-05 — Profit & Loss

Project
ERPFlow
Module
Accounting Module
Type
Feature (api + ui)
Title
Profit & Loss — Backend ও Frontend
Branch Name
`feature/accounting-report-profit-loss`
View Order
42
Description
**Summary**
বেছে নেওয়া সময়ের আয়, খরচ আর নিট লাভ/ক্ষতি দেখা যাবে; চাইলে আগের period বা গত বছরের সাথে তুলনাও করা যাবে।

**Task serial**
`ACC-P3-05 — Profit & Loss` · Feature

**Depends on**
- `ACC-P3-01`
- OD-1 (হিসাবের ভিত্তি `accounting_basis`: এই card Accrual ধরে লেখা)
- OD-16 (cash basis-এর সিদ্ধান্ত: নিচে দেখুন)

**Relation: আগের task**
- `P3-01`: `movements(from, to)` আর `netProfit`। P&L শুধু `type IN ('revenue','expense')` account-এর movement চায়।
- `P1-03`: `accounting_basis` (`accrual` বা `cash`, lock হয়), `report_comparison_enabled` (default `true`)।
- `P1-06`: CoA-র group গাছ (উপমোটের জন্য)।
- `P1-11`/`P2-14`: opening journal-এ Revenue বা Expense account থাকলে সাধারণভাবে ধরা হয় (নিচে OD-23)।
- `P1-12`: error enum। **নতুন code `RPT_BASIS_UNSUPPORTED` (422) এই task enum-এ যোগ করে** (Pack ১০.৩-এর নতুন code তালিকা)।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-06` (Balance Sheet) এই task-এর `netProfit` ও P&L logic ব্যবহার করে Equity-র দুটো উপাদান বানায়। তাই `P3-06` শুরুর আগে এটা শেষ।
- `P3-13` RC5: P&L-এর নিট লাভ = TB-র Revenue ও Expense পার্থক্য।
- `P3-08` (Indirect Cash Flow): Operating অংশের শুরু এই P&L-এর সময়ের নিট লাভ থেকে।
- `P3-10`: `profit-loss` export।
- `P3-15`: accountant নমুনা company-র P&L হাতে মেলায়, আর OD-16 (ক) হলে নতুন card `P3-05b` (cash basis) লাগবে, যা এই Pack-এ নেই।
- **খোলা প্রশ্ন OD-23:** opening balance-এ Revenue বা Expense account থাকতে পারবে কি, আর P&L-এ opening journal-এর line ধরা হবে কি। বিকল্প: (ক) যেমন আছে, কোনো বিশেষ আচরণ নেই (এই Pack-এর ধরে নেওয়া, এই card এভাবেই), (খ) opening-এ শুধু Asset/Liability/Equity (`P1-11`-এর validator বাড়ে), (গ) opening-এর Revenue ও Expense line P&L থেকে বাদ। (ক)-তে বছরের মাঝে cutover হলে চালু-মাসের P&L ফুলে যেতে পারে। সিদ্ধান্ত `P3-15`-এর আগে লিখিত হতে হবে (Accountant)।
- **খোলা প্রশ্ন OP-6:** OD-1 ও OD-16-এর বিকল্পগুলোর বিস্তারিত Pack-এ নেই (Blueprint-এর OD register-এ)। এই card-এ ধরা হয়েছে (খ): cash basis পরের ধাপ।

**Related tables / schema**
কোনো নতুন table নেই।

**Business rules (R3.1, R3.2, R3.4)**
- শুধু `revenue` ও `expense` type।
- Revenue = Credit − Debit। Expense = Debit − Credit।
- `net_profit = মোট আয় − মোট খরচ`।
- Group অনুযায়ী উপমোট (গাছ ধরে)।
- **Basis:** `accounting_basis = 'accrual'` হলে `journal_date`-এর ওপর (এই card)। `cash` হলে এখন `RPT_BASIS_UNSUPPORTED` (422), কারণ cash basis আলাদা logic, OD-16-এর (খ) অনুযায়ী পরের ধাপ। OD-16 (ক) হলে নতুন card `P3-05b` লাগবে।
- **Comparison:** `report_comparison_enabled` চালু থাকলে `compare=previous_period` (একই দৈর্ঘ্যের আগের সময়) বা `previous_year`।
- Reversal-সহ নিট প্রভাব শূন্য (কারণ `reversed` ও reversal দুটোই ধরা)।
- Opening journal-এ Revenue বা Expense থাকলে সাধারণভাবে ধরা হয়, বিশেষ আচরণ নেই (OD-23 সম্ভাব্য সমস্যা)।

**DB flow**
1. Snapshot।
2. `accounting_basis` পড়া; `cash` হলে `RPT_BASIS_UNSUPPORTED`।
3. `movements(from, to)` শুধু `type IN ('revenue','expense')` account-এর।
4. Revenue = Credit − Debit, Expense = Debit − Credit; গাছ ধরে group উপমোট।
5. `compare` চাইলে (এবং setting চালু) একই হিসাব আগের সময়ের জন্য, পরিবর্তন (পার্থক্য) সহ।
6. Audit `accounting.report.run`।

**Task scope**
1. Movement সংগ্রহ।
2. Group উপমোট।
3. `compare` (`previous_period`, `previous_year`)।
4. Basis পরীক্ষা ও নতুন error code।
5. API ও UI।
6. Test।

**Out of scope**
- Cash basis P&L (OD-16 (খ) অনুযায়ী পরের ধাপ; (ক) হলে `P3-05b`)।
- Opening journal-এর বিশেষ আচরণ (OD-23 (খ)/(গ) এই card-এ নেই)।
- Export (`P3-10`)।

**Contract**
`GET /accounting/reports/profit-loss?from=&to=&compare=`

Response:
- Revenue ও Expense আলাদা অংশে, প্রতিটায় group অনুযায়ী উপমোট
- মোট আয়, মোট খরচ
- `net_profit` (= মোট আয় − মোট খরচ)
- `compare` চাইলে আগের সময়ের পাশাপাশি আর পরিবর্তন

UI:
- সময় বাছাই, Revenue ও Expense অংশ, উপমোট।
- নিট লাভ হাইলাইট (ক্ষতি হলে ঋণাত্মক ও লাল)।
- তুলনা toggle।
- প্রতিটা account-এ click → GL (`P3-03`)।

**Permission**
`accounting.report.view`

**Error**
`RPT_BASIS_UNSUPPORTED` (422, নতুন), `ACC_FORBIDDEN`, `ACC_COMPANY_FORBIDDEN`

**Audit**
`accounting.report.run`

**Acceptance**
AC3.2, AC3.5, AC3.6

**Test**
- নমুনা data-য় হাতে হিসাব।
- Reversal-সহ যোগফল শূন্য প্রভাব।
- তুলনা (`previous_period`, `previous_year`)।
- `cash` basis-এ `RPT_BASIS_UNSUPPORTED`।
- ক্ষতি (ঋণাত্মক `net_profit`)।
- `report_comparison_enabled` বন্ধ থাকলে `compare` কাজ করে না।

**Deliverable**
API, UI

**Sub-tasks**
movement, group উপমোট, compare, basis check, API, UI, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.2 (P&L অংশ), AC3.5, AC3.6 সবুজ।
- `RPT_BASIS_UNSUPPORTED` `AccountingError` enum-এ (code ও 422 status সহ)।
- P&L service পুনঃব্যবহারযোগ্য (`P3-06`, `P3-13` ডাকবে)।

**Architecture constraints**
- Accrual ধরে লেখা; `cash` basis চুপচাপ accrual হিসেবে চালানো যাবে না, স্পষ্ট error।
- সব হিসাব `LedgerQueryService` দিয়ে।

Owner
Dev C
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-06 — Balance Sheet

Project
ERPFlow
Module
Accounting Module
Type
Feature (api + ui)
Title
Balance Sheet — Backend ও Frontend
Branch Name
`feature/accounting-report-balance-sheet`
View Order
43
Description
**Summary**
নির্দিষ্ট তারিখে Asset, Liability, Equity দেখে Asset = Liability + Equity সমীকরণ মিলছে কি না বোঝা যাবে।

**Task serial**
`ACC-P3-06 — Balance Sheet` · Feature

**Depends on**
- `ACC-P3-01`
- `ACC-P3-05`
- OD-1 (accrual)
- OD-20 (বছর-শেষ closing: ধরা হয়েছে (ক) গতিশীল হিসাব, বছর-শেষে আলাদা closing journal ছাড়া)

**Relation: আগের task**
- `P3-01`: `balancesAsOf` (asset/liability/equity) আর `netProfit(from, to)`।
- `P3-05`: P&L-এর নিট লাভ logic (Equity-র (২) ও (৩) উপাদান)।
- `P1-08`: `acc_periods`, `PeriodResolver::forDate` ও `fiscal_year`: `as_of` কোন আর্থিক বছরে পড়ে তা বের করতে।
- `P1-03`: `fiscal_year_start` (আর্থিক বছরের শুরু, যেমন জুলাই-শুরু) ও `base_currency`।
- `P1-06`: CoA গাছ।
- `P2-14`: Opening journal সাধারণ Post হয়ে Equity/Asset/Liability-র শুরুর balance বসায়।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-08` (Cash Flow Statement): শেষের নগদ Balance Sheet-এর নগদ-সমতুল্য account-এর যোগফলের সাথে মেলাতে হয় (RC4); Indirect পদ্ধতি `balancesAsOf` ও P&L-এর ওপর।
- `P3-13`: RC3 (Asset = Liability + Equity) এবং RC4-এর অংশ।
- `P3-10`: `balance-sheet` export।
- `P3-15`: accountant একাধিক আর্থিক বছরের Equity হাতে মেলাবে।
- `P5-02` (Cutover): ধাপ ৭, Balance Sheet মেলানো।
- **খোলা প্রশ্ন OD-20:** বছর-শেষের closing journal না থাকায় আগের বছরের লাভ প্রতিবার হিসাব হয়। Retained Earnings account-এ (যেটা system account `retained_earnings`) হাতে করা journal থাকলে তা Equity-র (১)-এ আসে আর (২)-এর সাথে যোগ হয়। এটা double count কি না তা accountant দেখবে। সিদ্ধান্ত লিখিত হতে হবে `P3-15`-এর আগে।
- **খোলা প্রশ্ন OP-7:** `netProfit(inception, FY শুরু − 1)`-এ `inception` Pack-এ সংজ্ঞায়িত নয়। ধরা হলো: নিচের সীমা ছাড়া (company-র প্রথম journal থেকে, বা যেকোনো `from` যা সব ledger ধরে)। `LedgerQueryService::netProfit`-এর signature-এ এটা সমর্থন করতে হবে।
- **খোলা প্রশ্ন OP-8:** `as_of` কোনো period-এ না পড়লে আচরণ Pack-এ নেই। `PeriodResolver::forDate` (`P1-08`) `JRN_DATE_INVALID` দেয়; সেটাই ধরা হলো (অনুমান)।

**Related tables / schema**
কোনো নতুন table নেই।

**Business rules (R3.6, RC3)**
Equity-র গঠন:
1. Equity type-এর account-এর balance।
2. **আগের বছরগুলোর জমানো নিট লাভ:** এই আর্থিক বছর শুরুর আগের দিন পর্যন্ত সব Revenue ও Expense-এর পার্থক্য: `netProfit(inception, FY শুরু − 1)`।
3. **চলতি বছরের নিট লাভ:** `netProfit(FY শুরু, as_of)`।

আর্থিক বছরের শুরু = `as_of` যে period-এ পড়ে সেই period-এর `fiscal_year`-এর প্রথম period-এর `start_date`।

- Equity = (১) + (২) + (৩)।
- Asset-এ Credit balance বা Liability-তে Debit balance ঋণাত্মক অঙ্কে দেখানো হয়, অন্য অংশে সরানো হয় না।
- **RC3:** মোট Asset = মোট Liability + মোট Equity। না মিললে লাল ব্যানার, পার্থক্য সহ (report আটকায় না)।
- বছর-শেষের closing journal না থাকায় আগের বছরের লাভ প্রতিবার হিসাব হয় (OD-20 (ক))।

**DB flow**
1. Snapshot।
2. `balancesAsOf(as_of)` শুধু `asset`, `liability`, `equity` type।
3. `netProfit` দুটো: আগের বছরগুলোর, আর চলতি বছরের।
4. Equity = (১) + (২) + (৩)।
5. RC3: মোট Asset = মোট Liability + মোট Equity।
6. Audit `accounting.report.run`।

**Task scope**
1. Asset-Liability-Equity হিসাব।
2. আগের বছরের লাভ (`netProfit(inception, FY শুরু − 1)`)।
3. চলতি লাভ (`netProfit(FY শুরু, as_of)`)।
4. আর্থিক বছরের শুরু বের করা (`PeriodResolver` ও `fiscal_year`)।
5. RC3 ও `balanced` flag।
6. তুলনা (আগের বছরের শেষ)।
7. API ও UI।
8. Test।

**Out of scope**
- বছর-শেষের closing journal (OD-20 (ক) অনুযায়ী নেই)।
- Export (`P3-10`)।
- RC3 সংরক্ষণ ও রাতের চালানো (`P3-13`)।

**Contract**
`GET /accounting/reports/balance-sheet?as_of=&compare=`

Response: Asset, Liability, Equity অংশ, প্রতিটায় group উপমোট; মোট Asset, মোট Liability, মোট Equity; `balanced` flag (RC3)।

UI:
- তারিখ বাছাই।
- তিন অংশ।
- Equity-তে তিন উপাদান আলাদা সারিতে।
- `balanced` চিহ্ন।
- তুলনা (আগের বছরের শেষ)।
- প্রতিটা account-এ click → GL।

**Permission**
`accounting.report.view`

**Error**
`ACC_FORBIDDEN`, `ACC_COMPANY_FORBIDDEN`; `RPT_RECON_FAILED` শুধু তথ্য (RC3)।

**Audit**
`accounting.report.run`

**Acceptance**
AC3.2, AC3.3, AC3.5, AC3.6

**Test**
- নমুনা data-য় হাতে হিসাব।
- একাধিক আর্থিক বছর (আগের বছরের লাভ Equity-তে)।
- আর্থিক বছর জুলাই-শুরু।
- RC3 সবসময় মেলে সুষম data-য়।
- ইচ্ছা করে ভাঙা data-য় লাল।
- Asset-এ Credit balance ঋণাত্মক অঙ্কে।

**Deliverable**
API, UI

**Sub-tasks**
Asset-Liability-Equity হিসাব, আগের বছরের লাভ, চলতি লাভ, RC3, API, UI, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.2, AC3.3, AC3.5, AC3.6 সবুজ।
- `BalanceSheetEquationTest` (RC3) পাশ।
- BS service পুনঃব্যবহারযোগ্য (`P3-08`, `P3-13` ডাকবে)।

**Architecture constraints**
- সব হিসাব `LedgerQueryService` দিয়ে।
- ঋণাত্মক balance অন্য অংশে সরানো যাবে না।
- Equity-র তিন উপাদান আলাদা রাখা, একত্র করে লুকানো নয়।

Owner
Dev C
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-07 — Cash Flow mapping UI ও যাচাই

Project
ERPFlow
Module
Accounting Module
Type
Feature (service + api + ui)
Title
Cash Flow mapping UI ও যাচাই — Backend ও Frontend
Branch Name
`feature/accounting-cashflow-mapping`
View Order
44
Description
**Summary**
Admin নিজেই ঠিক করবে কোন account নগদ-সমতুল্য, বাকিগুলো Operating / Investing / Financing-এর কোন ভাগে — Cash Flow কোনো অনুমানে চলবে না।

**Task serial**
`ACC-P3-07 — Cash Flow mapping UI ও যাচাই` · Feature

**Depends on**
- `ACC-P1-07` (CoA API ও UI)
- `ACC-P1-04` (Settings UI ও দ্বিতীয় অনুমোদন)
- OD-2 (কোন account নগদ-সমতুল্য, আর Cash Flow-এর পদ্ধতি)

**Relation: আগের task**
- `P1-06`: `acc_accounts.cashflow_class` (`operating`/`investing`/`financing`, nullable, `ck_acc_accounts_cf`) আর `is_cash_equivalent` (BOOLEAN, default false) column। এই task নতুন column বানায় না, বিদ্যমানগুলো ভরার জায়গা দেয়।
- `P1-07`: account তালিকা/গাছ UI আর `PUT /accounts/{id}` (`version` সহ) API; এই task-এর UI সেই গাছের ওপর।
- `P1-03`: `SettingsService` ও `cash_equivalent_accounts` setting (account id-র তালিকা, default খালি, lock নেই কিন্তু audit সহ)। `sensitive_settings` default তালিকায় `cash_equivalent_accounts` আছে।
- `P1-04`: সংবেদনশীল setting বদলাতে দ্বিতীয় অনুমোদন (change request): `POST /accounting/settings/{key}/change-requests`, অনুমোদনকারী ভিন্ন ব্যক্তি, `SET_STALE_REQUEST`।
- `P1-12`: error, audit।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-08` (Cash Flow Statement): `CashflowMappingService::missing` খালি না হলে Cash Flow চলে না (`RPT_MAPPING_INCOMPLETE`)। এই task শেষ না হলে `P3-08` শুরুই হয় না।
- `P3-13` RC4: CF-এর শেষের নগদ BS-এর নগদ-সমতুল্য account-এর যোগফলের সাথে।
- `P3-14`: AC3.11 (`CashflowMappingTest`)।
- `P3-15`: accountant এই mapping সই করে।
- `P1-05`-এর ধাপ `S11_cashflow_mapping` ("নগদ account ও `cashflow_class` ঠিক", Cash Flow চালু হলে বাধ্যতামূলক): একই শর্ত।
- **খোলা প্রশ্ন OP-9:** `S11`-এর checker `CashflowMappingService::missing` ডাকবে কি না Pack-এ স্পষ্ট লেখা নেই (শর্ত একই বলা আছে)। ধরা হলো ডাকবে; `P1-05`-এর checker-কে এই service-এ সংযুক্ত করা এই task-এর scope-এ ছোট একটা কাজ হিসেবে ধরা যায়, tech lead নিশ্চিত করবে।
- **খোলা প্রশ্ন OP-10:** Pack এই card-এ শুধু service method ও UI-র কথা বলে, API route-এর নাম/আকার লেখে না (অন্য card-এর মতো)। Route নাম implementation-এ ঠিক হবে, PR-এ লিখতে হবে।
- **খোলা প্রশ্ন OD-2:** নগদ-সমতুল্য কোন account, আর পদ্ধতি (`direct`/`indirect`/দুটোই (গ))। `cashflow_method` setting এখনো বাধ্যতামূলক ("Cash Flow চালু করতে")। এই task সরাসরি পদ্ধতির ওপর নির্ভর করে না, কিন্তু OD বন্ধ না হলে `Blocked`।

**Related tables / schema**
কোনো নতুন table নেই।
- `acc_accounts.cashflow_class`, `acc_accounts.is_cash_equivalent` (`P1-06`)।
- **একটাই উৎস:** `cash_equivalent_accounts` setting (সংবেদনশীল, দ্বিতীয় অনুমোদন লাগে)। `is_cash_equivalent` শুধু তার **প্রতিফলন**, যা `SettingsService` সেই একই transaction-এ আপডেট করে। কেউ সরাসরি এই flag বদলাতে পারে না।

**Business rules**
`CashflowMappingService::missing` যাচাই:
1. নগদ-সমতুল্য account-গুলো `asset` type আর leaf হতে হবে।
2. নগদ নয় এমন প্রতিটা `asset`, `liability`, `equity` account (leaf, active অথবা balance আছে) এর `cashflow_class` থাকতে হবে।
3. `revenue` ও `expense` account-এর `cashflow_class` ফাঁকা থাকলে ধরা হয় `operating`; আলাদা মান দিলে (যেমন সম্পদ বিক্রির লাভ `investing`) সেটাই।

অসম্পূর্ণ হলে ফেরত দেয় কোন কোন account বাকি।

- `cashflow_class` বদল: কোনো lock নেই, Posted data থাকলেও বদলানো যায় (এতে report-এর ফল বদলায়, তাই UI আগে সতর্ক করে আর audit-এ আগে ও পরে)।
- Group account-এ ভাগ বেছে নিলে সব descendant-এ প্রযোজ্য।
- নগদ-সমতুল্য চিহ্ন বদলাতে গেলে setting-এর দ্বিতীয় অনুমোদনের পথে যায় (`P1-04`), সরাসরি লেখা যায় না।
- `is_cash_equivalent`-এর একমাত্র লেখক `SettingsService`। `P1-06`-এর `AccountService::update` এবং `P1-07`-এর `PUT /accounts/{id}` দিয়ে এই flag বদলানো আটকাতে হবে।

**DB flow: `cashflow_class` বদল**
1. transaction।
2. account row `FOR UPDATE`।
3. `version` মেলানো (না মিললে `JRN_CONFLICT`)।
4. `UPDATE` (`version + 1`)।
5. Audit `accounting.cashflow.mapping_changed` (আগে ও পরে সহ)।
6. commit।

**DB flow: নগদ-সমতুল্য চিহ্ন বদল**
`cash_equivalent_accounts` setting বদলের অনুরোধ (`P1-04`) → অন্য ব্যক্তির অনুমোদন → `SettingsService` একই transaction-এ setting আর `is_cash_equivalent` আপডেট করে। Non-asset বা group account হলে `SET_INVALID`।

**Task scope**
1. `CashflowMappingService::missing`।
2. সুরক্ষা: `is_cash_equivalent`-এর একমাত্র লেখক `SettingsService`।
3. `cashflow_class` update (lock, version, audit)।
4. Group-এ প্রয়োগ (descendant)।
5. UI: সব account-এর তালিকা, দুই কলাম, "বাকি আছে: N টা account" গণনা ও ফিল্টার।
6. API।
7. Test।

**Out of scope**
- Cash Flow Statement নিজে (`P3-08`)।
- `cashflow_method` setting বদলানো (সাধারণ settings পাতা, `P1-04`)।
- Setup Checklist পাতা (`P1-05`)।

**Contract**
- Service: `CashflowMappingService::missing(companyId)` → বাকি account-এর তালিকা।
- API route-এর নাম Pack-এ নির্ধারিত নেই (OP-10)।
- `cash_equivalent_accounts` বদল `P1-04`-এর change-request endpoint দিয়ে।

UI:
- সব account-এর তালিকা, দুটো কলাম: "নগদ-সমতুল্য" (টিক, বদলাতে গেলে setting-এর দ্বিতীয় অনুমোদনের পথে যায়) আর "Cash Flow ভাগ" (ড্রপডাউন)।
- Group account-এ ভাগ বেছে নিলে সব descendant-এ প্রযোজ্য।
- উপরে "বাকি আছে: ১২টা account" গণনা আর ফিল্টার।
- ভাগ বদলের আগে সতর্কতা (report-এর ফল বদলাবে)।

**Permission**
`accounting.cashflow.manage` (ভাগ বদল)। নগদ-সমতুল্য চিহ্নের অনুরোধ ও অনুমোদন `accounting.settings.manage`/`accounting.settings.approve` (`P1-04`)।

**Error**
`RPT_MAPPING_INCOMPLETE` (422), `SET_INVALID` (422); পথে `JRN_CONFLICT`, `SET_STALE_REQUEST`, `JRN_SOD_VIOLATION` (`P1-04` থেকে)।

**Audit**
`accounting.cashflow.mapping_changed` (mapping বা নগদ চিহ্ন বদল); setting পথে `accounting.setting.change_requested/.approved/.rejected`, `accounting.setting.changed` (`P1-03`, `P1-04`)।

**Acceptance**
AC3.11

**Test**
- অসম্পূর্ণ তালিকা ঠিক ধরা।
- নগদ-সমতুল্য non-asset account-এ আপত্তি।
- দ্বিতীয় অনুমোদন ছাড়া নগদ চিহ্ন বদলানো যায় না।
- Group-এ প্রয়োগ।
- `is_cash_equivalent` সরাসরি account API দিয়ে বদলানো যায় না।
- Revenue/Expense-এর ফাঁকা `cashflow_class` Operating ধরা।
- দুজন একসাথে `cashflow_class` বদলালে `JRN_CONFLICT`।

**Deliverable**
Service, API, UI

**Sub-tasks**
`missing`, সুরক্ষা (flag-এর একমাত্র লেখক `SettingsService`), `cashflow_class` update, UI, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.11 সবুজ (`CashflowMappingTest`)।
- `is_cash_equivalent` ও `cash_equivalent_accounts` কখনো অমিল হতে পারে না (test প্রমাণ)।
- `missing` ফল `P3-08` সরাসরি ব্যবহার করতে পারে।

**Architecture constraints**
- `cash_equivalent_accounts` একটাই উৎস; `is_cash_equivalent` শুধু প্রতিফলন।
- কোনো account-এর ভাগ অনুমান করা যাবে না (R3.8 এর ভিত্তি)।
- Mapping বদল সবসময় audit-এ, আগে ও পরের মান সহ।

Owner
Dev C
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-09 — অন্য report (Account Summary, Opening Balance, Audit, Register-এর পূর্ণ রূপ)

Project
ERPFlow
Module
Accounting Module
Type
Feature (api + ui)
Title
অন্য report — Backend ও Frontend
Branch Name
`feature/accounting-report-others`
View Order
45
Description
**Summary**
মূল পাঁচটা report ছাড়াও Account Summary, Opening Balance, Audit Report আর Journal/Approval Register-এর পূর্ণ রূপ লাগবে — দৈনন্দিন কাজ আর নিরীক্ষায় ফাঁক থাকবে না।

**Task serial**
`ACC-P3-09 — অন্য report` · Feature

**Depends on**
- `ACC-P3-01`
- `ACC-P2-15` (Queue ও Register)

**Relation: আগের task**
- `P3-01`: Account Summary-র `movements` ও `balancesAsOf`।
- `P2-15`: Journal Register ও Approval Register (filter: অবস্থা, ধরন, তারিখ পরিসর, voucher নম্বর, reference, account, amount পরিসর, Maker, Approver; `id` ধরে cursor pagination)। এই task তাদের **export-সহ পূর্ণ রূপ** দেয়, নতুন করে লেখে না।
- `P1-09`/`P1-11`/`P2-14`: opening journal (`journal_type = 'opening'`) ও তার line, Draft/Submitted/Posted অবস্থা।
- `P1-12`: `activity_logs`-এ লেখা সব `accounting.*` event (`AccountingAudit` দিয়ে)।
- `P1-02`: `accounting.audit.view` permission (Phase 1 থেকে), `accounting.report.view`।
- `P2-05`/`P2-08`: `acc_approval_requests`, `acc_approval_actions` (Approval Register-এর উৎস)।
- `P0-02`: `activity_logs`-এর index বড় table-এ যথেষ্ট কি না, সেই নিশ্চিতকরণ।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-10`: পাঁচটা report-ই export-যোগ্য (`account-summary` ইত্যাদি)।
- `P3-13`: এই task অবরুদ্ধ করে (build sequence-এ)। সরাসরি RC নেই, কিন্তু সব report service একসাথে `P3-13`-এর আগে তৈরি।
- `P3-14`/`P3-15`: AC3.6, AC3.9।
- `P5-03` (monitoring): Audit Report-এর মতো `activity_logs` ব্যবহার করে (`post_failures`, `locked_post_attempts` alert)।
- `P5-02`: Opening Balance Report cutover মহড়ায় দরকার।
- **খোলা প্রশ্ন OP-11:** `P3-13`-এর `নির্ভর করে`-তে `P3-09` নেই (শুধু `P3-03` থেকে `P3-06`, `P3-08`) কিন্তু `P3-09`-এর `অবরুদ্ধ করে`-তে `P3-13` আছে। নিয়ম: dependency দুই দিকের ফারাক, build sequence (`P3-09` ক্রম ২৬, `P3-13` ক্রম ২৮) অনুসরণ করা হলো।
- **খোলা প্রশ্ন OP-12:** Account Summary-র `group` parameter-এর অর্থ (account group ধরে ভাগ করা?) Pack-এ বিস্তারিত নেই; ধরা হলো `account_group_id`-র মতো descendant ধরে যোগ।

**Related tables / schema**
কোনো নতুন table নেই। পড়া: `acc_journals`, `acc_journal_lines`, `acc_approval_requests`, `acc_approval_actions`, `activity_logs`।

| Report | Contract | কী দেখায় | Data কোথা থেকে |
| --- | --- | --- | --- |
| Account Summary | `GET /accounting/reports/account-summary?from=&to=&group=` | Account ধরে মোট Debit, Credit, শেষের balance | `movements` ও `balancesAsOf` (`P3-01`) |
| Opening Balance Report | `GET /accounting/reports/opening-balance` | Cutover journal-এর সব line, অবস্থা (Draft, Submitted, Posted), মোট ও পার্থক্য | `acc_journals` (`journal_type = 'opening'`) ও `acc_journal_lines`, **Draft-ও দেখায়** |
| Audit Report | `GET /accounting/reports/audit?actor=&event=&from=&to=&journal=` | Reversal, adjustment, approval, post, override, setting বদল, period বন্ধ, export | `activity_logs` (`event LIKE 'accounting.%'`, `company_id`) |
| Journal Register (পূর্ণ) | `P2-15`-এর filter-সহ, এখন export-সহ | সব journal | `acc_journals` |
| Approval Register (পূর্ণ) | `P2-15` + export | Pending, Approved, Rejected, Posted | `acc_approval_requests`, `acc_approval_actions` |

**Business rules**
- Opening Balance Report শুধু opening ধরনের journal দেখায় (Draft সহ)।
- Audit Report শুধু `accounting.audit.view` permission-ওয়ালাকে।
- সব report company-scoped (Audit Report-এ অন্য company-র event আসে না)।
- Audit Report-এ বড় table-এ `activity_logs`-এর index যথেষ্ট কি না `P0-02`-এ দেখা; নইলে index যোগ।
- Account Summary শুধু ledger-এর অবস্থা (`posted`, `reversed`) ধরে।

**DB flow**
- Account Summary: snapshot → `movements(from, to)` ও `balancesAsOf(to)` → account (বা `group`) ধরে Debit, Credit, শেষের balance।
- Opening Balance Report: `acc_journals WHERE company_id = ? AND journal_type = 'opening'` → line, অবস্থা, মোট Debit/Credit ও পার্থক্য।
- Audit Report: `activity_logs WHERE company_id = ? AND event LIKE 'accounting.%'` + filter (`actor`, `event`, `from`, `to`, `journal`)।
- Register: `P2-15`-এর query, শুধু export সংযোগ।
- প্রতিটায় audit `accounting.report.run`।

**Task scope**
1. Account Summary API ও UI।
2. Opening Balance Report API ও UI।
3. Audit Report API ও UI, `activity_logs` index যাচাই (দরকারে index migration)।
4. Journal Register ও Approval Register-এ export সংযোগ।
5. Test।

**Out of scope**
- Export ফাইল তৈরি (`P3-10`)।
- নতুন audit event বানানো।
- Dimension filter (Phase 6)।

**Contract**
- `GET /accounting/reports/account-summary?from=&to=&group=`
- `GET /accounting/reports/opening-balance`
- `GET /accounting/reports/audit?actor=&event=&from=&to=&journal=`
- Journal Register, Approval Register: `P2-15`-এর endpoint (`GET /accounting/journals?...`, `GET /accounting/approvals/register?...`) এখন export-সহ।

**Permission**
`accounting.report.view`; Audit Report-এ `accounting.audit.view`।

**Error**
`ACC_FORBIDDEN`, `ACC_COMPANY_FORBIDDEN`

**Audit**
`accounting.report.run`

**Acceptance**
AC3.6, AC3.9

**Test**
- প্রতিটা report নমুনা data-য় ঠিক।
- Draft opening-ও দেখায়।
- Audit Report-এ অন্য company-র event আসে না।
- `accounting.audit.view` ছাড়া Audit Report-এ `ACC_FORBIDDEN`।
- Account Summary `reversed` ধরে।

**Deliverable**
চারটা API, UI

**Sub-tasks**
প্রতিটা report আলাদা API ও UI, Audit Report-এর index যাচাই, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.6, AC3.9 (এই report-গুলোর অংশ) সবুজ।
- `activity_logs` index যাচাইয়ের ফল PR-এ লেখা (লাগলে migration)।

**Architecture constraints**
- Audit Report কখনো অন্য company-র event দেখাবে না।
- Account Summary `LedgerQueryService` ছাড়া নিজের SQL নয়।

Owner
Dev C
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-08 — Cash Flow Statement

Project
ERPFlow
Module
Accounting Module
Type
Feature (service + api + ui)
Title
Cash Flow Statement — Backend ও Frontend
Branch Name
`feature/accounting-report-cash-flow`
View Order
46
Description
**Summary**
নির্বাচিত সময়ে নগদ কীভাবে এলো-গেল Operating, Investing, Financing ভাগে দেখা যাবে; শুরু ও শেষের নগদ মিলছে কি নাও পরিষ্কার হবে।

**Task serial**
`ACC-P3-08 — Cash Flow Statement` · Feature

**Depends on**
- `ACC-P3-01`
- `ACC-P3-07`
- `ACC-P3-06`
- OD-1, OD-2 (দুটো বন্ধ না হলে এই card শুরু হয় না: acceptance-এ আটকাবে, stub চলবে না)

**Relation: আগের task**
- `P3-01`: `balancesAsOf` (নগদ account-এ শুরু ও শেষের নগদ), `movements`, snapshot।
- `P3-07`: `CashflowMappingService::missing` (খালি না হলে report চলে না) আর প্রতিটা account-এর `cashflow_class` ও নগদ-সমতুল্য চিহ্ন।
- `P3-06`: Balance Sheet-এর `balancesAsOf` ও নগদ-সমতুল্য যোগফল (RC4 এর অংশ)।
- `P3-05`: P&L-এর সময়ের নিট লাভ (Indirect পদ্ধতির Operating-এর শুরু), যদিও card-এ নির্ভরতা `P3-06` মারফত।
- `P1-03`: `cashflow_method` (`direct` বা `indirect`), `cash_equivalent_accounts`।
- `P1-12`: error `RPT_MAPPING_INCOMPLETE`।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-13`: RC4: CF-এর শেষের নগদ = Balance Sheet-এর নগদ-সমতুল্য account-এর যোগফল, আর তিন ভাগের মোট = নিট পরিবর্তন।
- `P3-10`: `cash-flow` export।
- `P3-14`: AC3.2, AC3.4 (`CashFlowReconciliationTest`), AC3.11।
- `P3-15`: accountant তাঁর হাতে করা Cash Flow-এর সাথে মেলান (নমুনা company)।
- **খোলা প্রশ্ন OD-2:** Deliverable "OD-2 যেটা চায় (গ) হলে দুটোই": কোন পদ্ধতি বা দুটোই, তা OD-2 বন্ধের সময় ঠিক হবে। `cashflow_method` setting-এ থাকবে; দুটো করার সিদ্ধান্ত হলে দুই পদ্ধতিই এই card-এর scope।
- **খোলা প্রশ্ন OD-1:** accounting basis; Cash Flow `accounting_basis` অনুযায়ী আচরণ বদলায় কি না Pack-এ বলা নেই, শুধু OD-1 বন্ধ থাকতে হবে।
- **খোলা প্রশ্ন OP-13:** `P3-08` build sequence-এ `P3-05` সরাসরি নির্ভরতা হিসেবে নেই, কিন্তু Indirect নিট লাভ `P3-05`-এর সময়ের নিট লাভ ধরে। `P3-06` মারফত (যার `P3-05` লাগে) পরোক্ষ নির্ভরতা।

**Related tables / schema**
কোনো নতুন table নেই।

**সংজ্ঞা:** নগদ = নগদ-সমতুল্য account-গুলোর balance-এর যোগফল। `নিট পরিবর্তন` = শেষের নগদ − শুরুর নগদ (`balancesAsOf(to)` ও `balancesAsOf(from − 1)` নগদ account-এ)।

**Direct পদ্ধতি (`cashflow_method = 'direct'`):**

| ধাপ | কাজ |
| --- | --- |
| 1 | সময়ের মধ্যে `status IN ('posted','reversed')` সব journal-এর মধ্যে সেগুলো, যেগুলোর অন্তত একটা line নগদ account-এ |
| 2 | প্রতিটা এমন journal-এ নগদ line-এর নিট (`Debit − Credit`) শূন্য হলে বাদ (যেমন ব্যাংক থেকে হাতে নগদে স্থানান্তর, দুটোই নগদ-সমতুল্য) |
| 3 | নইলে journal সুষম, তাই `Σ নগদ(Debit − Credit) = Σ অ-নগদ(Credit − Debit)`। প্রতিটা অ-নগদ line-এর `(Credit − Debit)` হলো ওই line-এর নগদে অবদান |
| 4 | সেই অবদান ওই line-এর account-এর `cashflow_class`-এ (revenue বা expense হলে ফাঁকা মানে Operating) যোগ |
| 5 | Presentation: প্রতিটা ভাগের ভেতরে account (বা group) ধরে লাইন, আগমন (ধনাত্মক) ও নির্গমন (ঋণাত্মক) আলাদা |

**Indirect পদ্ধতি (`cashflow_method = 'indirect'`):**

| অংশ | হিসাব |
| --- | --- |
| Operating | নিট লাভ (`P3-05`-এর সময়ের) + নগদ নয় এমন Operating-ভাগের `asset`-এর `−Δbalance` + Operating-ভাগের `liability` ও `equity`-র `+Δbalance` |
| Investing | Investing-ভাগের `asset`-এর `−Δbalance` + Investing-ভাগের `liability`, `equity`-র `+Δbalance` |
| Financing | Financing-ভাগের `liability`, `equity`-র `+Δbalance` + Financing-ভাগের `asset`-এর `−Δbalance` |

`Δbalance` = `balancesAsOf(to) − balancesAsOf(from − 1)` ওই account-এর। Depreciation-এর মতো নগদহীন খরচ আলাদা flag ছাড়াই ধরা পড়ে: জমা অবচয় (accumulated depreciation) account Operating ভাগে থাকলে তার balance কমা (Credit বাড়া) নিজেই নিট লাভে যোগ হয়ে ফেরে। তাই প্রতিটা অ-নগদ balance-sheet account-এর ভাগ বাধ্যতামূলক (`P3-07`)। **দুই পদ্ধতির মোট একই হবে।**

**Business rules (R3.8, R3.9)**
- কোনো account-এর ভাগ অনুমান করা হয় না। ভাগহীন account থাকলে `RPT_MAPPING_INCOMPLETE` (তালিকা সহ), report চলে না।
- তিন ভাগের মোট = নিট পরিবর্তন কি না (RC4-এর অংশ)। না মিললে লাল সতর্কতা আর পার্থক্য (report আটকায় না, R3.9)।
- পদ্ধতি `cashflow_method` setting থেকে; এই report-এর UI-তে বদলানো যায় না।
- মিশ্র journal (এক journal-এ একাধিক ভাগ) প্রতিটা অ-নগদ line আলাদা ভাগে যায়।

**DB flow**
1. Snapshot।
2. `CashflowMappingService::missing` খালি না হলে `RPT_MAPPING_INCOMPLETE` (তালিকা সহ), report চলে না।
3. পদ্ধতি অনুযায়ী উপরের ধাপ।
4. শুরুর ও শেষের নগদ।
5. তিন ভাগের মোট = নিট পরিবর্তন কি না (RC4-এর অংশ); না মিললে লাল সতর্কতা আর পার্থক্য।
6. Audit `accounting.report.run`।

**Direct-এ query:** একটা query দিয়ে সংশ্লিষ্ট journal id:

```sql
SELECT DISTINCT l.journal_id
FROM acc_journal_lines l JOIN acc_journals j ...
WHERE l.account_id IN (নগদ account)
  AND j.status IN ('posted','reversed')
  AND j.journal_date BETWEEN ? AND ?
```

তারপর ওই journal-এর অ-নগদ line পড়ে হিসাব।

**Task scope**
1. নগদ হিসাব (শুরু, শেষ, নিট পরিবর্তন)।
2. Direct পদ্ধতি।
3. Indirect পদ্ধতি।
4. মিল পরীক্ষা (তিন ভাগ = নিট পরিবর্তন)।
5. API।
6. UI ও drill-down।
7. Test।

**Out of scope**
- Mapping UI (`P3-07`)।
- `cashflow_method` setting বদলানো।
- Cash Flow Statement-এর Cash basis রূপ।
- RC4 সংরক্ষণ ও রাতের চালানো (`P3-13`)।

**Contract**
`GET /accounting/reports/cash-flow?from=&to=` (পদ্ধতি `cashflow_method` setting থেকে)

UI:
- সময় বাছাই।
- পদ্ধতির নাম দেখানো (setting থেকে, এখানে বদলানো যায় না)।
- তিন ভাগ, শুরু ও শেষের নগদ, `নিট পরিবর্তন`।
- মিল সবুজ বা লাল।
- প্রতিটা লাইনে click → Direct-এ journal-এর তালিকা, Indirect-এ সংশ্লিষ্ট account-এর GL।

**Permission**
`accounting.report.view`

**Error**
`RPT_MAPPING_INCOMPLETE` (422); `RPT_RECON_FAILED` শুধু তথ্য।

**Audit**
`accounting.report.run`

**Acceptance**
AC3.2, AC3.4, AC3.11

**Test**
- নমুনা company-র হাতে করা Cash Flow-এর সাথে মেলানো।
- দুই পদ্ধতির মোট একই।
- নগদ থেকে নগদে স্থানান্তর বাদ।
- মিশ্র journal (এক journal-এ একাধিক ভাগ)।
- ভাগহীন account (`RPT_MAPPING_INCOMPLETE`)।
- Reversal-সহ।

**Deliverable**
দুই পদ্ধতির service (OD-2 যেটা চায়, (গ) হলে দুটোই), API, UI

**Sub-tasks**
নগদ হিসাব, direct, indirect, মিল পরীক্ষা, API, UI, drill-down, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.2, AC3.4, AC3.11 সবুজ।
- OD-1 ও OD-2 লিখিতভাবে বন্ধ (stub দিয়ে এগোনো যাবে না)।
- দুই পদ্ধতির মোট একই, test প্রমাণ।

**Architecture constraints**
- কোনো account-এর ভাগ অনুমান নয়।
- সব সংখ্যা `LedgerQueryService` থেকে।
- OD-1 ও OD-2 বন্ধ না হলে stub দিয়ে চালানো যাবে না।

Owner
Dev A
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-13 — Reconciliation framework

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service + job + api + ui)
Title
Reconciliation framework — Backend ও Frontend
Branch Name
`feature/accounting-reconciliation`
View Order
47
Description
**Summary**
TB, GL, P&L, BS, CF, raw SQL, cache, hash — হিসাবের ভেতরের সব মিল স্বয়ংক্রিয়ভাবে বারবার চেক হবে, ফল সংরক্ষিত থাকবে; কোনো ভুল চুপচাপ পড়ে থাকতে পারবে না।

**Task serial**
`ACC-P3-13 — Reconciliation framework` · Feature

**Depends on**
- `ACC-P3-03`
- `ACC-P3-04`
- `ACC-P3-05`
- `ACC-P3-06`
- `ACC-P3-08`

**Relation: আগের task**
- `P3-03` (GL), `P3-04` (TB), `P3-05` (P&L), `P3-06` (BS), `P3-08` (CF) প্রত্যেকে পুনঃব্যবহারযোগ্য service দিয়েছে। RC1 থেকে RC5 সেই service-গুলোর **ফল তুলনা** করে। এজন্য ওই service-গুলো controller-এ নয়, আলাদা class-এ থাকা জরুরি।
- `P3-02` cache আর `balance_version`: RC7 বৈধ cache row-কে raw SQL-এর সাথে মেলায়। (build sequence-এ `P3-13`-এর `নির্ভর করে`-তে `P3-02` নেই, কিন্তু RC7 তার ওপর, পরোক্ষে `P3-04` মারফত।)
- `P1-10`: `Schema::appendOnly('acc_reconciliation_runs')` helper (table-এ UPDATE/DELETE trigger দিয়ে আটকায়) আর `acc_journals.posted_hash`: RC9 এটা মেলায়। P2-12 post-এর সময় hash বসায় (journal-এর নির্দিষ্ট column আর line `line_no` ক্রমে canonical JSON-এর SHA-256)।
- `P1-12`: `AccountingJob` (company context), `AccountingAudit`, error enum, correlation id।
- `P1-09`: `acc_journals`/`acc_journal_lines` যা RC6 ইচ্ছা করে raw SQL-এ পড়ে।
- `P3-01`: RC6-র তুলনার অপর প্রান্ত।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-11` (Period বন্ধ): `recon_pass` যাচাই (RC1 থেকে RC7, intake চালু থাকলে RC8, আর RC9) এই framework ডাকে, `triggered_by = 'close'`।
- `P3-14`: AC3.1, AC3.3, AC3.4, AC3.10-এর test।
- `P4-10` (RC8): একই framework-এর আরেকটা check class। একই রাতের job ও dashboard-এ।
- `P5-03` (Monitoring): `ReconciliationFailed` event আর `acc_reconciliation_runs`-এ `fail` দেখে `recon_failed` alert (যেকোনো একটা `fail`, সঙ্গে সঙ্গে)।
- `P6-13`/`P6-15`: নতুন RC sub-check (`RC1d`, `RC1m`, `RC1n`) এই framework গ্রহণ করবে। তাই check class-গুলো registry-ভিত্তিক হওয়া দরকার, hard-coded `RC1..RC9` তালিকা নয়।
- `P5-01` UAT (U08, U12): reconciliation dashboard সবুজ, ইচ্ছা করে ভাঙলে লাল।
- **খোলা প্রশ্ন OP-14:** "একটা run-এ ৯টা row, একই `started_at`" কিন্তু Phase 3-এ RC8 নেই (Phase 4-এ)। ধরা হলো: RC8-এর row `result = 'skipped'` হিসেবে লেখা হবে `P4-10` আসা পর্যন্ত (enum-এ `skipped` আছে), আর `P3-11`-এর `recon_pass` সেই `skipped`-কে ব্যর্থ ধরবে না। Tech lead নিশ্চিত করবে।
- **খোলা প্রশ্ন OP-15:** Deliverable-এ "আটটা check class (RC1..RC7, RC9)" কিন্তু schema-তে `check_code` `RC1`–`RC9` (নয়টা)। উপরের OP-14-এর মতোই।

**Related tables / schema**

**`acc_reconciliation_runs`** (append-only)

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `check_code` | VARCHAR(8) | না | `RC1` থেকে `RC9` |
| `scope` | VARCHAR(10) | না | `as_of`, `period` |
| `as_of_date` | DATE | না |  |
| `period_id` | FK | হ্যাঁ | Composite FK `(company_id, period_id)` |
| `result` | VARCHAR(8) | না | `pass`, `fail`, `error`, `skipped` |
| `expected`, `actual`, `difference` | DECIMAL(20,4) | হ্যাঁ |  |
| `detail` | JSON | হ্যাঁ | অমিলের account বা journal-এর তালিকা |
| `triggered_by` | VARCHAR(10) | না | `manual`, `close`, `schedule` |
| `run_by` | FK users | হ্যাঁ | schedule হলে null |
| `started_at`, `finished_at` | TIMESTAMP |  |  |

Index: `idx_acc_rr_company_check_time (company_id, check_code, started_at)`।
Table `Schema::appendOnly` (`P1-10`) দিয়ে বানানো: UPDATE/DELETE কখনো নয় (`ACC_APPEND_ONLY`)।

**Check তালিকা:**

| Check | কী মেলায় | কীভাবে (স্বাধীন পথ) |
| --- | --- | --- |
| RC1 | Trial Balance-এ মোট Debit = মোট Credit | TB service-এর ফল |
| RC2 | প্রতিটা account-এর GL-এর closing = TB-র closing | দুই service-এর ফল তুলনা |
| RC3 | Balance Sheet: Asset = Liability + Equity | BS service-এর ফল |
| RC4 | Cash Flow-এর শেষের নগদ = Balance Sheet-এর নগদ-সমতুল্য account-এর যোগফল, আর তিন ভাগের মোট = নিট পরিবর্তন | CF ও BS service |
| RC5 | P&L-এর নিট লাভ = TB-র Revenue ও Expense account-এর পার্থক্য | P&L ও TB service |
| RC6 | Report-এর মোট = posted journal line-এর যোগফল | **সরাসরি raw SQL**, `LedgerQueryService` পার না হয়ে, যাতে service-এর ভুল ধরা পড়ে। দুটো আলাদা query path |
| RC7 | Derived cache = মূল line-এর যোগফল (শুধু বৈধ row) | cache ও raw SQL |
| RC8 | Event থেকে ledger পর্যন্ত মিল (Phase 4) | `P4-10` |
| RC9 | `posted_hash` মিল: Posted journal ও line থেকে hash আবার হিসাব করে সংরক্ষিতটার সাথে মেলে | batch করে, সবসময় সব নয় |

**Business rules (R3.9)**
- প্রতিটা check একটা আলাদা class, নিজের snapshot transaction-এ।
- `error` মানে check নিজেই চলেনি (exception), `fail` মানে অমিল পাওয়া গেছে, `skipped` মানে প্রযোজ্য নয় (যেমন RC8 intake বন্ধ থাকলে)।
- **RC9 কৌশল:** Posted journal অনেক হতে পারে, তাই রাতে একটা period করে ঘোরানো (সব period ক্রমে, সপ্তাহে একবার সব)। চলতি আর আগের period প্রতিদিন। অমিল পাওয়া journal-এর id `detail`-এ।
- কোনো check ব্যর্থ হলে `ReconciliationFailed` event (`P5-03` alert-এ বাঁধা)। **Report আটকায় না** (R3.9; `RPT_RECON_FAILED` তথ্য, error নয়)।
- RC6 ইচ্ছা করে `LedgerQueryService`-এর ওপর নির্ভর করে না, যাতে service-এর ভুল ধরা পড়ে।
- প্রতিটা report চালানোর সময় সংশ্লিষ্ট check-গুলো (RC1 থেকে RC5) শুধু ফলের পাশে দেখানোর জন্য চলে, **সংরক্ষণ ছাড়া**।

**DB flow**
1. Run শুরু: একটা `started_at` ঠিক করা (এক run-এর সব row-এ একই)।
2. প্রতিটা check class নিজের snapshot transaction-এ চলে (`P3-01`-এর wrapper)।
3. ফল `acc_reconciliation_runs`-এ `INSERT` (একটা run-এ ৯টা row, একই `started_at`)।
4. `fail` হলে `ReconciliationFailed` event।
5. Audit `accounting.recon.run` (ফল সহ)।
6. কখনো row `UPDATE`/`DELETE` নয়।

**কখন চলে**
1. হাতে: `POST /accounting/reconciliation/run` (`triggered_by = 'manual'`)।
2. Period বন্ধের অনুরোধ ও অনুমোদনের সময় (`P3-11`, `triggered_by = 'close'`)।
3. রাতে সব সক্রিয় company-র জন্য (job, company context সহ, `triggered_by = 'schedule'`)।
4. প্রতিটা report চালানোর সময় সংশ্লিষ্ট RC1 থেকে RC5 (সংরক্ষণ ছাড়া)।

**Task scope**
1. Migration (`acc_reconciliation_runs`, `Schema::appendOnly` সহ)।
2. Check class RC1 থেকে RC7 আর RC9 (RC8 Phase 4-এ)।
3. Check registry (নতুন check নিবন্ধনের জায়গা)।
4. Runner (run-এর সব check এক `started_at`-এ, `error` ধরা)।
5. রাতের job (সব সক্রিয় company)।
6. `ReconciliationFailed` event।
7. API ও dashboard UI।
8. Test।

**Out of scope**
- RC8 check class (`P4-10`)।
- Alert পাঠানো (`P5-03`)।
- Period বন্ধ নিজে (`P3-11`)।
- RC1d/RC1m/RC1n (Phase 6)।

**Contract**
- `POST /accounting/reconciliation/run`
- `GET /accounting/reconciliation/latest`
- `GET /accounting/reconciliation/runs?check=&from=&to=`

UI (Dashboard):
- প্রতিটা check-এর সর্বশেষ ফল (সবুজ, লাল, ধূসর), কবে চলেছে, কে চালিয়েছে, পার্থক্য।
- অমিলের তালিকায় click → সংশ্লিষ্ট account বা journal।
- "এখন চালান" বাটন।

**Permission**
`accounting.reconcile.run`

**Error**
`RPT_RECON_FAILED` (তথ্য হিসেবে, error নয়), `ACC_FORBIDDEN`, `ACC_COMPANY_FORBIDDEN`, `ACC_APPEND_ONLY` (সরাসরি SQL-এ বদল চেষ্টা)

**Audit**
`accounting.recon.run`

**Acceptance**
AC3.1, AC3.3, AC3.4, AC3.10

**Test**
- প্রতিটা check-এর জন্য সুস্থ data-য় `pass`।
- ইচ্ছা করে ভাঙা data-য় `fail` (যেমন DB guard সাময়িক বন্ধ করে একটা line বদলে দেওয়া, **শুধু test পরিবেশে**, তারপর RC9 আর RC6 ধরে কি না)।
- `error` পথ (check-এ exception)।
- রাতের job (company context সহ)।
- RC9-এর batch (একটা period করে ঘোরানো)।
- Run-এ সব row-এর `started_at` এক।
- `acc_reconciliation_runs`-এ UPDATE/DELETE ব্যর্থ।

**Deliverable**
Migration, আটটা check class (RC1 থেকে RC7, RC9; RC8 Phase 4-এ), job, API, dashboard

**Sub-tasks**
migration, RC1 থেকে RC7, RC9, job, API, dashboard, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.1, AC3.3, AC3.4, AC3.10 সবুজ।
- DB guard বন্ধ করা শুধু test পরিবেশে, production কোডে এমন পথ নেই।
- নতুন check (RC8, Phase 6-এর RC1d ইত্যাদি) নিবন্ধন করা যায়।

**Architecture constraints**
- RC6 কখনো `LedgerQueryService` ডাকবে না।
- Reconciliation report আটকায় না।
- `acc_reconciliation_runs` append-only।
- প্রতিটা check নিজের snapshot transaction-এ।

Owner
Dev B
Estimate Hours
6
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-10 — Export (Excel ও PDF)

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + job + api)
Title
Export (Excel ও PDF) — Backend ও Frontend
Branch Name
`feature/accounting-report-export`
View Order
48
Description
**Summary**
Screen-এ যা দেখছেন হুবহু Excel বা PDF-এ পাবেন; ফাইল queue-তে তৈরি হবে, একই অনুরোধে দুবার আলাদা ফাইল হবে না।

**Task serial**
`ACC-P3-10 — Export (Excel ও PDF)` · Feature

**Depends on**
- `ACC-P3-03` থেকে `ACC-P3-09` সব (GL, TB, P&L, BS, Cash Flow mapping-সহ CF, Cash Flow, অন্য report)

**Relation: আগের task**
- `P3-03` থেকে `P3-09`: প্রতিটা report একটা service-class, যা screen আর export **দুজনেই** ডাকে (R3.10: screen যে service ও query ব্যবহার করে হুবহু সেটাই)। তাই export নিজের হিসাব লেখে না।
- `P3-09`: Journal Register ও Approval Register-এর export সংযোগ (পূর্ণ রূপ)।
- `P1-12`: `AccountingJob` (company context), `AccountingAudit`, error, correlation id।
- `P1-02`: `accounting.report.export` slug।
- `P3-13`: Reconciliation ব্যর্থ থাকলে ফাইলে "মেলেনি" লেখার জন্য (R3.9)। build sequence-এ `P3-13` (ক্রম ২৮) `P3-10`-এর (ক্রম ২৯) আগে।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-13`-এর `নির্ভর করে` তালিকায় `P3-10` নেই, কিন্তু Pack `P3-10`-এর `অবরুদ্ধ করে`-তে `P3-13` লিখেছে। (ক্রমের ফারাক, নিচে OP-16।)
- `P3-14`: AC3.9 (`ExportParityTest`) আর ১ লক্ষ row export-এর সময়ের পরিমাপ।
- `P3-15`: accountant ফাইলের সংখ্যাও মেলান।
- `P5-03`: `export_failures` alert (`acc_report_exports`-এ `failed`, যেকোনো একটা)।
- `P5-01` UAT U10: Excel ও PDF-এর সংখ্যা মেলে।
- `P1-10`-এর `Schema::appendOnly` এখানে নয়; `acc_report_exports` ইতিহাস, কিন্তু অবস্থা বদলায় (`queued → done`)।
- **খোলা প্রশ্ন OP-16:** `P3-10` build sequence-এ (ক্রম ২৯) এবং `P3-13`-এর `নির্ভর করে`-তে নেই, তবুও `P3-10`-এর `অবরুদ্ধ করে`-তে `P3-13`। ধরা হলো: `P3-13` আগে বানানো হয় (ক্রম ২৮), export-এর "মেলেনি" নোট `P3-13` ফল পড়ে।
- **খোলা প্রশ্ন OP-17:** `report` column-এর সব মান Pack-এ দেওয়া হয়নি (`general-ledger`, `trial-balance` "ইত্যাদি")। প্রতিটা report-এর slug route-এর নামের সাথে মিলিয়ে ঠিক করবে (`profit-loss`, `balance-sheet`, `cash-flow`, `account-summary`, `opening-balance`, `audit`, journal/approval register)।
- **খোলা প্রশ্ন OP-18:** টাকার অঙ্ক "নির্ধারিত দশমিক সংখ্যায়" কোন সংখ্যা: ধরা হলো `currency_precision` setting। Pack শুধু "নির্ধারিত" বলে।

**Related tables / schema**

**`acc_report_exports`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `report` | VARCHAR(40) | না | `general-ledger`, `trial-balance` ইত্যাদি |
| `filters` | JSON | না |  |
| `format` | VARCHAR(8) | না | `xlsx`, `pdf` |
| `request_hash` | CHAR(64) | না | `report` ও `filters` ও `format`-এর hash |
| `status` | VARCHAR(12) | না | `queued`, `processing`, `done`, `failed` |
| `active_key` | CHAR(64) (generated, stored) | হ্যাঁ | `status` `queued` বা `processing` হলে `request_hash`, নইলে null |
| `file_path` | VARCHAR(255) | হ্যাঁ |  |
| `row_count` | INT UNSIGNED | হ্যাঁ |  |
| `requested_by` | FK users | না |  |
| `requested_at`, `started_at`, `finished_at` | TIMESTAMP |  |  |
| `error` | VARCHAR(500) | হ্যাঁ |  |
| `expires_at` | TIMESTAMP | না |  |

Constraint ও index:
- Unique `uq_acc_exp_active (company_id, requested_by, active_key)`: একই ব্যক্তির একই report-এর দুটো চলমান export DB স্তরে আটকায় (দ্বিতীয় অনুরোধ আগেরটাই ফেরত পায়)।
- Index `idx_acc_exp_company_time (company_id, requested_at)`।

**Business rules (R3.10)**
- Screen যে service ও query ব্যবহার করে **হুবহু সেটাই** export ব্যবহার করে; সংখ্যা এক থাকা বাধ্যতামূলক।
- টাকার অঙ্ক নির্ধারিত দশমিক সংখ্যায় **সংখ্যা** হিসেবে (float-এর rounding নয়)।
- ফাইল নির্দিষ্ট পথে `exports/{company}/{id}.{ext}`, আবার চললে একই পথ ওভাররাইট, দুটো ফাইল হয় না।
- Reconciliation ব্যর্থ থাকলে ফাইলে "মেলেনি" লেখা (R3.9)।
- `accounting.report.export` আলাদা permission। প্রতিটা export audit-এ।
- ব্যর্থতা: তিনবার আবার চেষ্টা (বাড়তে থাকা বিরতি), তারপর `failed`। সর্বোচ্চ row সীমা (config, প্রস্তাব ১ লক্ষ); ছাড়ালে `failed` ও বার্তা, ছোট পরিসরে ভাগ করতে বলা।
- ফাইল রাখার সময় `expires_at` পর্যন্ত (30 দিন); তারপর রাতের job ফাইল মোছে ও row-এর `file_path` খালি করে (row থাকে)।
- ডাউনলোড: company ও permission যাচাই, মেয়াদী লিংক।

**DB flow: অনুরোধ**
1. `INSERT` row `queued` (`request_hash`, `expires_at` সহ)।
2. Unique ভাঙলে চলমান row-ই ফেরত।
3. Job dispatch।
4. Audit।

**DB flow: job**
1. company context (`P1-12`)।
2. row `FOR UPDATE`, `queued` হলে `processing` (`started_at`)।
3. Screen যে service ও query ব্যবহার করে হুবহু সেটাই (R3.10)।
4. Excel বা PDF তৈরি, টাকার অঙ্ক নির্ধারিত দশমিক সংখ্যায় সংখ্যা হিসেবে।
5. ফাইল `exports/{company}/{id}.{ext}`-এ লেখা, আবার চললে একই পথ ওভাররাইট।
6. Reconciliation ব্যর্থ থাকলে ফাইলে "মেলেনি" লেখা (R3.9)।
7. `done`, `row_count`, `finished_at`।
8. Audit `accounting.report.exported`।

**Task scope**
1. Migration (`acc_report_exports`, generated column `active_key`, unique)।
2. API: অনুরোধ, অবস্থা, ডাউনলোড।
3. Job (retry, row সীমা)।
4. Excel renderer।
5. PDF renderer।
6. মেয়াদ job (রাতে ফাইল মোছা)।
7. ডাউনলোড UI।
8. Test।

**Out of scope**
- Report হিসাব নিজে (`P3-03` থেকে `P3-09`)।
- Alert পাঠানো (`P5-03`)।
- ফাইলের সংরক্ষণ-নীতি (storage backend) ERPFlow-এর বিদ্যমান ব্যবস্থা (`P0-02`)।

**Contract**
- `POST /accounting/reports/{report}/export` (`filters`, `format`)
- `GET /accounting/exports/{id}` (অবস্থা)
- `GET /accounting/exports/{id}/download` (company ও permission যাচাই, মেয়াদী লিংক)

**Permission**
`accounting.report.export`

**Error**
`ACC_FORBIDDEN`, `ACC_COMPANY_FORBIDDEN` (অন্য company-র export ডাউনলোড), `ACC_MODULE_DISABLED`; `failed` অবস্থায় `error` column-এ বার্তা (সীমা ছাড়ানো ইত্যাদি)।

**Audit**
`accounting.report.exported`

**Acceptance**
AC3.9

**Test**
- Screen ও ফাইলের সংখ্যা এক (কয়েকটা report)।
- একই অনুরোধ দুইবার (একই row)।
- Job মাঝপথে ভাঙা ও আবার চালানো (একটাই ফাইল)।
- অন্য company-র export ডাউনলোড (নিষেধ)।
- মেয়াদ পেরোনো।
- সীমার বেশি row।
- Reconciliation `fail` থাকলে ফাইলে "মেলেনি"।

**Deliverable**
Migration, job, renderer (Excel ও PDF), API, ডাউনলোড UI

**Sub-tasks**
migration, API, job, Excel renderer, PDF renderer, মেয়াদ job, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.9 সবুজ (`ExportParityTest`)।
- Float ব্যবহার নেই; সংখ্যা ফাইলে সংখ্যা-কোষ (text নয়)।
- একই অনুরোধে দুটো ফাইল হয় না।

**Architecture constraints**
- Export screen-এর service ছাড়া আলাদা হিসাব করবে না।
- ফাইল পথ নির্ধারিত (`exports/{company}/{id}.{ext}`)।
- Download সবসময় company ও permission যাচাই করে।

Owner
Dev C
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-11 — Period বন্ধ ও আবার খোলা

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service + api + ui)
Title
Period বন্ধ ও আবার খোলা — Backend ও Frontend
Branch Name
`feature/accounting-period-close-reopen`
View Order
49
Description
**Summary**
Period যাচাই দিয়ে বন্ধ করা যাবে; আবার খুলতে হলে কারণ লিখে দুজনের সম্মতি লাগবে — বন্ধ হিসাবে কেউ চুপচাপ হাত দিতে পারবে না।

**Task serial**
`ACC-P3-11 — Period বন্ধ ও আবার খোলা` · Feature

**Depends on**
- `ACC-P1-08`
- `ACC-P3-13` (reconciliation)
- `ACC-P2-10` (SoD)
- `ACC-P3-01`
- OD-5 (period lock-এর কঠোরতা: `period_lock_mode`, `period_close_checks`)
- OD-15 (বন্ধ period-এ reversal-এর তারিখ)

**Relation: আগের task**
- `P1-08`: `acc_periods` (`status` `open`/`soft_closed`/`closed`, `closed_at`, `closed_by`, `reopened_at`, `reopened_by`, `version`: এই কয়টা column Phase 1-এ বানানো হয়েছিল "Phase 3-এ ব্যবহার" বলে), `PeriodResolver`, `uq_acc_periods_company_id` (event table-এর composite FK-র লক্ষ্য)।
- `P3-13`: `recon_pass` যাচাইয়ের জন্য RC1 থেকে RC7 (আর RC8 intake চালু থাকলে, RC9)। `triggered_by = 'close'`।
- `P2-10`: `SodGuard::assert` যার একটা নিয়ম `sod_poster_cannot_sole_close` (default `true`)। "যে journal post করেছে সে ওই period বন্ধের একমাত্র অনুমোদনকারী নয়"।
- `P3-01`: TB-এর RC1 (period-শেষ তারিখে `tb_balanced`)।
- `P3-02`: `acc_periods.balance_version` (পরে commit-এর পরে বাড়ে) আর `JournalPosted` listener। এই task-এর বন্ধ-প্রক্রিয়া সেটাই ব্যবহার করে (Pack ৯.৫)।
- `P2-12`: Post period row `FOR SHARE` নেয় (লক-ক্রম ৪); `posted_at`, `posted_by` যা এই task পড়ে।
- `P1-10`: `Schema::appendOnly` helper এবং `acc_period_events` সম্পর্কে নোট (নিচে OP-19)।
- `P1-12`: error/audit/company context।

**Relation: পরের task (কে অপেক্ষা করছে)**
- **`P3-12` (PeriodGuard):** এই task-এর `closed`/`soft_closed` অবস্থাই `period_lock_mode` ম্যাট্রিক্সের ইনপুট। এটা না থাকলে কোনো period বন্ধ হয় না, তাই `strict` mode পরীক্ষা করা যায় না।
- **`P5-02` (Cutover মহড়া):** ধাপগুলো বন্ধের সময় এই flow ব্যবহার করে।
- `P3-14`: AC3.7 (`PeriodCloseTest`), AC3.8-এর ভিত্তি।
- `P3-15`: accountant নমুনা company-তে period বন্ধ করে দেখে।
- `P5-01` UAT U09: Period বন্ধ, বন্ধ period-এ Post, আবার খোলা।
- `P5-03`: `locked_post_attempts` alert (সম্পর্কিত, `P3-12`-এর audit থেকে)।
- **খোলা প্রশ্ন OP-19:** `P1-10` `acc_period_events`-কে "পরের Phase-এ append-only table (সিদ্ধান্তের পর)" হিসেবে তালিকাভুক্ত করেছে, কিন্তু এই card-এর trigger শর্তসাপেক্ষ: `pending` অবস্থায় row বদলানো যায় (সিদ্ধান্ত লেখা হয়), শুধু `OLD.status <> 'pending'` হলে নিষেধ। তাই সরল `Schema::appendOnly` এখানে খাটে না। ধরা হলো: এই card নিজের শর্তসাপেক্ষ trigger বানায় (`P1-10`-এর একই `SQLSTATE 45000` ও error mapping ধরে), আর `P1-10`-এর তালিকার "সিদ্ধান্তের পর" শব্দটা এটাই বোঝায়। Tech lead নিশ্চিত করবে।
- **খোলা প্রশ্ন OP-20:** `period_close_sequential` (নতুন setting) Pack-এ "client চাইলে" হিসেবে প্রস্তাবিত, registry-তে (Pack ২.৪) নেই। এখন বাধ্যতামূলক নয়, তাই এই card-এ বানানো হয় না। Client চাইলে নতুন setting registry-তে যোগ হবে।
- **খোলা প্রশ্ন OP-21:** Pack-এ `soft_closed` থেকে `open`-এ ফেরার পথ (soft close তোলা) লেখা নেই; শুধু `open → soft_closed` (soft close), আর `closed → open` (reopen) আছে। `soft_closed → closed` close অনুরোধ ও অনুমোদনে (অনুমতি আছে, কারণ close অনুরোধ `open` বা `soft_closed` থেকে)। `soft_closed → open` কীভাবে হবে সিদ্ধান্তের অপেক্ষায়।
- **খোলা প্রশ্ন OP-22:** `PRD_REOPEN_DENIED` (403) "খোলার শর্ত বা অনুমোদন নেই" বলা আছে, কিন্তু কোন শর্ত ভাঙলে তা enumerated নয় (কারণ ছাড়া reopen কি এই code, নাকি validation?)। ধরা হলো: permission/অনুমোদন সংক্রান্ত না-পারা `PRD_REOPEN_DENIED`; `reason` না দেওয়া `422` validation ধরনের (field সহ)। নিশ্চিত করতে হবে।
- **খোলা প্রশ্ন OP-23:** `sod_poster_cannot_sole_close`-এ ওই period-এ কোনো posted journal না থাকলে নিয়ম প্রযোজ্য কি না (সব `posted_by` সেট খালি) Pack-এ স্পষ্ট নেই। ধরা হলো প্রযোজ্য নয় (লঙ্ঘন নেই)।
- **খোলা প্রশ্ন OP-24 (নকশায় দ্বন্দ্ব):** এই card-এর DB flow-এর ধাপ (৫) বলে অনুমোদনের সময় "period `FOR UPDATE`, যাচাই আবার চালানো"। Pack ৯.৫ বলে ভারী যাচাই (RC1–RC9) lock-এর **বাইরে**, ভেতরে শুধু `balance_version` ও `MAX(posted_at)` তুলনা। Pack নিজেই বলে "`P3-11`-এর 'যাচাই আবার চালানো' এই নকশাতেই করা হবে" তাই ৯.৫ নকশাটি নেওয়া হলো (নিচে DB flow: অনুমোদন)।
- **খোলা প্রশ্ন OD-5, OD-15:** `period_lock_mode` ও `period_close_checks` default, আর বন্ধ period-এ reversal-এর তারিখ। সিদ্ধান্ত আসা পর্যন্ত card `Blocked`।

**Related tables / schema**

**`acc_period_events`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `period_id` | FK | না | Composite FK `(company_id, period_id)` → `acc_periods (company_id, id)` |
| `kind` | VARCHAR(8) | না | `close`, `reopen`, `soft` |
| `status` | VARCHAR(10) | না | `pending`, `approved`, `rejected`, `cancelled` |
| `reason` | VARCHAR(500) | হ্যাঁ | Reopen-এ বাধ্যতামূলক |
| `checks` | JSON | হ্যাঁ | বন্ধের যাচাইয়ের ফল |
| `requested_by` | FK users | না |  |
| `decided_by` | FK users | হ্যাঁ |  |
| `decision_comment` | VARCHAR(500) | হ্যাঁ |  |
| `requested_at`, `decided_at` | TIMESTAMP |  |  |

Constraint, index, trigger:
- Index `idx_acc_pe_company_period (company_id, period_id, requested_at)`।
- Unique: একটা period-এ একটাই `pending` অনুরোধ: generated column `pending_key` (`status = 'pending'` হলে `period_id`, নইলে null)-এ `uq_acc_pe_pending (company_id, pending_key)`।
- Trigger: `OLD.status <> 'pending'` হলে row আর বদলানো বা মোছা যায় না (সিদ্ধান্ত হয়ে গেলে অপরিবর্তনীয়)।

**ব্যবহৃত `acc_periods` column (`P1-08`):** `status`, `closed_at`, `closed_by`, `reopened_at`, `reopened_by`, `version`, `balance_version` (`P3-02`)।

**Business rules (R3.13, R3.14)**
- **Soft close:** অনুমোদন ছাড়া, `accounting.period.manage` permission-এ। Period `open` থেকে `soft_closed`। `kind = 'soft'`, সঙ্গে সঙ্গে `approved`।
- **Close অনুরোধ:** অবস্থা `open` বা `soft_closed` না হলে `JRN_STATE_INVALID`। `period_close_checks` setting-এর (default তিনটাই) যাচাই: 
  - `no_pending_journals`: `SELECT ... FROM acc_journals WHERE company_id = ? AND journal_date BETWEEN start AND end AND status IN ('submitted','approved')` খালি হতে হবে; `draft`, `rejected` থাকলে **শুধু সতর্কতা**।
  - `tb_balanced`: RC1, period-শেষ তারিখে।
  - `recon_pass`: RC1 থেকে RC7, আর RC8 যদি intake চালু থাকে, RC9।
- যেকোনো একটা ব্যর্থ হলে `PRD_CLOSE_BLOCKED` আর `checks` সহ (কোন journal, কোন পার্থক্য); **অনুরোধ তৈরিই হয় না**।
- **অনুমোদন:** `decided_by <> requested_by` আর `accounting.period.approve` permission, নইলে `JRN_SOD_VIOLATION`। `sod_poster_cannot_sole_close` চালু থাকলে: ওই period-এর posted journal-এর **সব** `posted_by` সেই একই ব্যক্তি হলে সে একমাত্র অনুমোদনকারী হতে পারে না (অনুরোধকারী ভিন্ন হলেও)। এটা `SodGuard` ডাকে।
- অনুরোধ আর অনুমোদনের মাঝে নতুন journal এসে থাকতে পারে, তাই অনুমোদনে যাচাই **আবার** চলে; ব্যর্থ হলে event `rejected` (স্বয়ংক্রিয়, কারণ সহ)।
- **আবার খোলা:** অনুরোধ (`reason` বাধ্যতামূলক, `accounting.period.manage`) ও অনুমোদন (`accounting.period.approve`, অন্য ব্যক্তি) একই কাঠামোয়। অনুমোদনে period `closed` থেকে `open`, `reopened_at`, `reopened_by`। আংশিক খোলা (শুধু নির্দিষ্ট তারিখ) এই version-এ নেই, পুরো period-ই। সংশোধনের পর আবার বন্ধ করতে হয় (নতুন close অনুরোধ)।
- আগের period বন্ধ না হলে পরেরটা বন্ধ করা যাবে কি না তা এখন বাধ্যতামূলক নয় (OP-20)। আগের period আবার খোলা হলে পরের closed period-এর `checks` বাতিল হয় না, কিন্তু সতর্কতা দেখায়।
- একটা period-এ একটাই `pending` অনুরোধ (DB unique)।
- সিদ্ধান্ত হয়ে গেলে event row অপরিবর্তনীয় (trigger)।

**DB flow: soft close**
1. `POST /accounting/periods/{id}/soft-close`।
2. transaction; period `FOR UPDATE`; অবস্থা `open` না হলে `JRN_STATE_INVALID`।
3. period `soft_closed` (`version + 1`), event `kind='soft'`, `status='approved'`।
4. Audit।
5. commit।

**DB flow: বন্ধের অনুরোধ**
1. transaction।
2. period `FOR UPDATE`।
3. অবস্থা `open` বা `soft_closed`, নইলে `JRN_STATE_INVALID`।
4. `period_close_checks` setting-এর যাচাই চালানো (উপরের তিনটা)।
5. যেকোনো একটা ব্যর্থ হলে `PRD_CLOSE_BLOCKED` আর `checks` সহ; অনুরোধ তৈরিই হয় না।
6. সবুজ হলে `acc_period_events` `INSERT` (`pending`, `checks` সংরক্ষিত)।
7. commit।

(ভারী যাচাই চালানোর সময় period lock ধরে রাখা হয় না; নিচের "বন্ধের নকশা" দেখুন।)

**DB flow: অনুমোদন (Pack ৯.৫ নকশা)**
1. event `FOR UPDATE`, `pending` কি না।
2. `decided_by <> requested_by`, `accounting.period.approve`, না হলে `JRN_SOD_VIOLATION`।
3. `sod_poster_cannot_sole_close` চালু থাকলে `SodGuard`-এর পরীক্ষা।
4. `acc_periods.balance_version` পড়া (`v`), **lock ছাড়া**।
5. ভারী যাচাই: RC1 থেকে RC9, `no_pending_journals`, `tb_balanced` ইত্যাদি, **lock ছাড়া** (snapshot transaction)।
6. যাচাই ব্যর্থ হলে event `rejected` (স্বয়ংক্রিয়, কারণ সহ), শেষ।
7. নতুন ছোট transaction শুরু: period `FOR UPDATE` (লক-ক্রম ৪; চলমান Post-এর `FOR SHARE`-এর সাথে বিরোধ, তাই চলমান Post শেষ হওয়া পর্যন্ত অপেক্ষা, নতুন Post আটকে)।
8. `balance_version` আবার পড়া।
9. `balance_version = v` হলে মাঝে কোনো Post হয়নি, তাই ধাপ ৫-এর ফল এখনো সত্য। সঙ্গে সস্তা যাচাই `no_pending_journals` ও `MAX(posted_at)` তুলনা (period-র journal-এর সর্বশেষ `posted_at` ধাপ ৫-এর সময়ের পরে হলে ব্যর্থ)। এগুলো commit-এর পরে `balance_version` বাড়ার সংক্ষিপ্ত ফাঁকে বন্ধ ফসকে যাওয়া ঠেকায়।
10. সব সবুজ হলে period `closed`, `closed_at`, `closed_by`, `version + 1`।
11. event `approved` (`decided_by`, `decided_at`)।
12. Audit।
13. commit।
14. `balance_version ≠ v` হলে lock ছেড়ে ধাপ ৪ থেকে আবার (সর্বোচ্চ তিনবার)। তারপরও না মিললে `PRD_CLOSE_BLOCKED` ("চলতি Post-এর কারণে স্থির হচ্ছে না, আবার চেষ্টা করুন")।

**DB flow: আবার খোলা**
অনুরোধ (`reason` বাধ্যতামূলক) ও অনুমোদন একই কাঠামোয়। অনুমোদনে period `closed` থেকে `open`, `reopened_at`, `reopened_by`, `version + 1`। কোনো ভারী যাচাই নেই।

**Task scope**
1. Migration ও trigger (`acc_period_events`, `pending_key` generated column, `uq_acc_pe_pending`)।
2. Soft close।
3. Close অনুরোধ (যাচাই সহ)।
4. Close অনুমোদন (পুনঃযাচাই, `balance_version` নকশা, `SodGuard`)।
5. Reopen (অনুরোধ ও অনুমোদন)।
6. API।
7. UI।
8. Test।

**Out of scope**
- `period_lock_mode`-এর প্রয়োগ (Post/Submit/Reversal আটকানো) → `P3-12`।
- আংশিক period খোলা।
- `period_close_sequential` (OP-20)।
- Reconciliation check নিজেরা (`P3-13`)।

**Contract**
- `POST /accounting/periods/{id}/soft-close` (`accounting.period.manage`)
- Close/reopen অনুরোধ ও অনুমোদনের API route-এর নাম Pack-এ নির্ধারিত নেই (শুধু soft-close দেওয়া): implementation-এ ঠিক করে PR-এ লিখতে হবে।

UI:
- Period-এর তালিকা (অবস্থা, কে কখন বন্ধ করেছে)।
- "বন্ধ করুন" যাচাইয়ের ফল সহ (কোনটা সবুজ, কোনটা লাল আর কেন)।
- অনুমোদনের অপেক্ষায় তালিকা।
- "আবার খুলুন" কারণের ঘর সহ।
- Soft close বাটন।
- আগের period reopen হলে পরের closed period-এর পাশে সতর্কতা।

**Permission**
`accounting.period.manage` (soft close, close/reopen অনুরোধ); `accounting.period.approve` (অনুমোদন); দেখা `accounting.period.view`।

**Error**
`PRD_CLOSE_BLOCKED` (422), `PRD_REOPEN_DENIED` (403), `JRN_SOD_VIOLATION` (403), `JRN_STATE_INVALID` (409)

**Audit**
`accounting.period.close_requested`, `.closed`, `.reopen_requested`, `.reopened`; `SodGuard` লঙ্ঘনে `accounting.guard.denied`।

**Acceptance**
AC3.7

**Test**
- যাচাই ব্যর্থ হলে বন্ধ হয় না (অনুরোধই তৈরি হয় না)।
- অনুরোধ ও অনুমোদনের মাঝে নতুন journal এলে স্বয়ংক্রিয় reject।
- নিজের অনুরোধ নিজে অনুমোদন (নিষেধ)।
- SoD-poster (একমাত্র poster অনুমোদনকারী হতে পারে না)।
- কারণ ছাড়া reopen।
- দুজন একসাথে একই period-এ অনুরোধ (DB unique আটকায়)।
- Soft close।
- অনুমোদনের সময় Post চলমান থাকলে `balance_version` নকশা কাজ করে (তিনবার পুনঃচেষ্টা, তারপর `PRD_CLOSE_BLOCKED`)।
- সিদ্ধান্ত হওয়া event row সরাসরি SQL-এ বদলানো যায় না।
- `draft`/`rejected` journal শুধু সতর্কতা।

**Deliverable**
Migration ও trigger, service, API, UI

**Sub-tasks**
migration, soft close, close অনুরোধ, close অনুমোদন (পুনঃযাচাই), reopen, UI, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.7 সবুজ (`PeriodCloseTest`)।
- OD-5, OD-15 লিখিতভাবে বন্ধ।
- Lock-ক্রম (Pack ৯.১) মানা: period `FOR UPDATE` লক-ক্রম ৪, ভারী যাচাই lock-এর বাইরে।
- সিদ্ধান্ত হওয়া event অপরিবর্তনীয় (trigger test)।

**Architecture constraints**
- ভারী যাচাই চলার পুরো সময় period lock ধরে রাখা যাবে না।
- Period বন্ধ ও আবার খোলা কখনো একজনের সিদ্ধান্তে নয়।
- Posted ledger row এই task কখনো বদলায় না।

Owner
Dev A
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-12 — Period lock-এর কঠোর রূপ (PeriodGuard)

Project
ERPFlow
Module
Accounting Module
Type
Feature (service)
Title
Period lock-এর কঠোর রূপ (PeriodGuard) — Backend
Branch Name
`feature/accounting-period-guard`
View Order
50
Description
**Summary**
`period_lock_mode` ঠিক করবে বন্ধ period-এ Submit, Post, Reversal-এ কী হবে; তিন জায়গায় একই নিয়ম, যাতে বন্ধ হিসাবে কিছু চুপচাপ ঢোকে না।

**Task serial**
`ACC-P3-12 — Period lock-এর কঠোর রূপ (PeriodGuard)` · Feature

**Depends on**
- `ACC-P3-11`
- `ACC-P2-12`
- OD-5, OD-15

**Relation: আগের task**
- `P2-12` (Posting service) `PeriodGuard::check` hook বানিয়ে রেখেছে। Phase 2-এ period সবসময় `open` ছিল, তাই শুধু `period_lock_mode`-এর `warn` পথ চলত: `closed` + `strict` হলে `JRN_PERIOD_LOCKED`, `warn` হলে সতর্কতা আর `accounting.period.locked_post_attempt` audit। এই task সেই hook-কে পূর্ণ রূপ দেয়।
- `P3-11` period-কে `soft_closed`/`closed` করতে শুরু করেছে। তাই এখন থেকে matrix-এর সব সারি প্রমাণযোগ্য।
- `P1-08`: `PeriodResolver::forDate`।
- `P2-03`: `SubmitValidator` (এখানে নতুন hook যোগ হয়)।
- `P2-13`: Reversal-এর তারিখ ঠিক করা (`reversal_default_date`: `today`, `original`, `ask`)।
- `P1-03`: `period_lock_mode` (`off`, `warn`, `strict`, default `warn`), `reversal_default_date`।
- `P1-12`: error `JRN_PERIOD_LOCKED`, audit।
- Pack ৯.১ (লক-ক্রম): Post-এ period `FOR SHARE` (ক্রম ৪)।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-14`: AC3.8 (`PeriodGuardMatrixTest`, নয়টা জোড়া)।
- `P3-15` (সই) ও `P5-01` UAT U09 (বন্ধ period-এ Post)।
- `P5-03`: `locked_post_attempts` alert (`monitor_closed_period_post_attempts` দিনে) `activity_logs`-এর `accounting.period.locked_post_attempt` event গোনে।
- `P4-06` (Event থেকে Draft) পরে একই hook ব্যবহার করবে (Post-এর পথ একই)।
- **খোলা প্রশ্ন OP-25:** matrix-এর `soft_closed` + `strict` ঘরে "শুধু `journal.post` permission-ওয়ালার জন্য, নইলে `JRN_PERIOD_LOCKED`"। কিন্তু Post নিজেই `journal.post` ছাড়া অসম্ভব (`P2-12` ধাপ ৬)। অর্থাৎ শর্তটা কেবল Submit (Maker) ও Reversal-তারিখের ক্ষেত্রে অর্থপূর্ণ। ধরা হলো: Submit-এ `soft_closed` + `strict`-এ Maker যদি `journal.post` না রাখে তাহলে `JRN_PERIOD_LOCKED`। Accountant নিশ্চিত করবে।
- **খোলা প্রশ্ন OP-26:** `period_lock_mode = off` ঘরে `closed` period-এ "অনুমোদিত" (কোনো সতর্কতা, audit নেই?)। Matrix-এ `off` সারিতে শুধু "অনুমোদিত"। কিন্তু Audit-এ "সব চেষ্টা" বলা আছে। ধরা হলো: `off` মোডেও `accounting.period.locked_post_attempt` লেখা হয় না (সতর্কতা/audit শব্দটা শুধু `warn`/`strict` সারির সাথে লেখা); নিশ্চিত করা দরকার।

**Related tables / schema**
কোনো নতুন table নেই। পড়া: `acc_periods` (`status`, `start_date`, `end_date`)।

Matrix:

| Period-এর অবস্থা | `period_lock_mode = off` | `warn` | `strict` |
| --- | --- | --- | --- |
| `open` | অনুমোদিত | অনুমোদিত | অনুমোদিত |
| `soft_closed` | অনুমোদিত | অনুমোদিত + সতর্কতা + audit | অনুমোদিত + সতর্কতা + audit (শুধু `journal.post` permission-ওয়ালার জন্য, নইলে `JRN_PERIOD_LOCKED`) |
| `closed` | অনুমোদিত | অনুমোদিত + সতর্কতা + audit | `JRN_PERIOD_LOCKED` |

**Business rules (R3.15)**
- `period_lock_mode` বদলালে আগে Post হওয়া কিছু প্রভাবিত হয় না।
- Hook তিন জায়গায়:
  1. **Submit validator (`P2-03`):** `strict` মোডে `closed` period হলে Submit-এই `JRN_PERIOD_LOCKED`, যাতে Maker দেরিতে না জানে।
  2. **Post (`P2-12` ধাপ ৯, Pack ৯.২-এর ধাপ ৪):** আবার, কারণ Submit-এর পরে period বন্ধ হয়ে থাকতে পারে।
  3. **Reversal-এর তারিখ ঠিক করার সময় (`P2-13`):** `reversal_default_date` অনুযায়ী পাওয়া তারিখ `strict` ও `closed` period-এ পড়লে `JRN_PERIOD_LOCKED`; তখন Maker খোলা period-এর তারিখ বেছে নেয় (বা Admin period আবার খোলে)।
- Post-এ period row `FOR SHARE`: বন্ধ করার transaction (`FOR UPDATE`, `P3-11`) আর এই Post একসাথে চললে একজন অপেক্ষা করে, ফলে বন্ধ হওয়ার ঠিক মুহূর্তে Post ফসকে ঢোকা অসম্ভব।
- Audit সব চেষ্টার জন্য: সফল (সতর্কতা সহ) বা ব্যর্থ।

**DB flow**
1. `PeriodResolver::forDate(companyId, date)` → period-এর `status` পড়া (কোনো period না পেলে `JRN_DATE_INVALID`, `P1-08`)।
2. Post-এর ক্ষেত্রে period row `FOR SHARE` (লক-ক্রম ৪)।
3. matrix অনুযায়ী সিদ্ধান্ত: অনুমোদিত / অনুমোদিত + সতর্কতা + audit / `JRN_PERIOD_LOCKED`।
4. Audit `accounting.period.locked_post_attempt` (সফল সতর্কতা সহ বা ব্যর্থ)।

**Task scope**
1. `PeriodGuard` নিয়ম (matrix, তিন mode × তিন অবস্থা)।
2. Submit hook (`SubmitValidator`-এ)।
3. Post-এর lock (`FOR SHARE`) ও hook (`PostingService`-এ)।
4. Reversal-তারিখ hook।
5. Audit event।
6. Test।

**Out of scope**
- Period বন্ধ/খোলা (`P3-11`)।
- `period_lock_mode` setting বদলানোর UI (`P1-04`-এর settings পাতা)।
- আংশিক period lock।

**Contract**
`PeriodGuard::check(company, journal_date)` (`P2-12`-এ বানানো hook-এর একই নাম ও আকার)। ফল: অনুমোদিত (সতর্কতা সহ বা ছাড়া) বা `JRN_PERIOD_LOCKED`। Submit, Post ও Reversal-তারিখ তিন জায়গা থেকেই এই একটাই method ডাকা হয়; কোনো public HTTP endpoint নেই। (Matrix-এর `journal.post` শর্তের জন্য actor-এর permission কীভাবে পৌঁছাবে, তা implementation-এ ঠিক হবে, OP-25।)

**Permission**
নতুন slug নেই। `accounting.journal.post` (matrix-এর `soft_closed` + `strict` ঘরে)।

**Error**
`JRN_PERIOD_LOCKED` (409)

**Audit**
`accounting.period.locked_post_attempt` (সফল, সতর্কতা সহ বা ব্যর্থ, সব চেষ্টা)

**Acceptance**
AC3.8

**Test**
- তিন mode × তিন অবস্থা (নয়টা জোড়া)।
- Post আর period বন্ধের একসাথে চলা (কোনো journal বন্ধ period-এ ঢোকে না)।
- Submit-এ আগাম আটকানো (`strict` + `closed`)।
- Reversal-এর তারিখ বন্ধ period-এ।
- `period_lock_mode` বদলালে আগে Post হওয়া অপ্রভাবিত।
- প্রতিটা চেষ্টার audit।

**Deliverable**
`PeriodGuard` পূর্ণ, test

**Sub-tasks**
guard নিয়ম, Submit hook, Post-এর lock, reversal hook, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC3.8 সবুজ (`PeriodGuardMatrixTest`)।
- OD-5, OD-15 বন্ধ।
- Concurrency test MySQL-এ একাধিক connection দিয়ে।
- `P2-12`-এর বিদ্যমান test ভাঙেনি (hook-এর পুরোনো `warn` আচরণ matrix-এর অংশ)।

**Architecture constraints**
- Lock-ক্রম (Pack ৯.১) ভাঙা যাবে না: period `FOR SHARE` ক্রম ৪, journal ও request ও account-এর পরে।
- Guard একটাই class, Submit/Post/Reversal তিন জায়গা থেকে ডাকা হয়; নিয়ম আলাদা আলাদা কপি নয়।

Owner
Dev A
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-14 — Performance ও Phase 3-এর test suite

Project
ERPFlow
Module
Accounting Module
Type
Test (`test` + `ops`)
Title
Performance ও Phase 3-এর test suite
Branch Name
`feature/accounting-phase3-tests`
View Order
51
Description
**Summary**
Phase 3-এর সব report-এর Acceptance Criteria স্বয়ংক্রিয় test-ে প্রমাণিত হবে; বড় data-তেও report-এর গতি গ্রহণযোগ্য থাকবে — না হলে contingency (যেমন `entry_date` বা OD-14 Option B) ঠিক হবে।

**Task serial**
`ACC-P3-14 — Performance ও Phase 3-এর test suite` · Test

**Depends on**
- `ACC-P3-01` থেকে `ACC-P3-13` সব

**Relation: আগের task**
- প্রতিটা Phase 3 task নিজের test লিখেছে। এই task সেগুলোকে AC ধরে এক জায়গায় আনে, ফাঁক ধরে, আর বড় data-য় গতি মাপে।
- `P3-01`: AC3.1, AC3.5, AC3.12-এর ভিত্তি আর contingency-র সিদ্ধান্তের বিন্দু (লাইনে `entry_date` কপি, বা OD-14 Option B)।
- `P3-02`: AC3.10 (cache)।
- `P3-03` থেকে `P3-06`, `P3-08`, `P3-09`: AC3.1, AC3.2, AC3.3, AC3.4, AC3.6।
- `P3-07`: AC3.11।
- `P3-10`: AC3.9।
- `P3-11`, `P3-12`: AC3.7, AC3.8।
- `P3-13`: RC1 থেকে RC9, AC3.1, AC3.3, AC3.4, AC3.10।
- `P2-16`: Phase 2-এর test suite এবং helper (failure-injection, সমান্তরাল runner) যা এই suite পুনঃব্যবহার করতে পারে।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P3-15` (accountant-এর সই): এই suite সবুজ না হলে accountant-এর হাতে report যায় না।
- **Phase 4 শুরু:** Phase 3 পুরো সবুজ হওয়া Phase 4-এর শর্ত।
- `P5-05` (শেষ security ও performance): এই task-এর ফল (সীমা ও পরিমাপ) staging-এ আবার চালায়।
- **খোলা প্রশ্ন OP-27:** নিচের performance সংখ্যাগুলো **প্রস্তাব**; Phase শুরুতে client ও tech lead নিশ্চিত করবে (AC3.12-এ "নির্ধারিত আকারের data" বলা আছে)। "কয়েক সেকেন্ড", "এক সেকেন্ডের কাছাকাছি", "কয়েক মিনিট" এর সংখ্যাগত মান ঠিক হয়নি।
- **খোলা প্রশ্ন OP-28:** Golden suite-এর সংজ্ঞা Blueprint ১২.২-এ (এই Pack-এ নেই); শুধু বলা আছে "৭টা পর্যন্ত এই Phase-এ সবুজ"। সাতটার তালিকা Blueprint থেকে নিতে হবে।
- **খোলা প্রশ্ন OP-29:** Seeder command-এর নাম Pack-এ নেই, শুধু "একটা seeder command"। নাম implementation-এ ঠিক হবে।

**Related tables / schema**
নতুন table নেই।

**Performance পরিমাপ।** Test পরিবেশে (কখনো production নয়) একটা seeder command বাস্তবসম্মত বড় data বানায়। **সংখ্যাগুলো প্রস্তাব।**

| বিষয় | প্রস্তাবিত data | প্রস্তাবিত সীমা |
| --- | --- | --- |
| Data আকার | ৫ বছর, ৫০০ account, ১০ লক্ষ journal line, ৬০টা period | — |
| Trial Balance (এক বছর) | উপরের data | কয়েক সেকেন্ডের মধ্যে |
| Balance Sheet | উপরের data | কয়েক সেকেন্ডের মধ্যে |
| General Ledger, ১ লক্ষ row-এর account, এক পৃষ্ঠা | উপরের data | এক সেকেন্ডের কাছাকাছি |
| Cash Flow | উপরের data | কয়েক সেকেন্ডের মধ্যে |
| Export, ১ লক্ষ row | উপরের data | queue-তে কয়েক মিনিট |

প্রতিটা প্রধান query-র `EXPLAIN` দেখে index ঠিক ব্যবহার হচ্ছে কি না যাচাই। সীমা ছাড়ালে `P3-01`-এর contingency (line-এ `entry_date` কপি বা OD-14-এর Option B) ও তা সিদ্ধান্তের জন্য tech lead-এর কাছে।

**AC থেকে test**

| AC | কী | Test class (প্রস্তাব) |
| --- | --- | --- |
| AC3.1 | TB ও GL posted data-র সাথে মেলে | `LedgerReconciliationTest` (RC1, RC2, RC6 স্বাধীন SQL-এর সাথে) |
| AC3.2 | P&L, BS, CF অনুমোদিত mapping অনুযায়ী | `StatementCorrectnessTest` (হাতে করা সংখ্যার সাথে) |
| AC3.3 | BS সমীকরণ | `BalanceSheetEquationTest` (RC3) |
| AC3.4 | CF-এর নগদ BS-এর সাথে | `CashFlowReconciliationTest` (RC4) |
| AC3.5 | Report শুধু ledger-এর অবস্থার journal দেখায় | `LedgerStatusFilterTest` (Draft, submitted, approved, rejected, cancelled আসে না; posted ও reversed আসে) |
| AC3.6 | Company isolation report ও export-এও | `ReportIsolationTest` |
| AC3.7 | Period বন্ধ ও খোলার নিয়ম | `PeriodCloseTest` |
| AC3.8 | Lock mode | `PeriodGuardMatrixTest` |
| AC3.9 | Export screen-এর সাথে মেলে | `ExportParityTest` |
| AC3.10 | Cache ভুল সংখ্যা দেয় না | `CacheStalenessTest` (RC7) |
| AC3.11 | ভাগহীন account-এ পরিষ্কার error | `CashflowMappingTest` |
| AC3.12 | বড় data-য় গতি | `ReportPerformanceTest` (CI-তে নয়, আলাদা চালানো) |
| AC3.13 | বিদ্যমান ERPFlow অক্ষত | সম্পূর্ণ বিদ্যমান suite |

**Acceptance**
AC3.1 থেকে AC3.13 (সব Phase 3 AC; AC3.12 আলাদা চালানো)

**Business rules**
- **Golden suite** (Blueprint ১২.২)-এর ৭টা পর্যন্ত এই Phase-এ সবুজ।
- **সব report test MySQL-এ**।
- `ReportPerformanceTest` CI-র সাধারণ run-এ নয়, আলাদা চালানো হয় (বড় data লাগে)।
- Performance পরিমাপ কখনো production-এ নয়।
- Seeder শুধু test/staging পরিবেশে চলে।

**Task scope**
1. Seeder (বড় data: ৫ বছর, ৫০০ account, ১০ লক্ষ line, ৬০ period)।
2. `EXPLAIN` যাচাই (প্রধান query-গুলোর index ব্যবহার)।
3. প্রতিটা test class আলাদা (উপরের তালিকা)।
4. Golden suite-এর (৭টা পর্যন্ত) সংযোগ।
5. Performance রিপোর্ট ও সিদ্ধান্ত (সীমা ছাড়ালে contingency)।
6. বিদ্যমান ERPFlow regression (AC3.13)।

**Out of scope**
- Production-এ পরিমাপ।
- নতুন feature বা report।
- `P5-05`-এর শেষ security/performance যাচাই।

**Deliverable**
Seeder, performance রিপোর্ট ও সিদ্ধান্ত, সব test class

**Sub-tasks**
seeder, EXPLAIN যাচাই, প্রতিটা test class, performance রিপোর্ট

**Definition of done**
- AC3.1 থেকে AC3.13 সবুজ (AC3.12 আলাদা চালানো, রিপোর্ট আছে)।
- Golden suite-এর ৭টা পর্যন্ত সবুজ।
- বিদ্যমান ERPFlow suite সবুজ।
- MySQL CI job ব্যবহার হয়েছে।
- Performance সীমা ছাড়ালে tech lead-এর লিখিত সিদ্ধান্ত।

**Architecture constraints**
- Concurrency, snapshot, generated column, trigger-নির্ভর test SQLite-এ চালানো যাবে না।
- Seeder production-এ চলবে না।
- Contingency (entry_date বা Option B) এই task নিজে বাস্তবায়ন করে না, শুধু সিদ্ধান্তের জন্য তোলে।

Owner
Dev A
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P3-15 — Accountant-এর নমুনা report pack ও সই

Project
ERPFlow
Module
Accounting Module
Type
Governance (`gov`)
Title
Accountant-এর নমুনা report pack ও সই
Branch Name
`feature/accounting-phase3-sample-report-pack` (শুধু নমুনা company-র data/seeder ও সই করা নথির জন্য, product code বদলায় না)
View Order
52
Description
**Summary**
Client-এর accountant নমুনা company-তে পুরো এক আর্থিক বছরের data দিয়ে সব report নিজে হাতে মিলিয়ে মেনে নেবেন — তখনই Phase 3 report-এ লিখিত আস্থা থাকবে, Phase 4 শুরু হবে।

**Task serial**
`ACC-P3-15 — Accountant-এর নমুনা report pack ও সই` · Governance

**Depends on**
- `ACC-P3-14`
- OD-1, OD-2, OD-5, OD-14, OD-15, OD-16, OD-20, OD-23 লিখিতভাবে বন্ধ

**Relation: আগের task**
- `P3-14` সব test সবুজ করেছে এবং performance সীমা প্রমাণ করেছে। তাই accountant-এর হাতে যে report যায় সেগুলো যান্ত্রিকভাবে নির্ভুল।
- `P3-03`, `P3-04`, `P3-05`, `P3-06`, `P3-08`, `P3-09`: যে report-গুলো accountant মেলান (TB, GL, P&L, BS, Cash Flow, Account Summary)।
- `P3-07`: Cash Flow mapping accountant-ই সই করে (OD-2)।
- `P3-11`: period বন্ধের প্রবাহ একই নমুনায় প্রদর্শন।
- `P2-13`: reversal ও adjustment যা নমুনা data-য় থাকে।
- OD-গুলোর বিস্তারিত অর্থ: OD-1 (accounting basis), OD-2 (নগদ-সমতুল্য account ও Cash Flow পদ্ধতি), OD-5 (period lock), OD-14 (ledger-এর উৎস), OD-15 (বন্ধ period-এ reversal-এর তারিখ), OD-16 (cash basis P&L, (ক) হলে `P3-05b`), OD-20 (Balance Sheet-এর বছর-শেষ closing, Retained Earnings-এর double count), OD-23 (opening-এ Revenue/Expense)।

**Relation: পরের task (কে অপেক্ষা করছে)**
- **Phase 4 শুরু** (`P4-01`: Change request অনুমোদন ও সিদ্ধান্ত বন্ধ)। `P3-15` সই না হলে কোনো Phase 4 task শুরু হবে না।
- `P5-01` (UAT pack ও সই): `P3-15`-র নমুনা pack থেকে UAT scenario ও ফল।
- `P5-02` (Cutover মহড়া): এই task-এর সই করা নিশ্চয়তা ধরে staging-এ মহড়া।
- **খোলা প্রশ্ন OP-30:** "সই করা নমুনা pack"-এর বিন্যাস (PDF, নথি, spreadsheet) Pack-এ নির্ধারিত নেই। Client-এর accountant ও Finance contact-এর সাথে ঠিক হবে।
- **খোলা প্রশ্ন OP-31:** "বাস্তবসম্মত journal" বানানোর দায়িত্ব কার (accountant নিজে, না dev seeder), Pack-এ স্পষ্ট নয়। কাজ (১) "একটা নমুনা company তৈরি, বাস্তবসম্মত journal" বলে; ধরা হলো dev/QA বানায়, accountant বিষয়বস্তু অনুমোদন করে।

**Related tables / schema**
কোনো নতুন table নেই। নমুনা company-র data (journal, line, CoA, period) ব্যবহার।

**Business rules**
- OD-1, OD-2, OD-5, OD-14, OD-15, OD-16, OD-20, OD-23 লিখিতভাবে বন্ধ না হলে এই task শুরু বা শেষ হবে না।
- Accountant নিজের হাতে বা অন্য tool-এ একই data-র হিসাব করে মেলাবেন। System-এর হিসাব মেনে নেওয়া নয়, স্বাধীন হিসাব।
- অমিল পেলে কারণ খোঁজা, ঠিক করা, আবার। অমিল চুপচাপ গ্রহণ নয়।
- Phase 3 exit criteria (Blueprint ৮.১০) পূরণ।

**Task scope**
1. একটা নমুনা company তৈরি: একটা পূর্ণ আর্থিক বছরের বাস্তবসম্মত journal (আয়, খরচ, বেতন, ভাড়া, সম্পদ কেনা, ঋণ, reversal, adjustment সহ)।
2. সব report চালানো: TB, GL, P&L, BS, Cash Flow, Account Summary।
3. Accountant তাঁর নিজের হাতে বা অন্য tool-এ একই data-র হিসাব করে মেলানো।
4. অমিল পেলে কারণ খোঁজা, ঠিক করা, আবার।
5. সই করা নমুনা pack।

**Out of scope**
- নতুন feature বা report যোগ।
- Production data।
- Phase 4-এর কোনো কাজ।
- OD বন্ধ করার আলোচনা নিজে (সেটা আগেই লিখিতভাবে হওয়ার কথা; এই task শুধু বন্ধ হয়েছে কি না যাচাই করে)।

**Deliverable**
- সই করা নমুনা report pack
- বন্ধ হওয়া OD-র তালিকা

**Permission**
প্রযোজ্য নয় (নথি-ভিত্তিক)। নমুনা company-তে report চালাতে `accounting.report.view`।

**Audit**
Report চালানো `accounting.report.run` স্বাভাবিক নিয়মে audit-এ যায়।

**Acceptance**
AC3.2, Phase 3 exit criteria (Blueprint ৮.১০)

**Test**
প্রযোজ্য নয় (গভর্নেন্স)। প্রমাণ = accountant-এর স্বাধীন হিসাবের সাথে মিল।

**Sub-tasks**
নমুনা company ও journal, report চালানো, accountant-এর হিসাব মেলানো, অমিল ঠিক, সই, OD তালিকা

**Definition of done**
- সই করা নমুনা report pack আছে।
- আটটা OD (1, 2, 5, 14, 15, 16, 20, 23) লিখিতভাবে বন্ধ, তালিকা সংযুক্ত।
- অমিল থাকলে কারণ ও সমাধান নথিভুক্ত।
- Phase 3 exit criteria পূরণ।
- Phase 4-এর (`P4-01`) ও `P5-01`-এর নির্ভরতা খুলে দেওয়া হয়েছে।

**Architecture constraints**
- Accountant-এর সই ছাড়া Phase 4 শুরু নয়।
- অমিল "পরে দেখব" বলে এড়ানো যাবে না।

Owner
Dev C
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment
