# Accounting Module — Phase 1 Tasks (Foundation ও Configuration)

> এখানকার প্রতিটা task আলাদা করে একজন কাজ করতে পারবে।
> Phase 1 শেষ মানে একটা company accounting চালু করার মতো **তৈরি** করা যায় — কিন্তু এখনও কোনো লেনদেন post হবে না।
> কাজের ক্রম: module → permission → settings → checklist → CoA → period → journal ভিত্তি → DB guard → opening balance → tenancy/audit → test।
> Estimate, Owner, Sprint ইচ্ছা করে ফাঁকা — পরে ভর্তি হবে।

## Phase 1-এ সব task-এ মেনে চলতে হবে (প্রতিটা task-এর DoD-তেও এগুলো ধরে নেওয়া হয়)

- **Database:** MySQL, InnoDB, `utf8mb4`। Table prefix `acc_`।
- **Primary key:** `ACC-P0-02`-এর Convention Sheet অনুযায়ী (অনুমান: bigint unsigned বা ULID)। সব `*_id` FK একই ধরন।
- **Tenant:** প্রতিটা business table-এ `company_id NOT NULL`, `companies(id)`-এ FK; composite index-এ `company_id` সবার আগে।
- **টাকা:** `DECIMAL(20,4)`। Enum হিসেবে `VARCHAR(32)` + `CHECK` — MySQL-এর built-in enum নয়।
- **Optimistic lock:** বদলানো যায় এমন row-এ `version INT UNSIGNED NOT NULL DEFAULT 1`; update-এ `WHERE version = ?` আর `version = version + 1`।
- **লেজার:** Posted বা ledger row কখনো মোছা হয় না। ডিফল্ট `ON DELETE RESTRICT`; ledger table-এ `CASCADE` নয়।
- **Migration:** table প্রতি আলাদা migration; একবার apply হওয়া migration এডিট নয়; পিছনে সামঞ্জস্যপূর্ণ (add → alter → drop); `down()` নিরাপদ।
- **Composite FK:** `account_id` বা `journal_id` যেখানে আছে, সেখানে `(company_id, account_id)` / `(company_id, journal_id)` — একা `account_id`-র FK নয় (অন্য company-র data জোড়া লাগানো DB-তেই আটকাতে)।
- **সাধারণ DoD (প্রতিটা task-এ):** Service–Repository (interface সহ), Form Request, API Resource; `company_id` tenant scope + IDOR test; Feature test (happy path, validation, unauthorized/forbidden); audit `AccountingAudit` দিয়ে; error `AccountingError` enum code দিয়ে; flag বন্ধ রেখে merge নিরাপদ; বিদ্যমান ERPFlow regression সবুজ; Project/Payroll/Asset/Commissioning code untouched।

**Phase 1 task-গুলো কার ওপর ঝুলে (নির্ভরতা):**

```
P1-01 ─┬─► P1-12 ──────────────────────────────┐
       ├─► P1-02 ──┐                            │
       └─► P1-03 ─┬─► P1-04 ─► P1-05           │
                  ├─► P1-06 ─► P1-07 ─┐         │
                  └─► P1-08 ──┐       │         │
        P1-06+P1-08+P0-05 ─► P1-09 ─► P1-10 ─► P1-11 (P1-07,P1-12ও লাগে)
        সব ─► P1-13 ─► P1-14 ─► Phase 2
```

---

## ACC-P1-01 — Module scaffold ও flag

Project
ERPFlow
Module
Accounting Module
Type
Feature (service)
Title
Module scaffold ও flag — Backend ও Frontend
Branch Name
`feature/accounting-module-scaffold`
View Order
6
Description
**Summary**
এই কাজটায় `Accounting` module-এর খালি কাঠামো বানাতে হবে — flag বন্ধ থাকলে কিছুই চালু হবে না। বাকি Phase 1 task-এর নিরাপদ ভিত্তি এটাই।

**Task serial**
`ACC-P1-01 — Module scaffold ও flag` · Feature

**Depends on**
- `ACC-P0-02` (module scaffold পদ্ধতি, `Setting` API নিশ্চিত)
- `ACC-P0-03` (branch ও release নিয়ম, CI check)

**Relation: আগের task**
`P0-02` বলে দিয়েছে কোন বিদ্যমান module নকল করে scaffold বানাতে হয় আর `Setting` model কীভাবে কাজ করে। `P0-03` merge নিয়ম ঠিক করেছে। দুটো না থাকলে এখানে আন্দাজে কাজ শুরু হতো, পরে ঘুরে আসতে হতো।

**Relation: পরের task (কে অপেক্ষা করছে)**
**সব Phase 1 task** এটার ওপর। বিশেষ করে `P1-02` (permission), `P1-03` (settings), `P1-12` (tenancy, audit, error)। Flag ছাড়া কোনো accounting route চালু করা যাবে না।

**Related tables / schema**
কোনো নতুন table নেই। শুধু বিদ্যমান `Setting` table পড়া:
`SELECT value FROM settings WHERE company_id = ? AND group = 'accounting' AND key = 'enabled'`

**Business rules**
- Flag `accounting.enabled` company ধরে, `Setting` model-এ (group `accounting`, key `enabled`)।
- তিনটা flag (`accounting.enabled`, `event_intake.enabled`, `event_intake.sources.*`) `acc_settings`-এ নয়, `Setting`-এ থাকে, যাতে `acc_settings` table তৈরির আগেই flag কাজ করে।
- **Default সবসময় `false`।**
- Flag বন্ধ হলে middleware `ACC_MODULE_DISABLED` (403) দেয়।
- Module চালু থাকলে migration চলে, কিন্তু সব ব্যবহার flag-এ আটকানো।

**Task scope**
1. `Modules/Accounting` তৈরি (Convention Sheet-এর পদ্ধতিতে): service provider, routes, config, migrations ফোল্ডার।
2. `modules_statuses.json`-এ `Accounting` যোগ।
3. `AccountingFeatureService` (`TimeBillingFeatureService`-এর ধাঁচে): `enabled(companyId)`, `intakeEnabled(companyId)`, `sourceEnabled(companyId, source)`। সব default `false`।
4. Middleware `accounting.enabled`।
5. Frontend: `registerModules.ts`-এ module নিবন্ধন; navigation-এ আইটেম যা flag বন্ধ থাকলে দেখা যায় না।

**Out of scope**
Flag চালু করার UI/API (`P5-06`, `P4-11`) — এখানে শুধু infrastructure। Setup Checklist (`P1-05`) আলাদা task।

**Contract**
`AccountingFeatureService::enabled(companyId): bool`, `::intakeEnabled(companyId)`, `::sourceEnabled(companyId, source)`।

**Error**
`ACC_MODULE_DISABLED` (403)

**Audit**
`accounting.flag.enabled`, `accounting.flag.disabled` (flag বদলানোর card `P5-06` আর `P4-11`-এ লেখা হবে, এই task শুধু event নাম নিবন্ধন করে)

**Acceptance**
AC1.10

**Test**
Flag বন্ধ থাকলে কোনো route সাড়া দেয় না; navigation-এ আইটেম নেই; বিদ্যমান module-এর কোনো test ভাঙে না।

**Deliverable**
খালি কিন্তু বুট হওয়া module, flag service, middleware।

**Sub-tasks**
module কাঠামো, status, flag service, middleware, frontend নিবন্ধন, test

**Definition of done**
- Phase 1-এর সাধারণ DoD (উপরের তালিকা) পূরণ।
- Flag বন্ধ রেখে `development`-এ merge নিরাপদ; PR-এ "flag Default বন্ধ প্রমাণিত" টিক।
- বিদ্যমান ERPFlow test একটাও ভাঙেনি।

**Architecture constraints**
- Flag default কখনো `true` নয় — merge gate-এ প্রমাণ লাগবে।
- Project/Payroll/Asset/Commissioning-এর code ছুঁয়ে ফেলা যাবে না।

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

---

## ACC-P1-12 — Tenancy, audit আর error-এর সাধারণ ব্যবস্থা

Project
ERPFlow
Module
Accounting Module
Type
Feature (service, cross-cutting)
Title
Tenancy, audit আর error-এর সাধারণ ব্যবস্থা
Branch Name
`feature/accounting-tenancy-audit-error`
View Order
7
Description
**Summary**
এই কাজটায় company context, audit লেখা আর error response একবারেই গুছিয়ে রাখতে হবে — যাতে বাকি task-গুলো একই জিনিস ব্যবহার করে, কেউ আলাদা নিজের মতো বানাবে না।

**Task serial**
`ACC-P1-12 — Tenancy, audit আর error-এর সাধারণ ব্যবস্থা` · Feature

**Depends on**
- `ACC-P1-01`
- `ACC-P0-02` (প্রশ্ন ৩ `TenantContext`, ৫ `ActivityLogService`)

**Relation: আগের task**
`P1-01` module আর flag দিয়েছে। `P0-02` Convention Sheet-এ `TenantContext` job-এ কীভাবে বসে আর `ActivityLogService`-এর signature কী — সেটা এখানে ব্যবহার।

**Relation: পরের task (কে অপেক্ষা করছে)**
ক্রমে `P1-01`-এর ঠিক পরে রাখা হয়েছে — **error, audit বা job যেখানে আছে প্রায় সব task** এটা ব্যবহার করে (`P1-03`, `P1-08`, `P1-11`, `P2-*`, `P3-*`, `P4-*`)। আগে না থাকলে প্রতিটা task নিজের মতো error/audit বানিয়ে ফেলত।

**Related tables / schema**
কোনো নতুন table নেই। বিদ্যমান `activity_logs`-এ লেখা।

**Business rules**
- `companyId` ছাড়া job ডাকা যাবে না (exception, job আরম্ভই হয় না)।
- Job শেষে (`finally`) `TenantContext` পরিষ্কার, যাতে পরের job আগের company দেখে না।
- ব্যর্থ বা অস্বীকৃত চেষ্টাও audit-এ লেখা হয়, শুধু সফল কাজ নয়।
- অপ্রত্যাশিত exception 500 আর `correlation_id`, ভেতরের তথ্য বাইরে যাবে না।

**Task scope**
1. **Company context:** `RunsInCompanyContext` trait বা base class `AccountingJob`: constructor-এ `companyId` বাধ্যতামূলক, `handle()`-এর শুরুতে `TenantContext` বসানো, `finally`-তে পরিষ্কার। `CompanyScope` console-এ enforce না হলে সেটা test দিয়ে ধরা।
2. **Audit:** `AccountingAudit::log(event, subject, before, after, reason = null)`: সাধারণ field (`company_id`, `actor_id`, `subject_type`, `subject_id`, `before`, `after`, `reason`, `correlation_id`) বসিয়ে `ActivityLogService` ডাকে। Permission বা company লঙ্ঘনে middleware `accounting.guard.denied` লেখে।
3. **Error:** `AccountingException` ও সন্তান class; একটা `AccountingError` enum (code ও HTTP status বাঁধা, কোডে একটাই উৎস)। Handler সব Accounting API-তে `{code, message, field, details, correlation_id}` আকারে উত্তর দেয়।
4. **Correlation:** Middleware `X-Correlation-Id` পড়ে বা বানায়, response-এও ফেরত দেয়।

**Out of scope**
নির্দিষ্ট business error-এর ব্যবহার (প্রতিটা task নিজে enum থেকে নেবে)।

**Error**
`ACC_FORBIDDEN` (403), `ACC_COMPANY_FORBIDDEN` (403), `ACC_MODULE_DISABLED` (403)

**Acceptance**
AC1.8

**Test**
- Job context ছাড়া ব্যর্থ।
- Context পরিষ্কার হয় (পরের job আগের company দেখে না)।
- একটা contract test `AccountingError`-এর প্রতিটা case ঘুরে HTTP status আর response আকার যাচাই করে।
- Audit-এ সব সাধারণ field আছে।

**Deliverable**
Base job, audit service, error enum ও handler, correlation middleware।

**Sub-tasks**
base job, audit service, error enum, handler, correlation, test

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- AC1.8 সবুজ।
- Pack-এর Section ২.৩-এর সব error code enum-এ আছে।

**Architecture constraints**
- HTTP status আর error code-এর একমাত্র উৎস `AccountingError` enum — handler/API-তে আলাদা করে লেখা যাবে না।
- পরে নতুন code (যেমন `COA_CODE_INVALID`) শুধু enum-এ যোগ; string ছড়ানো নয়।

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

---

## ACC-P1-02 — Permission slug ও role seed

Project
ERPFlow
Module
Accounting Module
Type
Feature (service)
Title
Permission slug ও role seed
Branch Name
`feature/accounting-permissions-seed`
View Order
8
Description
**Summary**
এই কাজটায় Accounting-এর সব permission নিবন্ধিত থাকবে আর ডিফল্ট role-এ বসানো থাকবে — যাতে কোনো API route permission ছাড়া খোলা না থাকে।

**Task serial**
`ACC-P1-02 — Permission slug ও role seed` · Feature

**Depends on**
- `ACC-P1-01`

**Relation: আগের task**
`P1-01` module আর flag দিয়েছে। Module বুট হওয়ার পর permission middleware যোগ করা যায়।

**Relation: পরের task (কে অপেক্ষা করছে)**
**সব API task** (`P1-04`, `P1-07`, `P1-11`, Phase 2–5 API)। Route permission middleware ছাড়া বানানো যাবে না।

**Related tables / schema**
RBAC-এর বিদ্যমান table-এ row যোগ (নতুন table নয়)। Seeder idempotent: আবার চালালে duplicate হয় না।

**Business rules**
- Permission company ধরে দেওয়া হয়। এক company-তে পাওয়া permission অন্য company-তে কাজ করে না (বিদ্যমান RBAC এটা কীভাবে করে তা Convention Sheet-এ)।
- ডিফল্ট role: Accountant, Finance Manager, Head of Finance, Auditor, Admin। প্রতিটার ডিফল্ট permission Blueprint ৪.৫ ও Phase-ভিত্তিক role table অনুযায়ী।

**Phase 1-এর slug (মোট ২৪টা slug Pack-এর Section ২.৩-এ, Phase অনুযায়ী পরে আরও আসবে)**
`accounting.settings.view/.manage/.approve`, `accounting.enable.manage`, `accounting.coa.view/.manage`, `accounting.journal.view/.create`, `accounting.period.view`, `accounting.audit.view`। বাকিগুলো (journal.approve/post/reverse/override, approval.manage, period.manage/approve, report.*, cashflow.manage, reconcile.run, mapping.manage, intake.*) config-এ একসাথে নিবন্ধিত, কিন্তু ব্যবহার হবে তাদের নিজের Phase-এ।

**Task scope**
1. `config/actions.php`-এ `accounting` module-এর অধীনে সব slug।
2. ডিফল্ট role-এ বসানোর seeder বা migration (Convention Sheet অনুযায়ী)।
3. প্রতিটা API route-এ permission middleware।

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

**Audit**
`accounting.guard.denied`

**Acceptance**
AC1.6

**Test**
প্রতিটা role-slug জোড়ার matrix test; company পার হওয়ার চেষ্টা; seeder দুইবার চালানো।

**Deliverable**
Slug তালিকা, seeder, middleware।

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- AC1.6 (permission matrix) সবুজ।
- Seeder দুইবার চালালে কোনো duplicate নেই।

**Architecture constraints**
- বিদ্যমান RBAC ব্যবস্থা নতুন করে বানানো যাবে না, ব্যবহার করতে হবে।
- নতুন approval engine নয়।

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

---

## ACC-P1-03 — Settings store, registry, history ও lock

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service)
Title
Settings store, registry, history ও lock — Backend
Branch Name
`feature/accounting-settings-store-backend`
View Order
9
Description
**Summary**
এই কাজটায় company-ভিত্তিক typed setting, তার ইতিহাস আর post-এর পর lock-এর নিয়ম থাকবে — যাতে নীতি কোডে বাঁধা না থেকে Admin Configuration থেকে বদলানো যায়।

**Task serial**
`ACC-P1-03 — Settings store, registry, history ও lock` · Feature

**Depends on**
- `ACC-P1-01`
- `ACC-P0-04` (OD-3 currency, OD-4 fiscal year: কাঠামো আটকায় না, শুধু **মান** বসাতে লাগে)

**Relation: আগের task**
`P1-01` flag service আর module দিয়েছে। `P0-04` client decision list-এ OD-3 (currency), OD-4 (fiscal year) — কাঠামো আটকায় না, **মান** বসাতে লাগে।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P1-04` (Settings UI ও দ্বিতীয় অনুমোদন): `set()`-এর step 5 change request তৈরি করে এই task-এর registry দেখে।
- `P1-05` (Checklist): ধাপ S01-S03, S09 `acc_settings` পড়ে।
- `P1-06` (CoA): code-এর দৈর্ঘ্য ও পরিসর setting-এ।
- `P1-08` (Period): `fiscal_year_start` পড়ে।
- `P2-03` (Validator): সব setting এখান থেকে পড়ে।

**Related tables / schema**

**`acc_settings`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `key` | VARCHAR(100) | না | Registry-র key |
| `value` | JSON | না | Typed মান |
| `version` | INT UNSIGNED | না | Default 1 |
| `created_at`, `updated_at` | TIMESTAMP | না |  |
| `updated_by` | FK users | হ্যাঁ |  |

Unique: `uq_acc_settings_company_key (company_id, key)`।

**`acc_settings_history`** (শুধু যোগ হয়, কখনো বদলায় না বা মোছা হয় না)

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `key` | VARCHAR(100) | না |  |
| `old_value` | JSON | হ্যাঁ | প্রথমবার হলে null |
| `new_value` | JSON | না |  |
| `changed_by` | FK users | না |  |
| `reason` | VARCHAR(500) | হ্যাঁ |  |
| `change_request_id` | FK | হ্যাঁ | `P1-04` থেকে এলে |
| `created_at` | TIMESTAMP | না |  |

Index: `idx_acc_settings_history_company_key_time (company_id, key, created_at)`।

**Business rules**
- Registry (`SettingRegistry`) কোডে একটাই উৎস: প্রতিটা key-র ধরন, যাচাই, default, lock, sensitive, phase, OD। Pack-এর Section ২.৪ ঠিক যা। UI আর API এটাই পড়ে।
- "বাধ্যতামূলক" default মানে কোনো default নেই; Admin না বসানো পর্যন্ত Setup Checklist সবুজ হয় না, `get()` তখন `null` দেয়।
- Lock-করা key (যেমন `fiscal_year_start`, `base_currency`, `currency_precision`, `accounting_basis`, `system_accounts`, `opening_cutover_date`) কোনো journal post হওয়ার পর বদলানো যায় না (`SET_LOCKED`)।
- Flag তিনটা (`accounting.enabled`, `event_intake.enabled`, `event_intake.sources.*`) এখানে নয়, `P1-01`-এর `Setting` model-এ।

**`set()`-এর DB flow (এক transaction)**
1. transaction শুরু।
2. `SELECT ... FROM acc_settings WHERE company_id = ? AND key = ? FOR UPDATE`।
3. Registry দিয়ে মান যাচাই (ধরন, পরিসর); ব্যর্থ হলে `SET_INVALID`।
4. Key-র `lock = true` হলে `PostedJournalProbe::hasPosted(companyId)` ডাকা। **এটা একটা interface।** এই task-এ এর stub implementation থাকে যা সবসময় `false` দেয় (কারণ `acc_journals` table এখনো নেই)। **আসল implementation `P1-09`-এ বসে:** `SELECT EXISTS(SELECT 1 FROM acc_journals WHERE company_id = ? AND status = 'posted')`। Posted থাকলে `SET_LOCKED`।
5. Key `sensitive_settings` তালিকায় থাকলে আর `settings_require_second_approval` true হলে শুধু change request তৈরি (`P1-04`), এখানেই শেষ।
6. নইলে `acc_settings` upsert: `version + 1`, `WHERE version = ?` মিলিয়ে; না মিললে `JRN_CONFLICT`।
7. `acc_settings_history`-এ row।
8. Audit event।
9. commit।

**Task scope**
দুটো migration, registry, `SettingsService` (`get`, `set`, `all`, `history`), probe interface ও stub, API (`GET`, `PUT`, history), test।

**Out of scope**
Settings UI, দ্বিতীয় অনুমোদনের পূর্ণ workflow (`P1-04`), `PostedJournalProbe`-এর আসল implementation (`P1-09`)।

**Contract**
`SettingsService::get(companyId, key)`, `::set(...)`, `::all(companyId)`, `::history(companyId, key)`। API: `GET`, `PUT`, history API।

**Error**
`SET_INVALID` (422), `SET_LOCKED` (409), `JRN_CONFLICT` (409)

**Audit**
`accounting.setting.changed`

**Acceptance**
AC1.4

**Test**
টাইপ ও পরিসরের যাচাই; lock; দুজন একসাথে বদলানো; history যোগ হওয়া; default ফেরত।

**Deliverable**
দুটো migration, registry, service, probe interface ও stub, API, history API।

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- AC1.4 সবুজ।
- `acc_settings_history`-এ `UPDATE`/`DELETE` করা যায় না (পরে `P1-10` trigger আটকাবে; এই task-এ application-এ কখনো update/delete নেই)।

**Architecture constraints**
- `SettingRegistry`-ই key-র type/default/lock-এর একমাত্র উৎস — UI/API-তে duplicate definition নয়।
- `PostedJournalProbe` stub এখানে; আসল query `P1-09`-এ (`acc_journals` তৈরি হলে)।

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

---

## ACC-P1-04 — Settings UI ও দ্বিতীয় অনুমোদন

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service + ui)
Title
Settings UI ও দ্বিতীয় অনুমোদন (Maker–Checker)
Branch Name
`feature/accounting-settings-ui-approval`
View Order
10
Description
**Summary**
এই কাজটায় settings screen আর সংবেদনশীল setting-এ দ্বিতীয় অনুমোদন (Maker–Checker) থাকবে — `base_currency` বা `accounting_basis`-এর মতো জিনিস একজন একা বদলাতে পারবে না।

**Task serial**
`ACC-P1-04 — Settings UI ও দ্বিতীয় অনুমোদন` · Feature

**Depends on**
- `ACC-P1-03`
- `ACC-P1-02`

**Relation: আগের task**
`P1-03`-এর `set()` step 5 বলে sensitive হলে change request — সেই request এই task বানায়। `P1-02` `accounting.settings.manage` / `.approve` slug দিয়েছে।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P1-05` (Checklist UI): এই task-এর settings পাতায় "ঠিক করুন" লিংক যায়।
- `P3-07` (নগদ-সমতুল্য account বদল): `cash_equivalent_accounts` সংবেদনশীল, তাই দ্বিতীয় অনুমোদনের পথে।
- `P4-05` (mapping), `P4-11` (event_intake flag, প্রথমবার), `P5-06` (`accounting.enabled`), `P6-02`/`P6-06`/`P6-03`: সবাই এই task-এর **`ChangeApplier` রেজিস্ট্রি** ব্যবহার করে prefix (`mapping:`, `flag:`, `jtype:`, `jtemplate:`, `series:`) নিবন্ধন করে। তাই `ChangeApplier` এমনভাবে বানাও যাতে পরে prefix যোগ করা যায়।

**Related tables / schema**

**`acc_setting_change_requests`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `key` | VARCHAR(100) | না |  |
| `old_value` | JSON | হ্যাঁ | অনুরোধের সময়ের মান |
| `proposed_value` | JSON | না |  |
| `base_version` | INT UNSIGNED | না | অনুরোধের সময় `acc_settings.version` |
| `reason` | VARCHAR(500) | না | বাধ্যতামূলক |
| `status` | VARCHAR(16) | না | `pending`, `approved`, `rejected`, `cancelled` |
| `requested_by` | FK users | না |  |
| `decided_by` | FK users | হ্যাঁ |  |
| `decided_at` | TIMESTAMP | হ্যাঁ |  |
| `decision_comment` | VARCHAR(500) | হ্যাঁ |  |
| `version` | INT UNSIGNED | না |  |
| `created_at`, `updated_at` | TIMESTAMP | না |  |

Index: `idx_acc_scr_company_status (company_id, status)`। Check: `decided_by` ও `decided_at` শুধু `status ≠ pending` হলে থাকবে।

**Business rules**
- **অনুরোধকারী নিজে অনুমোদন করতে পারে না (সবসময়, setting বদলে বন্ধ করা যায় না)।**
- একটা key-র জন্য একসাথে একটাই `pending` অনুরোধ; pending থাকা অবস্থায় সরাসরি `set()` সেই key-তে আটকে।
- `settings_require_second_approval` false হলে অনুরোধ তৈরিই হয় না, সরাসরি লেখা।
- অনুমোদনের সময় `acc_settings.version` ≠ `base_version` হলে অনুরোধ বাতিল (`cancelled`, নতুন code `SET_STALE_REQUEST` 409), নতুন করে অনুরোধ করতে হবে।

**DB flow: অনুমোদন (এক transaction)**
1. transaction।
2. `SELECT ... FROM acc_setting_change_requests WHERE id = ? FOR UPDATE`।
3. `status = 'pending'` না হলে `JRN_STATE_INVALID`।
4. `decided_by <> requested_by` না হলে `JRN_SOD_VIOLATION`।
5. `accounting.settings.approve` permission।
6. `acc_settings` row `FOR UPDATE`; `version` = `base_version` না হলে অনুরোধ বাতিল (`SET_STALE_REQUEST`)।
7. `SettingsService`-এর ভেতরের লেখার অংশ (দ্বিতীয় অনুমোদন এড়িয়ে) দিয়ে মান লেখা, history-তে `change_request_id` সহ।
8. request `approved`।
9. commit।

**Task scope**
Migration, request service, approve/reject service, API, settings পাতা, history ড্রয়ার, অপেক্ষমাণ tab, test।

**UI**
1. Settings পাতা: group ধরে (Basic, Journal, Approval-সংক্রান্ত, Period, Report, Intake, Monitoring); প্রতিটায় মান, default, lock চিহ্ন (কেন lock, কোন journal পোস্ট হয়েছে), সংবেদনশীল চিহ্ন।
2. বদলানোর সময় কারণ লেখার ঘর।
3. History ড্রয়ার।
4. "অপেক্ষমাণ অনুরোধ" tab অনুমোদনকারীর জন্য: পুরোনো ও নতুন মান পাশাপাশি, প্রভাবের বিবরণ (যেমন cash equivalent বদলালে Cash Flow বদলাবে)।

**Contract**
`POST /accounting/settings/{key}/change-requests`, `POST .../{id}/approve`, `.../reject`, `.../cancel`, `GET` তালিকা।

**Permission**
`accounting.settings.manage` (অনুরোধ), `accounting.settings.approve` (সিদ্ধান্ত)

**Error**
`JRN_STATE_INVALID`, `JRN_SOD_VIOLATION`, `SET_STALE_REQUEST` (নতুন)

**Audit**
`accounting.setting.change_requested`, `.approved`, `.rejected`

**Acceptance**
AC1.4, AC1.6

**Test**
নিজে approve (নিষেধ); stale অনুরোধ; একই key-তে দুটো অনুরোধ; rejected-এর পর আবার।

**Deliverable**
Migration, service, API, UI, `ChangeApplier` রেজিস্ট্রি।

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- AC1.4 ও AC1.6 সবুজ।
- `ChangeApplier` prefix-ভিত্তিক নিবন্ধন সমর্থন করে (পরের task-গুলো prefix যোগ করবে)।

**Architecture constraints**
- পুরো ApprovalEngine এখানে নয় — settings-এর জন্য হালকা Maker–Checker। Phase 2 journal approval আলাদা।

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

---

## ACC-P1-05 — Setup Checklist

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service + ui)
Title
Setup Checklist
Branch Name
`feature/accounting-setup-checklist`
View Order
11
Description
**Summary**
এই কাজটায় company accounting চালু করার আগে সব দরকারি জিনিস ঠিক আছে কি না যাচাই হবে — অসম্পূর্ণ setup-এ `accounting.enabled` চালু করা যাবে না।

**Task serial**
`ACC-P1-05 — Setup Checklist` · Feature

**Depends on**
- `ACC-P1-03`
- `ACC-P1-04`

**Relation: আগের task**
`P1-03` setting মান দেয় (checklist S01–S03, S09 এখান থেকে পড়ে)। `P1-04` settings UI দেয় — লাল ধাপে "ঠিক করুন" লিংক ওখানে যায়।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P5-06` (`accounting.enabled` চালু): `canEnable()` এখান থেকে।
- `P2-14` (opening post): post হলে `ChecklistService::evaluate` চালায়, S10 সবুজ হয়।
- কিছু ধাপ (S04, S05) `P1-06` (CoA)-এর পর পূর্ণ কাজ করবে; তার আগে সেই checker `pending` দেবে। S06-S08 Phase 2 চালু না হওয়া পর্যন্ত `not_applicable`।

**Related tables / schema**

**`acc_setup_status`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `step_code` | VARCHAR(40) | না | `S01_fiscal_year` ইত্যাদি |
| `state` | VARCHAR(16) | না | `pending`, `done`, `not_applicable`, `skipped` |
| `detail` | JSON | হ্যাঁ | কী বাকি তার বিবরণ |
| `evaluated_at` | TIMESTAMP | না |  |
| `updated_by` | FK users | হ্যাঁ | `skipped` হলে কে |

Constraint: `uq_acc_setup_company_step (company_id, step_code)`।

**Checklist-এর ধাপ**

| ধাপ | কী যাচাই | কখন বাধ্যতামূলক |
| --- | --- | --- |
| `S01_fiscal_year` | `fiscal_year_start` আছে | সবসময় |
| `S02_currency` | `base_currency` ও `currency_precision` আছে | সবসময় |
| `S03_basis` | `accounting_basis` আছে | সবসময় |
| `S04_coa` | কমপক্ষে একটা leaf account, প্রতিটা type-এ অন্তত একটা | সবসময় |
| `S05_system_accounts` | দুটো system account নির্ধারিত | সবসময় |
| `S06_voucher` | `voucher_format` আছে | Phase 2 চালু হলে |
| `S07_approval_rule` | কমপক্ষে একটা সক্রিয় rule | Phase 2 চালু হলে |
| `S08_roles` | কমপক্ষে একজন Maker আর একজন Checker | Phase 2 চালু হলে |
| `S09_cutover` | `opening_cutover_date` আছে | সবসময় |
| `S10_opening` | Opening journal post হয়েছে বা Admin স্পষ্ট করে এড়িয়েছে | চালুর আগে |
| `S11_cashflow_mapping` | নগদ account ও `cashflow_class` ঠিক | Cash Flow চালু হলে, নয়তো `not_applicable` |
| `I01_mappings` | প্রয়োজনীয় সব mapping বৈধ | `event_intake.enabled` চালু করতে |
| `I02_currency_policy` | `event_currency_policy` আছে | `event_intake.enabled` চালু করতে |
| `I03_source_flags` | অন্তত একটা source চালু | Asset intake চালু করতে |

**Business rules**
- **`S01` থেকে `S10` সবুজ না হলে `accounting.enabled` চালু করা যায় না (`SET_INCOMPLETE`)।** `I01`-`I03` আলাদা group, শুধু intake চালুর সময়।
- প্রতিটা ধাপ আলাদা checker class, যে **নিজে মূল DB পড়ে** ফল বের করে (সংরক্ষিত অবস্থার ওপর নির্ভর করে না)।
- `canEnable()` true যদি S01-S10-এর সবকটা `done`, `not_applicable` বা `skipped`।
- শুধু S10 `skipped` হতে পারে, Admin কারণ সহ।

**কখন চলে**
যেকোনো setting, CoA বা permission বদলের পর (event listener); checklist পাতা খোলার সময়; `accounting.enabled` চালুর অনুরোধের সময় (সবসময় নতুন করে)।

**Task scope**
Migration, checker class সব ধাপের (১৪টা), `ChecklistService::evaluate`, listener, API, UI, test। ফল একটা transaction-এ upsert।

**Contract**
`GET /accounting/setup-checklist`, `POST /accounting/setup-checklist/steps/{code}/skip` (শুধু S10, কারণ সহ)। `POST /accounting/enable` পূর্ণ হবে `P5-06`-এ।

**UI**
ধাপের তালিকা: সবুজ, লাল, ধূসর (প্রযোজ্য নয়); প্রতিটা লাল ধাপে "ঠিক করুন" লিংক সংশ্লিষ্ট পাতায়।

**Error**
`SET_INCOMPLETE`

**Audit**
`accounting.setup.step_changed`

**Acceptance**
AC1.5

**Test**
প্রতিটা ধাপের লাল ও সবুজ; ধাপ বদলের পর নিজে নিজে মূল্যায়ন; `canEnable`।

**Deliverable**
Migration, checker class সব ধাপের, service, API, UI।

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- AC1.5 সবুজ।
- যে ধাপের উৎস table এখনো নেই (S04, S05, S10), সেটা ভাঙে না, `pending` দেয়।

**Architecture constraints**
- Checklist step-এর `state` DB-তে cache, কিন্তু `canEnable()` বা enable চেষ্টায় **সবসময়** checker দিয়ে live data থেকে পুনর্মূল্যায়ন — পুরোনো সবুজ "বিশ্বাস" করা যাবে না।

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

---

## ACC-P1-06 — Chart of Accounts: schema ও service

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service)
Title
Chart of Accounts schema ও service — Backend
Branch Name
`feature/accounting-coa-backend`
View Order
12
Description
**Summary**
এই কাজটায় প্রতি company-র আলাদা Chart of Accounts (CoA) থাকবে — অন্য company-র account parent বা line-এ জোড়া লাগানো DB composite FK দিয়েই আটকাবে।

**Task serial**
`ACC-P1-06 — Chart of Accounts: schema ও service` · Feature

**Depends on**
- `ACC-P1-03` (code-এর দৈর্ঘ্য ও পরিসর setting-এ; OD-7 আংশিক, কাঠামো আটকায় না)

**Relation: আগের task**
`P1-03` settings store থেকে `coa_code_min_length`, `coa_code_max_length`, `coa_type_ranges`, `coa_inactive_nonzero_policy` পড়ে — account create/update-এ validation এগুলো দিয়ে।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P1-07` (CoA API ও UI): এই service-এর ওপর।
- `P1-09` (Journal table): `acc_journal_lines`-এর composite FK `(company_id, account_id)` → এই task-এর `acc_accounts (company_id, id)` unique-কে লক্ষ্য করে। **তাই `uq_acc_accounts_company_id` বাদ দেওয়া যাবে না।**
- `P1-11` (Opening balance): account leaf ও active কি যাচাই।
- **Probe stub:** এই task `PostedLineProbe`-এর stub রাখে (সবসময় `false`); আসল implementation `P1-09`-এ বসে।

**Related tables / schema**

**`acc_accounts`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `code` | VARCHAR(20) | না |  |
| `name` | VARCHAR(150) | না |  |
| `type` | VARCHAR(16) | না | `asset`, `liability`, `equity`, `revenue`, `expense` |
| `parent_id` | FK (নিজের) | হ্যাঁ |  |
| `is_group` | BOOLEAN | না | Default false; `true` হলে line বসানো যাবে না |
| `is_active` | BOOLEAN | না | Default true |
| `is_system` | BOOLEAN | না | Default false |
| `system_key` | VARCHAR(40) | হ্যাঁ | `opening_balance_equity`, `retained_earnings` |
| `cashflow_class` | VARCHAR(16) | হ্যাঁ | `operating`, `investing`, `financing` |
| `is_cash_equivalent` | BOOLEAN | না | Default false |
| `description` | VARCHAR(500) | হ্যাঁ |  |
| `version` | INT UNSIGNED | না | Default 1 |
| `created_at`, `updated_at`, `created_by`, `updated_by` |  |  | সাধারণ নিয়ম |

Constraint ও index:
- `uq_acc_accounts_company_code (company_id, code)`
- **`uq_acc_accounts_company_id (company_id, id)`** (অন্য table-এর composite FK-র লক্ষ্য)
- `uq_acc_accounts_company_syskey (company_id, system_key)` (`system_key` null হলে বহু row চলে, MySQL-এ null unique-এ গণ্য হয় না)
- `fk_acc_accounts_parent (company_id, parent_id)` → `acc_accounts (company_id, id)` (অন্য company-র account parent হতে পারে না, DB নিজেই আটকায়)
- `ck_acc_accounts_type`, `ck_acc_accounts_cf`
- `idx_acc_accounts_company_parent (company_id, parent_id)`, `idx_acc_accounts_company_type_active (company_id, type, is_active)`

**Business rules (Blueprint ৬.৫, R1.1 থেকে R1.6)**
- Group account-এ line নেই; line আছে এমন account-এ `is_group = true` করা যায় না।
- Parent অবশ্যই `is_group = true` এবং একই type।
- Line আছে এমন account-এর type বদল আটকানো (`COA_IN_USE`)।
- System account মোছা, inactive করা, type বদল সব আটকে (`COA_SYSTEM_PROTECTED`)।
- মোছা শুধু যদি line নেই আর অন্য account-এর parent নয়।
- Parent বদলালে চক্র দেখা (সর্বোচ্চ গভীরতা 10)।
- Inactive করার সময় closing balance শূন্য না হলে `coa_inactive_nonzero_policy` অনুযায়ী `warn` বা `block` (balance হিসাব `P1-09`-এর পরে; stub শূন্য ফেরত)।

**DB flow: তৈরি (এক transaction)**
1. transaction।
2. `parent_id` থাকলে parent row `SELECT ... FOR UPDATE`।
3. যাচাই: parent `is_group = true`; parent-এর type = নতুন account-এর type; code-এর দৈর্ঘ্য ও পরিসর settings অনুযায়ী। ভাঙলে নতুন code `COA_CODE_INVALID` (422)।
4. `INSERT`। Unique ভাঙলে `COA_CODE_DUPLICATE`।
5. Audit।
6. commit।

**Task scope**
Migration, model, `AccountService` (`create/update/deactivate/reactivate/delete`, প্রতিটায় `version` মেলানো), চক্র যাচাই, probe stub, test।

**Out of scope**
API ও UI (`P1-07`)।

**Error**
`COA_CODE_DUPLICATE`, `COA_CODE_INVALID` (নতুন), `COA_IN_USE`, `COA_SYSTEM_PROTECTED`, `JRN_CONFLICT`

**Audit**
`accounting.coa.created/.updated/.deactivated/.reactivated/.deleted`

**Acceptance**
AC1.2

**Test**
একই code দুই company-তে; অন্য company-র parent; type বদল line সহ ও ছাড়া; চক্র; system account; দুজন একসাথে বদলানো।

**Deliverable**
Migration, model, service।

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- AC1.2 সবুজ।
- সরাসরি SQL দিয়ে অন্য company-র parent বসাতে গেলে DB FK আটকায় (test প্রমাণ)।

**Architecture constraints**
- Composite FK নিয়ম ভাঙা যাবে না।
- `PostedLineProbe`-এর stub `P1-09`-এ আসল implementation দিয়ে বদলাতে হবে (সেই task-এর দায়িত্ব)।

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

---

## ACC-P1-07 — CoA API ও UI

Project
ERPFlow
Module
Accounting Module
Type
Feature (api + ui)
Title
CoA API ও UI
Branch Name
`feature/accounting-coa-api-ui`
View Order
13
Description
**Summary**
এই কাজটায় accountant বা finance manager CoA গাছের মতো দেখে account তৈরি/সম্পাদনা/নিষ্ক্রিয় করতে পারবে — API আর UI একসাথে।

**Task serial**
`ACC-P1-07 — CoA API ও UI` · Feature

**Depends on**
- `ACC-P1-06`
- `ACC-P1-02`

**Relation: আগের task**
`P1-06` service আর business rule দিয়েছে। এখানে শুধু API ও UI — নতুন rule বানানো যাবে না, server-ই আসল বাধা। `P1-02` permission slug দিয়েছে।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P1-11` (Opening balance গ্রিড): account picker এই API থেকে leaf ও active account আনে।
- `P2-02` (journal editor): account picker একই।
- `P1-05`-এর S04, S05: এই task-এর "system account তৈরি" endpoint দিয়ে সবুজ হয়।

**Related tables / schema**
নতুন table নেই। `acc_accounts` (`P1-06`)।

**Business rules**
- Bulk import নেই, বাটনও নেই (OD-19)।
- Type বদলানোর ঘর line থাকলে নিষ্ক্রিয়, কারণ লিখে।
- System account চিহ্নিত ও সুরক্ষিত।

**Contract**
- `GET /accounting/accounts` (গাছ বা flat; filter: type, active, search, `is_group`)
- `POST`, `PUT /{id}` (`version` সহ)
- `POST /{id}/deactivate`, `POST /{id}/reactivate`, `DELETE /{id}`
- `POST /accounting/accounts/system`: Admin একটা ক্লিকে দুটো system account তৈরি করে (নাম ও code বসিয়ে), না হলে বিদ্যমান account চিহ্নিত করে।

**UI**
1. গাছের view, expand ও collapse; প্রতিটা node-এ code, নাম, type, অবস্থা।
2. তৈরি ও সম্পাদনার ফর্ম: setting-এর code নিয়মের ইঙ্গিত দেখায়।
3. Type বদলানোর ঘর line থাকলে নিষ্ক্রিয়।
4. Inactive দেখার toggle।
5. System account চিহ্ন ও সুরক্ষা।

**Permission**
দেখা: `accounting.coa.view`। বদলানো: `accounting.coa.manage`।

**Acceptance**
AC1.2, AC1.6

**Test**
API-র permission matrix; UI-তে গাছ ও ফর্ম; optimistic `version` ভাঙা।

**Deliverable**
API, গাছের UI, ফর্ম।

**Sub-tasks**
list API, write API, system account API, গাছ UI, ফর্ম UI, error বার্তা, test

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- সব error API-র `field` ধরে ফর্মের জায়গায় দেখায়।

**Architecture constraints**
- CoA UI-তে business rule duplicate নয় — type বদল disable ইত্যাদি server error দেখায়, client শুধু UX।

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

---

## ACC-P1-08 — Accounting period কাঠামো

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service + job)
Title
Accounting period কাঠামো — Backend
Branch Name
`feature/accounting-periods-backend`
View Order
14
Description
**Summary**
এই কাজটায় fiscal year আর মাস অনুযায়ী accounting period তৈরি হবে — পরে Phase 3-এ সেগুলো বন্ধ করা যাবে।

**Task serial**
`ACC-P1-08 — Accounting period কাঠামো` · Feature

**Depends on**
- `ACC-P1-03` (`fiscal_year_start` setting)
- OD-4 (আর্থিক বছরের নামকরণ: শুরুর বছর ধরে, Blueprint-এর ধরে নেওয়া)

**Relation: আগের task**
`P1-03` settings store দিয়েছে। `fiscal_year_start` না থাকলে period generator `SET_INCOMPLETE` দেয় — checklist S01-ও লাল থাকে।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P1-09` (Journal table): `acc_journals.period_id` composite FK `(company_id, period_id)` → `acc_periods (company_id, id)`। **তাই `uq_acc_periods_company_id` বাদ দেওয়া যাবে না।**
- `P2-03` (Validator): `PeriodResolver::forDate`।
- `P2-12` (Post), `P3-11` (Period বন্ধ), `P3-12` (PeriodGuard)।

**Related tables / schema**

**`acc_periods`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `fiscal_year` | SMALLINT UNSIGNED | না | শুরুর বছর |
| `period_no` | TINYINT UNSIGNED | না | 1 থেকে 12 |
| `period_key` | VARCHAR(10) | না | `YYYY-MM` (সেই মাসের শুরুর তারিখের) |
| `start_date`, `end_date` | DATE | না |  |
| `status` | VARCHAR(12) | না | Default `open`; `soft_closed`, `closed` |
| `closed_at`, `closed_by` |  | হ্যাঁ | Phase 3-এ ব্যবহার |
| `reopened_at`, `reopened_by` |  | হ্যাঁ | Phase 3-এ ব্যবহার |
| `version` | INT UNSIGNED | না |  |
| `created_at`, `updated_at` |  | না |  |

Constraint ও index: `uq_acc_periods_company_key (company_id, period_key)`, `uq_acc_periods_company_year_no (company_id, fiscal_year, period_no)`, **`uq_acc_periods_company_id (company_id, id)`**, `idx_acc_periods_company_dates (company_id, start_date, end_date)`, `ck_acc_periods_dates` (`end_date >= start_date`), `ck_acc_periods_status`।

**Business rules**
- Period-এর তারিখ ফাঁকহীন ও overlap-হীন।
- যে বছরে post হওয়া লেনদেন আছে, তার period-এর তারিখ বদলানো যাবে না (`SET_LOCKED`-এর মতো)।
- FY শুরু মাসের ১ তারিখ না হলে (যেমন ১৫ জুলাই) period-ও সেই তারিখে শুরু ও তার আগের দিন শেষ।
- Edge: leap year; ২৯ ফেব্রুয়ারিতে শুরু হওয়া FY (setting-এ যাচাই করে `SET_INVALID`); বছরের মাঝে setting বদল (lock আটকায়)।

**DB flow**
- **তৈরি:** `PeriodGenerator::ensureYear(companyId, fiscalYear)`: (1) `fiscal_year_start` পড়া (না থাকলে `SET_INCOMPLETE`); (2) ১২টা মাসের সীমা হিসাব; (3) transaction-এ `INSERT ... ON DUPLICATE KEY` (বা `INSERT IGNORE`) দিয়ে সব period; আবার চালালে একই ফল।
- **খোঁজা:** `PeriodResolver::forDate(companyId, date)`: `SELECT ... WHERE company_id = ? AND start_date <= ? AND end_date >= ?`; না পেলে `JRN_DATE_INVALID`।

**Job**
মাসে একবার চলে, পরের আর্থিক বছরের period আগে থেকে তৈরি করে রাখে (idempotent, `AccountingJob` base দিয়ে company context সহ; `P1-12` লাগে)।

**Task scope**
Migration, generator, resolver, scheduler job, test।

**Error**
`SET_INCOMPLETE`, `JRN_DATE_INVALID`

**Audit**
`accounting.period.generated`

**Acceptance**
নতুন AC1.12: period ফাঁকহীন ও overlap-হীন

**Test**
বিভিন্ন FY শুরু (জানুয়ারি, জুলাই, ১৫ জুলাই); আবার চালানো; leap year; তারিখ খোঁজা।

**Deliverable**
Migration, generator, resolver, job।

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- AC1.12 সবুজ (`PeriodGeneratorTest`)।

**Architecture constraints**
- Period scheduler job অবশ্যই `P1-12`-এর `AccountingJob` base — `companyId` ছাড়া queue-তে যাবে না।

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

---

## ACC-P1-09 — Journal-এর ভিত্তি table ও idempotency

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service)
Title
Journal-এর ভিত্তি table ও idempotency — Backend
Branch Name
`feature/accounting-journal-foundation-backend`
View Order
15
Description
**Summary**
এই কাজটায় সব journal-এর জন্য header, line, attachment table আর API idempotency বসবে — Phase 1-এ মূলত opening-এর জন্য, কিন্তু নকশা Phase 2 ও 4-এর পুরো journal flow-এর জন্য।

**Task serial**
`ACC-P1-09 — Journal-এর ভিত্তি table ও idempotency` · Feature

**Depends on**
- `ACC-P1-06` (CoA: `uq_acc_accounts_company_id`)
- `ACC-P1-08` (Period: `uq_acc_periods_company_id`)
- `ACC-P0-05` (DB guard ও ledger-উৎস সিদ্ধান্ত)

**Relation: আগের task**
`P1-06` (`uq_acc_accounts_company_id`) আর `P1-08` (`uq_acc_periods_company_id`) composite FK-র লক্ষ্য unique key দিয়েছে। `P0-05` spike-এ generated column আর constraint নকশা ঠিক হয়েছে — সেটা এখানে বাস্তবায়ন।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P1-10` (DB guard): এই task-এর table-এ trigger বসায়।
- `P1-11` (Opening balance): `acc_journals`, `acc_journal_lines` ব্যবহার।
- পুরো Phase 2। Phase 4-এর `uq_acc_journals_source_active` এই task-এ বসে।
- **Probe-এর আসল implementation:** `P1-03`-এর `PostedJournalProbe` আর `P1-06`-এর `PostedLineProbe`-এর stub এই task বদলে আসল query দেয়।

**Related tables / schema**

**`acc_journals`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `journal_type` | VARCHAR(20) | না | `manual`, `opening`, `reversal`, `adjustment`, `event_generated` |
| `status` | VARCHAR(16) | না | Default `draft`; `draft`, `submitted`, `approved`, `rejected`, `posted`, `cancelled`, `reversed` |
| `voucher_no` | VARCHAR(40) | হ্যাঁ | Post-এর সময় বসে |
| `journal_date` | DATE | না |  |
| `period_id` | FK | হ্যাঁ | Post-এর সময় বসে |
| `narration` | VARCHAR(1000) | হ্যাঁ |  |
| `reference_no` | VARCHAR(100) | হ্যাঁ |  |
| `currency` | CHAR(3) | না | তৈরির সময়ের base currency |
| `total_debit`, `total_credit` | DECIMAL(20,4) | না | Default 0, service রক্ষণাবেক্ষণ করে |
| `source_type`, `source_id`, `source_event` | VARCHAR | হ্যাঁ | Phase 4-এর জন্য |
| `reverses_journal_id`, `adjusts_journal_id` | FK (নিজের) | হ্যাঁ |  |
| `reversal_reason` | VARCHAR(500) | হ্যাঁ |  |
| `submission_no` | INT UNSIGNED | না | Default 0 |
| `submitted_at`, `submitted_by`, `approved_at`, `posted_at`, `posted_by`, `cancelled_at` |  | হ্যাঁ |  |
| `reversed_at`, `reversed_by_journal_id` |  | হ্যাঁ |  |
| `opening_active` | TINYINT (generated, stored) | হ্যাঁ | `journal_type = 'opening'` আর status `draft/submitted/approved/posted`-এর একটা হলে 1, নইলে null |
| `version` | INT UNSIGNED | না |  |
| `created_by`, `updated_by`, `created_at`, `updated_at`, `deleted_at` |  |  |  |

Constraint ও index:
- `uq_acc_journals_company_id (company_id, id)`
- `uq_acc_journals_company_voucher (company_id, voucher_no)` (null অনেক চলে)
- **Source unique (সংশোধিত, `P4-06` থেকে):** সরাসরি তিন column-এ নয়। একটা generated stored column `source_active` (`CASE WHEN source_type IS NOT NULL AND status <> 'cancelled' THEN CONCAT(source_type, ':', source_id, ':', source_event) END`) আর `uq_acc_journals_source_active (company_id, source_active)`। এতে Event-এর Draft বাতিল হলে নতুন Draft বানানো যায়, কিন্তু একসাথে দুটো সক্রিয় নয়।
- `uq_acc_journals_opening (company_id, opening_active)` (R1.11 DB স্তরে)
- `fk_acc_journals_reverses (company_id, reverses_journal_id)` ও `fk_acc_journals_adjusts` (composite, নিজের table-এ)
- `fk_acc_journals_period (company_id, period_id)` → `acc_periods (company_id, id)`
- `ck_acc_journals_status`, `ck_acc_journals_type`
- `ck_acc_journals_posted`: `status NOT IN ('posted','reversed')` অথবা (`voucher_no`, `posted_at`, `period_id` সব NOT NULL)
- `ck_acc_journals_reversal`: `reverses_journal_id IS NULL` অথবা `journal_type = 'reversal'` (অনুরূপ adjusts)
- `ck_acc_journals_balanced_when_posted`: `status NOT IN ('posted','reversed')` অথবা `total_debit = total_credit`
- Index: `idx_acc_journals_company_status_date`, `idx_acc_journals_company_ref`, `idx_acc_journals_company_type_status`

**`acc_journal_lines`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `journal_id` | FK | না |  |
| `line_no` | SMALLINT UNSIGNED | না |  |
| `account_id` | FK | না |  |
| `debit`, `credit` | DECIMAL(20,4) | না | Default 0 |
| `line_narration` | VARCHAR(500) | হ্যাঁ |  |
| `cost_center_id` | FK (Configuration) | হ্যাঁ |  |
| `created_at`, `updated_at` |  | না |  |

Constraint: `uq_acc_jl_journal_line (journal_id, line_no)`; `fk_acc_jl_journal (company_id, journal_id)` → `acc_journals` `ON DELETE RESTRICT`; `fk_acc_jl_account (company_id, account_id)` → `acc_accounts`; `ck_acc_jl_amounts`: `debit >= 0 AND credit >= 0 AND ((debit > 0 AND credit = 0) OR (credit > 0 AND debit = 0))`; `idx_acc_jl_company_account (company_id, account_id, journal_id)`, `idx_acc_jl_journal (journal_id)`।

**`acc_journal_attachments`:** `id`, `company_id`, `journal_id`, `storage_path`, `original_name`, `mime_type`, `size_bytes`, `uploaded_by`, `created_at`। FK composite journal-এ; Index `(company_id, journal_id)`।

**`acc_idempotency_keys`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `idem_key` | VARCHAR(100) | না | Client-এর key |
| `endpoint` | VARCHAR(120) | না | Method ও route |
| `request_hash` | CHAR(64) | না | Request body-র hash |
| `state` | VARCHAR(12) | না | `processing`, `done` |
| `response_status` | SMALLINT | হ্যাঁ |  |
| `response_body` | JSON | হ্যাঁ |  |
| `created_by` | FK users | না |  |
| `created_at`, `expires_at` | TIMESTAMP | না |  |

Constraint: `uq_acc_idem (company_id, idem_key, endpoint)`; Index: `idx_acc_idem_expires (expires_at)`।

**Business rules**
- Posted হওয়া journal বা line বদলানো এই task-এ শুধু schema-এ ঠিক করা; **প্রয়োগ `P1-10`-এ।**

**DB flow: idempotency middleware**
1. `Idempotency-Key` header পড়া (write endpoint-এ বাধ্যতামূলক চিহ্নিতগুলোতে না থাকলে 400)।
2. `INSERT` row `state = 'processing'`। Unique ভাঙলে: আগের row পড়া। `request_hash` মিললে আর `done` হলে সংরক্ষিত response ফেরত। `processing` হলে 409 "চলছে"। `request_hash` না মিললে `ACC_IDEMPOTENCY_CONFLICT`।
3. আসল কাজ চলে।
4. Response সংরক্ষণ ও `state = 'done'`।
5. Controller ব্যর্থ হলে (5xx) row মোছা হয়, আবার চেষ্টা করা যায়।

**Job:** দৈনিক, `expires_at` পেরোনো row মোছা; retention 7 দিন।

**Task scope**
চারটা migration, model, idempotency middleware ও cleanup job, দুটো probe-এর আসল implementation, constraint test, idempotency test।

**Error**
`ACC_IDEMPOTENCY_CONFLICT` (409)

**Acceptance**
AC1.1 (composite FK), AC1.9-এর ভিত্তি

**Test**
Constraint গুলোর প্রতিটা **সরাসরি SQL দিয়ে ভাঙার চেষ্টা**: অন্য company-র account, দুটো opening, দুটো একই source, Debit আর Credit দুটোই, ঋণাত্মক। Idempotency-র চারটা পথ।

**Deliverable**
চারটা migration, model, idempotency middleware ও cleanup job, দুটো probe-এর আসল implementation।

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- AC1.1 সবুজ; `ConstraintTest` সবুজ।
- `P1-03`-এর `SET_LOCKED` এখন আসল probe দিয়ে কাজ করে (posted journal থাকলে)।
- MySQL-এ test (SQLite-এ generated column ও composite FK আলাদা আচরণ করে)।

**Architecture constraints**
- Journal status transition API এই task-এ নয় — Phase 2 `PostingService`/state machine।
- Posted row delete/`CASCADE` নিষিদ্ধ (guard `P1-10`-এ)।

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

---

## ACC-P1-10 — DB স্তরের অপরিবর্তনীয়তা (guard)

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema, trigger)
Title
DB স্তরের অপরিবর্তনীয়তা (guard) — Backend
Branch Name
`feature/accounting-db-guard-triggers`
View Order
16
Description
**Summary**
এই কাজটায় posted journal আর line যেন application ছাড়াও সরাসরি SQL দিয়ে বদলানো বা মোছা না যায় — DB trigger দিয়ে guard।

**Task serial**
`ACC-P1-10 — DB স্তরের অপরিবর্তনীয়তা (guard)` · Feature

**Depends on**
- `ACC-P1-09`
- `ACC-P0-05` (OD-13: trigger-ভিত্তিক বিকল্প (ক) ধরে লেখা)

**Relation: আগের task**
`P1-09` journal table দিয়েছে। `P0-05` spike-এ যে prototype প্রমাণিত, trigger guard-এর নকশা **হুবহু সেটাই** — নতুন করে ভাবার সুযোগ নেই।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P2-01` (state machine): trigger-এর অনুমোদিত পথ (`approved` থেকে `posted`) ধরে চলে।
- `P2-12` (Post): `posted_hash` এই task-এর column-এ বসায়।
- `P2-04`, `P2-05`, `P3-11`, `P4-09`, `P3-13`, `P5-06` এই task-এর **`Schema::appendOnly('table')` helper** ব্যবহার করে append-only table বানায়।
- `P3-13` (RC9): `posted_hash` মিলায়।

**Related tables / schema**

**নিয়ম যা trigger প্রয়োগ করবে:**

| Table | কাজ | কখন অনুমোদিত |
| --- | --- | --- |
| `acc_journals` | UPDATE | `OLD.status` যদি `posted` বা `reversed` না হয়, সবকিছু। `OLD.status = 'posted'` আর `NEW.status = 'reversed'` হলে শুধু `status`, `reversed_at`, `reversed_by_journal_id`, `updated_at`, `updated_by`, `version` বদলাতে পারবে, আর কোনো column নয় |
| `acc_journals` | DELETE | `OLD.status` `posted` বা `reversed` না হলে |
| `acc_journal_lines` | INSERT, UPDATE, DELETE | Parent journal-এর status `draft` বা `rejected` হলে |
| `acc_journal_attachments` | INSERT, UPDATE, DELETE | Parent journal-এর status `draft` বা `rejected` হলে |
| শুধু-যোগ-হয় table | UPDATE, DELETE | কখনো নয় |

**Append-only table:** এই Phase-এ `acc_settings_history`। পরের Phase-এ `acc_journal_status_history`, `acc_approval_actions`, `acc_finance_event_actions`, `acc_period_events` (সিদ্ধান্তের পর), `acc_reconciliation_runs`, `acc_enablement_log`। প্রতিটার জন্য একটা সাধারণ migration helper `Schema::appendOnly('table')`, যাতে প্রতিবার হাতে trigger লিখতে না হয়।

**Trigger কাঠামো (সংক্ষিপ্ত, পুরো column তালিকা implementation-এ):**

```sql
CREATE TRIGGER trg_acc_journals_bu BEFORE UPDATE ON acc_journals
FOR EACH ROW
BEGIN
  IF OLD.status IN ('posted','reversed') THEN
    IF NOT (
      OLD.status = 'posted' AND NEW.status = 'reversed'
      AND NEW.reversed_at IS NOT NULL AND NEW.reversed_by_journal_id IS NOT NULL
      AND NEW.company_id <=> OLD.company_id
      AND NEW.voucher_no <=> OLD.voucher_no
      AND NEW.journal_date <=> OLD.journal_date
      AND NEW.total_debit <=> OLD.total_debit
      AND NEW.total_credit <=> OLD.total_credit
      /* বাকি সব column একইভাবে */
    ) THEN
      SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'JRN_IMMUTABLE';
    END IF;
  END IF;
END
```

**Business rules**
- নতুন column `acc_journals.posted_hash CHAR(64) NULL` (expand migration)। `P2-12` post-এর সময় journal আর তার line-এর canonical SHA-256 এখানে বসায়।
- Application `SQLSTATE 45000` আর বার্তা `JRN_IMMUTABLE` ধরে error `JRN_IMMUTABLE` (409); `APPEND_ONLY` ধরে নতুন code `ACC_APPEND_ONLY` (409)।
- **সতর্কতা:** MySQL-এ binary log চালু থাকলে trigger বানাতে `log_bin_trust_function_creators` বা সংশ্লিষ্ট অনুমতি লাগে। `P0-02`-এর ১২ নম্বর উত্তর অনুযায়ী deployment-এ কী করতে হবে ঠিক হয়। Trigger বানানো না গেলে বিকল্প: application guard + `posted_hash` দিয়ে রাতের integrity check (`P3-13`)। এটা দুর্বল, তাই OD-13-এ client ও tech lead-কে স্পষ্ট জানাতে হবে।
- **Rollback:** `down()` trigger মুছে। এটা বিপজ্জনক (guard সরে যায়), তাই migration-এর মন্তব্যে স্পষ্ট লেখা আর **production-এ `down()` চালানো নিষেধ** (runbook)।

**Task scope**
journals trigger, lines trigger, attachments trigger, append-only helper, `posted_hash` column, error mapping, test, fallback ও সতর্কতার নথি।

**Error**
`JRN_IMMUTABLE` (409), `ACC_APPEND_ONLY` (নতুন, 409)

**Acceptance**
AC1.9, পরে AC2.5

**Test (সরাসরি SQL দিয়ে)**
- Posted journal-এ `UPDATE` ও `DELETE` ব্যর্থ।
- `posted` থেকে `reversed` শুধু অনুমোদিত column সহ চলে; বাকি column বদলালে ব্যর্থ।
- Draft journal ও line স্বাভাবিক চলে।
- `submitted` অবস্থায় line বদলানো ব্যর্থ।
- Append-only table-এ `UPDATE`/`DELETE` ব্যর্থ।
- Transaction-এর ভেতরে `approved` থেকে `posted` চলে।

**Deliverable**
Migration ও helper, trigger, error mapping, test।

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- AC1.9 সবুজ (`GuardTriggerTest`, MySQL-এ)।
- Runbook-এর জন্য `down()` সতর্কতা লেখা আছে (`P1-14` নেবে)।

**Architecture constraints**
- Trigger deploy না গেলে চুপচাপ fallback নয় — OD-13-এ client/tech lead লিখিত স্বীকৃতি।
- Posted data ঠিক করার পথ reversal/adjustment; production-এ trigger `down()` নয়।

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

---

## ACC-P1-11 — Opening balance Draft

Project
ERPFlow
Module
Accounting Module
Type
Feature (service + api + ui)
Title
Opening balance Draft — Backend ও Frontend
Branch Name
`feature/accounting-opening-balance`
View Order
17
Description
**Summary**
এই কাজটায় opening balance হাতে ঢোকানোর গ্রিড থাকবে — Debit/Credit না মিললে সংরক্ষণই হবে না (transaction rollback)।

**Task serial**
`ACC-P1-11 — Opening balance Draft` · Feature

**Depends on**
- `ACC-P1-07` (CoA API, account picker)
- `ACC-P1-09` (journal table)
- `ACC-P1-10` (DB guard)
- `ACC-P1-12` (error, audit, tenancy)
- OD-9 (cutover তারিখ setting-এ থাকে; বসানো Admin-এর কাজ)

**Relation: আগের task**
`P1-07` leaf/active account picker; `P1-09` `acc_journals`/`acc_journal_lines` (এক company-তে এক opening — `uq_acc_journals_opening`); `P1-10` Draft-এ line লেখা/`DELETE` অনুমোদন; `P1-12` error, audit, tenancy।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P2-14` (Opening post): এই Draft সাধারণ Submit, approve, post পথে যায়।
- `P1-05`-এর S10: opening post হলে সবুজ।
- `P5-02` (Cutover মহড়া): এই গ্রিডে ঢোকানো হয়।
- **খোলা প্রশ্ন OD-23:** opening-এ Revenue বা Expense account থাকতে পারবে কি। Pack ধরে নিয়েছে (ক) "বিশেষ আচরণ নেই", কিন্তু Accountant-কে এই task শুরুর আগে সিদ্ধান্ত দিতে হবে। (খ) হলে এই task-এর validator বাড়বে।

**Related tables / schema**
কোনো নতুন table নেই। `acc_journals` (`journal_type = 'opening'`) ও `acc_journal_lines`। একটা company-র একটাই সক্রিয় opening journal, `uq_acc_journals_opening` (`P1-09`) DB স্তরে নিশ্চিত করে।

**Business rules (Blueprint ৬.৫, R1.7 থেকে R1.11)**
- **কোনো আপনা-আপনি মেলানোর line (যেমন "Opening Balance Equity" দিয়ে পার্থক্য ঢাকা) তৈরি হয় না।** না-মেলা balance চুপচাপ গ্রহণ নয়।
- প্রতিটা account leaf ও active ও এই company-র।
- Debit বা Credit একটাই; শূন্য amount-এর line বাদ।
- Draft ছাড়া অন্য অবস্থায় (posted হলে) সংশোধন Phase 2-এর Adjustment-এ।

**DB flow: `PUT` (এক transaction)**
1. Idempotency middleware।
2. transaction।
3. `opening_cutover_date` setting পড়া (না থাকলে `SET_INCOMPLETE`)।
4. এই company-র সক্রিয় opening journal `SELECT ... WHERE company_id = ? AND opening_active = 1 FOR UPDATE`। না থাকলে নতুন `draft` journal `INSERT` (`journal_date` = cutover, `currency` = `base_currency`)। থাকলে আর `status` `draft`-এ না হলে `JRN_STATE_INVALID`।
5. `version` মেলানো, না মিললে `JRN_CONFLICT`।
6. সব পুরোনো line `DELETE` (Draft, তাই guard অনুমোদন করে)।
7. নতুন line `INSERT`: শূন্য amount-এর line বাদ।
8. যাচাই: account leaf ও active ও এই company-র (`JRN_ACCOUNT_INVALID`); amount precision-এর মধ্যে (`JRN_LINE_INVALID`); Debit বা Credit একটাই।
9. `total_debit`, `total_credit` হিসাব ও `UPDATE`।
10. `total_debit <> total_credit` হলে **পুরো transaction rollback** আর `JRN_UNBALANCED` (`details.difference` সহ)।
11. `version + 1`।
12. Audit।
13. commit।

**DB flow: বাতিল**
`DELETE /accounting/opening-balance`: Draft হলে `status = 'cancelled'`, `cancelled_at`। `opening_active` তখন null হয়, নতুন Draft বানানো যায়।

**Contract**
- `GET /accounting/opening-balance` (journal, line, মোট, পার্থক্য)
- `PUT` (পুরো line তালিকা, `version`, idempotency key)
- `DELETE`

**UI**
1. Account-এর গ্রিড: leaf ও active account, প্রতিটায় Debit ও Credit ঘর, অনুসন্ধান, type অনুযায়ী ভাগ।
2. চলতি মোট Debit, মোট Credit আর পার্থক্য; না মিললে লাল ব্যানার, সংরক্ষণ বাটন নিষ্ক্রিয়।
3. Cutover তারিখ দেখানো (সম্পাদনা এখানে নয়, setting-এ)।
4. অবস্থা দেখানো (Draft, আর Phase 2-এর পর Submitted ইত্যাদি)।

**Permission**
লেখা: `accounting.journal.create`। দেখা: `accounting.journal.view`।

**Error**
`SET_INCOMPLETE`, `JRN_ACCOUNT_INVALID`, `JRN_LINE_INVALID`, `JRN_UNBALANCED`, `JRN_STATE_INVALID`, `JRN_CONFLICT`

**Audit**
`accounting.journal.draft_created`, `.draft_updated`, `.cancelled`

**Acceptance**
AC1.3

**Test**
মিললে সংরক্ষণ; না মিললে rollback ও পার্থক্য; inactive ও group account; অন্য company-র account; দ্বিতীয় opening Draft বানানো (DB আটকায়); বাতিলের পর নতুন; একই idempotency key দুইবার; দুজন একসাথে।

**Deliverable**
Service, API, গ্রিড UI।

**Sub-tasks**
service, `PUT`, `GET`, `DELETE`, গ্রিড UI, মোট ও পার্থক্যের অংশ, test

**Definition of done**
- Phase 1-এর সাধারণ DoD পূরণ।
- AC1.3 সবুজ (`OpeningBalanceTest`)।
- না-মেলা balance কখনো DB-তে থাকে না (rollback প্রমাণিত)।

**Architecture constraints**
- Opening journal শুধু service layer — raw SQL insert নয়। Post workflow Phase 2 (`P2-14`)।
- System কখনো "Opening Balance Equity" দিয়ে পার্থক্য auto-fill করবে না (R1.7)।

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

---

## ACC-P1-13 — Phase 1-এর test suite ও regression

Project
ERPFlow
Module
Accounting Module
Type
Test
Title
Phase 1-এর test suite ও regression
Branch Name
`feature/accounting-phase1-tests`
View Order
18
Description
**Summary**
এই কাজটায় Phase 1-এর সব Acceptance Criteria (AC1.1–AC1.12) স্বয়ংক্রিয় test দিয়ে প্রমাণিত হবে — বিদ্যমান ERPFlow suite-ও সবুজ থাকতে হবে।

**Task serial**
`ACC-P1-13 — Phase 1-এর test suite ও regression` · Test

**Depends on**
- `ACC-P1-01` থেকে `ACC-P1-12` সব

**Relation: আগের task**
প্রতিটা Phase 1 task নিজের unit/feature test লিখেছে। এখানে AC1.1–AC1.12 ধরে suite এক জায়গায় জোড়া, ফাঁক ধরা, আর পুরো ERPFlow regression সবুজ রাখা।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `ACC-P1-14` (Runbook ও merge gate)
- Phase 2 শুরু: **Phase 1 test সবুজ না হলে `P2-01` শুরু করা যাবে না।**

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

**AC থেকে test**

| AC | কী প্রমাণ | Test class (প্রস্তাব) |
| --- | --- | --- |
| AC1.1 | অন্য company-র data দেখা বা বদলানো যায় না (API, DB composite FK) | `CompanyIsolationTest` |
| AC1.2 | প্রতি company-র আলাদা CoA | `CoaRulesTest` |
| AC1.3 | Opening না মিললে সংরক্ষণ হয় না | `OpeningBalanceTest` |
| AC1.4 | Lock নিয়ম | `SettingsLockTest` |
| AC1.5 | Checklist অসম্পূর্ণ হলে চালু হয় না | `SetupChecklistTest` |
| AC1.6 | Permission matrix | `PermissionMatrixTest` |
| AC1.7 | সব বদল audit-এ | `AuditCoverageTest` |
| AC1.8 | Job-এ company context | `CompanyContextJobTest` |
| AC1.9 | DB guard | `GuardTriggerTest` |
| AC1.10 | Flag বন্ধ থাকলে কিছু হয় না | `FeatureFlagTest` |
| AC1.11 | বিদ্যমান ERPFlow অক্ষত | সম্পূর্ণ বিদ্যমান suite |
| AC1.12 | Period ফাঁকহীন | `PeriodGeneratorTest` |

আরও: `ConstraintTest` (`P1-09`-এর প্রতিটা constraint ভাঙার চেষ্টা), `IdempotencyTest`, `ErrorContractTest`।

**Test data:** Factory-তে দুই company, প্রতিটায় পাঁচ role-এর user, একটায় একই code-এর account।

**Business rules**
- **CI-তে MySQL-ভিত্তিক job চালাতে হবে।** SQLite-এ trigger, generated column, composite FK আলাদা আচরণ করে, তাই এই test গুলো MySQL ছাড়া মূল্যহীন।
- Golden suite (Blueprint ১২.২)-এর প্রথম ৪টা সবুজ হতে হবে।

**Task scope**
প্রতিটা test class আলাদা, factory, CI job, test report।

**Deliverable**
সব test class, factory, CI configuration, test report।

**Definition of done**
- AC1.1 থেকে AC1.12 সবুজ।
- Golden suite-এর প্রথম ৪টা সবুজ।
- বিদ্যমান ERPFlow suite সবুজ।
- MySQL CI job ব্যবহার হয়েছে।

**Architecture constraints**
- Guard trigger, generated column, composite FK test **MySQL CI-তে** — SQLite-এ মূল্যহীন।

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

---

## ACC-P1-14 — Runbook ও merge gate

Project
ERPFlow
Module
Accounting Module
Type
Operations (`ops`)
Title
Runbook ও merge gate
Branch Name
`feature/accounting-phase1-runbook`
View Order
19
Description
**Summary**
এই কাজটায় Phase 1 নিরাপদে `development`-এ merge করার runbook আর gate থাকবে (flag বন্ধ), Phase 2 শুরুর আগে accountant-এর লিখিত মতামত লাগবে।

**Task serial**
`ACC-P1-14 — Runbook ও merge gate` · Ops

**Depends on**
- `ACC-P1-13`

**Relation: আগের task**
`P1-13` AC সবুজ প্রমাণ করেছে। এখন runbook, merge gate checklist আর accountant demo/মতামত — তারপর Phase 2।

**Relation: পরের task (কে অপেক্ষা করছে)**
Phase 2 শুরু (`P2-01`)। `P2-17` এই runbook হালনাগাদ করে।

**Business rules**
- Phase 1 exit criteria (Blueprint ৬.১০) পূরণ।
- `development`-এ push মানে production deploy, তাই merge gate কঠোর।

**Task scope**
1. **Runbook-এর প্রথম খসড়া:** module কী, flag, Setup Checklist, trigger-এর সতর্কতা (`P1-10`), `down()` চালানো নিষেধ, সাধারণ সমস্যা।
2. **Merge gate checklist:** সব flag Default বন্ধ প্রমাণ; প্রতিটা migration পিছনে সামঞ্জস্যপূর্ণ প্রমাণ; বিদ্যমান suite সবুজ; Project/Payroll/Asset/Commissioning-এর code অপরিবর্তিত (diff দিয়ে প্রমাণ)।
3. **Accountant demo:** নমুনা company-তে CoA, settings আর opening Draft দেখানো; মতামত লিখে রাখা।

**Deliverable**
Runbook খসড়া, merge gate-এর সই করা checklist, accountant-এর মতামত নথি।

**Definition of done**
- সই করা merge checklist আছে।
- Accountant-এর লিখিত মতামত আছে।
- Phase 1 exit criteria (Blueprint ৬.১০) পূরণ।

**Architecture constraints**
- Phase 1 merge শুধু সব accounting flag default `false` রেখে — `P0-03` CI check সবুজ।

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