# Accounting Module — Phase 4 Tasks (Finance Event Intake ও Asset Pilot)

> এখানকার প্রতিটি task আলাদা করে একজন কাজ করতে পারবে।
> Phase 4-এর শেষে Asset module-এ write-off event হলে Accounting-এ একটা Draft Journal নিজে থেকে তৈরি হয় — কিন্তু কিছুই নিজে নিজে post হয় না; Submit, approve, post আগের মতোই মানুষের হাতে।
> ক্রম (master sequence অনুযায়ী): অনুমোদন ও সিদ্ধান্ত → inbox → Asset reader → consumer → mapping → Draft তৈরি → তিন স্তরের flag → Asset-এ ফেরত → approval ও SoD → ops screen → RC8 → test → runbook।
> Estimate, Owner, Sprint ইচ্ছে করে খালি রাখা হয়েছে — Phase 3 exit ও `ACC-P4-01` শেষ হলে ভর্তি হবে।

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

- Phase 4 শুরু হয় শুধু Phase 3 পুরো সবুজ (`ACC-P3-14`), accountant-এর সই করা নমুনা report pack (`ACC-P3-15`) আর client-এর লিখিত change request অনুমোদনের (`ACC-P4-01`, OD-18) পরে। এর আগে কোনো Phase 4 code লেখা যাবে না।
- 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(n)` + `CHECK`, MySQL enum নয়। বদলানো যায় এমন row-এ `version INT UNSIGNED NOT NULL DEFAULT 1`; update-এ `WHERE version = ?` আর `version = version + 1`।
- `ON DELETE RESTRICT` ডিফল্ট; লেজারের table-এ `CASCADE` নয়। Posted বা লেজারের row কখনো মোছা হয় না।
- **account_id, journal_id বা inbox_id রাখা table-এ composite FK** `(company_id, account_id)` / `(company_id, journal_id)` / `(company_id, inbox_id)`, একা `*_id`-র FK নয়।
- Migration: প্রতি table-এ আলাদা, প্রয়োগ হওয়া migration সম্পাদনা নয়, পিছনে সামঞ্জস্যপূর্ণ (আগে যোগ, পরে বদল, শেষে মোছা), `down()` নিরাপদ। Append-only table (`acc_finance_event_actions` ইত্যাদি) `Schema::appendOnly('table')` helper (`ACC-P1-10`) দিয়ে।
- **Phase 4-এ কিছুই নিজে post হয় না (AC4.4):** Consumer বা `DraftBuilder` থেকে `PostingService`-এ কোনো code-পথ থাকবে না। Event থেকে শুধু Draft তৈরি হয়; Submit, approve, post সাধারণ মানুষের পথে (`ACC-P2-08`, `ACC-P2-12`)।
- **Asset module-এর code Phase 4-এ অপরিবর্তিত**, একমাত্র ব্যতিক্রম `ACC-P4-07`-এর ছোট পরিবর্তন (`FinanceEventAcknowledger`-এর Asset-দিকের বাস্তবায়ন, আর OD-21 (ক) হলে একটা migration ও writer-এর পরিবর্তন)। Accounting Asset-এর table শুধু **পড়ে** (`ACC-P4-04`), লেখে না, শুধু Asset-এর নিজের acknowledger দিয়ে `status` বদলায়।
- Lock-এর সর্বজনীন ক্রম (Pack Section ৯.১): `acc_journals` → `acc_approval_requests` → `acc_accounts` (`FOR SHARE`, `id` ছোট থেকে বড়) → `acc_periods` → `acc_voucher_counters` → দ্বিতীয় journal। Phase 4-এর নতুন code এই ক্রম ভাঙবে না।
- তিন স্তরের flag (`accounting.enabled`, `event_intake.enabled`, `event_intake.sources.asset_write_off`) সব company-তে **Default বন্ধ**। Flag বন্ধ রেখে merge নিরাপদ; PR-এ "flag Default বন্ধ প্রমাণিত" টিক।
- Phase 4-এ যোগ হওয়া নতুন error code: `EVT_STATE_INVALID` (409), `EVT_PROCESSING_ERROR` (তথ্য, inbox `failed`), `SET_STALE_REQUEST` (409, `ACC-P1-04`-এর সাথে ভাগ করা)। সব code `AccountingError` enum-এ (`ACC-P1-12`), অন্য কোথাও নয়।
- **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 দিয়ে; Job হলে `AccountingJob` base (company context, `ACC-P1-12`); concurrency ও constraint test **MySQL-এ** (SQLite-এ নয়); বিদ্যমান ERPFlow regression সবুজ; Project/Payroll/Asset/Commissioning-এর code অপরিবর্তিত (`ACC-P4-07` ছাড়া)।
- Blueprint-এর `R4.x`, `R5.x`, `AC4.x`, `AC5.x` নম্বর Pack-এ যেভাবে উল্লেখ আছে ঠিক সেভাবে রাখা হয়েছে; Blueprint নিজে এই task-এর সাথে নেই, তাই AC-র অর্থ `ACC-P4-12`-এর AC তালিকা থেকে নেওয়া।

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

```
P3-14 + P3-15 ─► P4-01 ─┬─► P4-02 ─┬─► P4-03 ─┐        (P4-04, P1-12, P1-01-ও লাগে)
                        │          └─► P4-05 ─┼─► P4-06 ─┬─► P4-07   (P4-01-ও লাগে)
                        └─► P4-04 ─► P4-03    │          ├─► P4-08   (P2-08, P2-10-ও লাগে)
                                              │          ├─► P4-09   (P4-02, P4-03, P4-05-ও লাগে)
                                              │          └─► P4-10   (P3-13-ও লাগে)
        P4-05 + P1-01 + P1-04 + P1-05 ─► P4-11     (P4-03 চালানোর শর্ত)
        P4-02 থেকে P4-11 সব ─► P4-12 ─► P4-13 ─► Phase 5
```

**Build order (master sequence ক্রম 33 থেকে 40):** P4-01 → P4-02, P4-04 → P4-03, P4-05 → P4-06, P4-11 → P4-07, P4-08, P4-09 → P4-10 → P4-12 → P4-13। একই ক্রম-গ্রুপের card একসাথে চলতে পারে।

---

## ACC-P4-01 — Change request অনুমোদন ও সিদ্ধান্ত বন্ধ

Project
ERPFlow
Module
Accounting Module
Type
Governance (`gov`)
Title
Change request অনুমোদন ও সিদ্ধান্ত বন্ধ
Branch Name
কোনো branch নেই (নথি-ভিত্তিক কাজ)
View Order
53
Description
**Summary**
Asset write-off থেকে Draft Journal বানানোর pilot-এর আগে client-এর লিখিত change request আর ছয়টা সিদ্ধান্ত (OD-18, OD-3, OD-10, OD-11, OD-21, OD-22) নথিতে বন্ধ করে নাও। অনুমোদন ছাড়া Phase 4-এর কোনো code লেখা বা merge করা যাবে না।

**Task serial**
`ACC-P4-01 — Change request অনুমোদন ও সিদ্ধান্ত বন্ধ` · Governance

**Depends on**
- `ACC-P3-15` (accountant-এর সই করা নমুনা report pack; Phase 3 exit criteria পূরণ)
- OD: `OD-18`, `OD-3`, `OD-10`, `OD-11`, `OD-21`, `OD-22` (এই task নিজেই এগুলো বন্ধ করে)

**Relation: আগের task**
- `P3-15` দিয়েছে: সই করা নমুনা report pack আর বন্ধ হওয়া OD-র তালিকা (OD-1, 2, 5, 14, 15, 16, 20, 23)। অর্থাৎ ledger আর report-এর হিসাব accountant মেনে নিয়েছে। এই ভিত্তি ছাড়া "event থেকে journal" যোগ করা অর্থহীন, কারণ event-এর ফল শেষ পর্যন্ত ওই report-এই দেখা যাবে।
- `P3-14` দিয়েছে Phase 3-এর সম্পূর্ণ test সবুজ প্রমাণ (`P3-15` এর মাধ্যমে Phase 4 শুরুর শর্ত)।
- `P0-04` আগেই client-কে Setup Checklist ও decision list পাঠিয়েছিল এবং **Asset pilot-এর change request (OD-18)-এর খসড়া আলাদা পাতায়** রেখেছিল। সেই খসড়াই এই task-এ চূড়ান্ত করে সই করানো হয়।

**Relation: পরের task (কে অপেক্ষা করছে)**
- **পুরো Phase 4** এই task বন্ধ না হলে শুরু হয় না (Pack: "অবরুদ্ধ করে: পুরো Phase 4")।
- সরাসরি `depends on` লেখা card: `P4-02` (inbox), `P4-04` (Asset reader, OD-21), `P4-07` (Asset-এ ফেরত, OD-11 ও OD-21)।
- সিদ্ধান্তগুলো যে card-এ কাজে লাগে: OD-10 -> `P4-05` (mapping-এর মান) ও `P4-06` (line-নিয়ম); OD-3 -> `P4-06` (currency); OD-11 -> `P4-07`; OD-21 -> `P4-04` ও `P4-07`; OD-22 -> `P4-07` ও Asset-এর ভবিষ্যৎ কাজ।
- `P4-11` ও `P5-08`: `event_intake.sources.asset_write_off` চালুর শর্ত "OD-18 অনুমোদিত" এই task-এর সই করা নথি। System এটা যাচাই করে না, নথি দেখে মানুষ নিশ্চিত করে।

**Related tables / schema**
কোনো table বা migration নেই। এই task শুধু নথি ও সিদ্ধান্ত।

**Business rules**
- মূল scope-এ Fixed Assets আলাদা workflow, আর অন্য module থেকে automatic posting দুটোই বাদ ছিল। এই pilot দুটোকেই ছুঁয়েছে, তাই client-এর লিখিত অনুমোদন ছাড়া কোনো code লেখা যাবে না।
- **যা বদলাচ্ছে:** Asset-এর write-off থেকে Draft Journal নিজে তৈরি হবে (post নয়)।
- **যা বদলাচ্ছে না:** Asset-এর নিজের নিয়ম, বিদ্যমান invoice ও payment।
- ছয়টা সিদ্ধান্তই লিখিতভাবে বন্ধ না হলে `ACC-P4-02` থেকে কোনো card শুরু হবে না।

**Task scope**
নিচের ছয়টা কাজ। প্রতিটার একজন owner আর একটা লিখিত deliverable।

| # | কাজ | কে | Deliverable |
| --- | --- | --- | --- |
| 1 | **Change request (OD-18)** সই করানো। যা বদলাচ্ছে ও যা বদলাচ্ছে না (ওপরে) স্পষ্ট লেখা | Client management | সই করা নথি |
| 2 | **OD-10:** write-off-এর Debit account, Credit account, আর `amount` কি journal-এর একমাত্র amount (accumulated depreciation ছাড়া) | Accountant | সিদ্ধান্ত নথি ও mapping-এর মান |
| 3 | **OD-3:** Asset-এর currency আর company-র base currency মিলবে কি, না মিললে কী | Management ও tech lead | সিদ্ধান্ত নথি |
| 4 | **OD-11:** `accepted` মানে Draft তৈরি, নাকি Post | Tech lead ও accountant | সিদ্ধান্ত নথি |
| 5 | **OD-21:** `effective_date` ও `schema_version` Asset-এর outbox-এ যোগ হবে, নাকি Accounting-এর adapter write-off source থেকে পড়বে | Tech lead | সিদ্ধান্ত নথি |
| 6 | **OD-22:** সম্পন্ন write-off পরে বাতিল বা সংশোধিত হলে কী | Accountant ও tech lead | সিদ্ধান্ত নথি |

**সিদ্ধান্ত অনুযায়ী কোন card কীভাবে বদলায় (প্রভাব):**

| সিদ্ধান্ত | বিকল্প | প্রভাব |
| --- | --- | --- |
| OD-21 | (ক) Asset-এর outbox-এ `effective_date`, `schema_version` যোগ | `P4-07` Asset-এ একটা ছোট migration ও writer-এর পরিবর্তন করে; `P4-04` ওই column থেকে পড়ে |
| OD-21 | (খ) Accounting-এর adapter নিজে পড়বে | `P4-04` Asset-এর `asset_write_offs` table থেকে পড়ে (শুধু `SELECT`); Asset অপরিবর্তিত; `schema_version = 1` adapter-এ ধ্রুবক |
| OD-11 | (ক) `accepted` = Draft তৈরি | `P4-07` Draft commit-এর পর (`afterCommit`) `AcknowledgeEventJob` চালায় |
| OD-11 | (খ) `accepted` = Post | `P4-07` Asset-এর status বদল Post-এর পরে (`JournalPosted`) করে; Draft অনেকক্ষণ আটকে থাকলে Asset-এ event `pending` দেখায় |
| OD-22 | (খ) বা (গ) | Asset-এ আরও কাজ লাগে, **এই Pack-এ নেই** |

**Out of scope**
কোনো code, migration বা test নয়। Disposal ও acquisition event-এর সিদ্ধান্ত (সেগুলো Phase 4-এর বাইরে, `P4-13`-এ শুধু তালিকা হয়)।

**Acceptance**
ছয়টাই লিখিতভাবে বন্ধ।

**Test**
কোড test নেই। যাচাই: ছয়টা সিদ্ধান্তের প্রতিটার সই বা লিখিত নিশ্চয়তা নথিতে আছে; প্রতিটা সিদ্ধান্ত কোন বিকল্প নেওয়া হলো স্পষ্ট লেখা; OD-10-এর মান `P4-05`-এর mapping তালিকার সাথে মিলিয়ে দেখা হয়েছে।

**Deliverable**
সই করা change request নথি, ছয়টা সিদ্ধান্ত নথি, OD-10-এর account mapping-এর মান, বন্ধ হওয়া OD-র তালিকা।

**Sub-tasks**
change request সই (OD-18), OD-10, OD-3, OD-11, OD-21, OD-22

**Definition of done**
- ছয়টা সিদ্ধান্তের প্রতিটা লিখিতভাবে বন্ধ এবং owner-এর নাম সহ।
- সই করা নথি প্রকল্পের নথি-ভান্ডারে রাখা, `P4-02` থেকে সবার কাছে লিংক সহ উপলব্ধ।
- কোন বিকল্প (OD-11 ক/খ, OD-21 ক/খ) বেছে নেওয়া হলো তা পরের card-গুলোর team জানে।

**Architecture constraints**
- Client-এর লিখিত অনুমোদন ছাড়া Phase 4-এর কোনো code merge নয়।
- Asset-এর business rule, invoice বা payment বদলানোর কোনো প্রতিশ্রুতি এই নথিতে নেই।

**Open points (Pack-এ অস্পষ্ট)**
- OD-3-এর বিকল্পের তালিকা Pack-এ নেই। Pack শুধু ধরে নেয় `event_currency_policy` default `hold` (`P4-06`, `P4-03`)। সিদ্ধান্তে `hold`/`ignore` ছাড়া তৃতীয় কিছু এলে `P4-06`/`P4-03` বদলাবে।
- OD-22-এর বিকল্প (ক) Pack-এ স্পষ্ট লেখা নেই; শুধু বলা আছে (খ) বা (গ) হলে Asset-এ আরও কাজ, এই Pack-এ নেই। বিকল্পগুলো নথিতে নাম ধরে লিখতে হবে।
- OD-10: "`amount` কি একমাত্র amount" প্রশ্নের উত্তর "না" হলে (accumulated depreciation আলাদা line) `P4-06`-এর line-নিয়ম বদলাবে; Pack-এর line-নিয়ম শুধু দুই line (Debit `debit_account`, Credit `credit_account`, দুটোতেই `amount`)।

Owner
All (TL+PO)
Estimate Hours
3
Status
Todo
QA Status
-
QA Comment

---

## ACC-P4-02 — Finance Event Inbox

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service)
Title
Finance Event Inbox — schema, গ্রহণ ও অবস্থা-বদলের service
Branch Name
`feature/accounting-event-inbox`
View Order
54
Description
**Summary**
অন্য module থেকে আসা finance event-এর জন্য একটা source-নিরপেক্ষ inbox বানাতে হবে — একই event যতবারই আসুক একটাই row, আর status বদল শুধু একটা service (`InboxTransitionService`) দিয়ে। বাকি Phase 4-এর সব কাজ এই ভিত্তির ওপর দাঁড়াবে।

**Task serial**
`ACC-P4-02 — Finance Event Inbox` · Feature

**Depends on**
- `ACC-P4-01` (সই করা change request, সিদ্ধান্ত বন্ধ)
- `ACC-P1-12` (`AccountingAudit`, `AccountingError` enum, company context)
- `ACC-P2-12` (Posting service, Phase 2 সম্পূর্ণ; Pack কারণ আলাদা করে বলেনি, নিচের open point দেখুন)

**Relation: আগের task**
- `P4-01` দিয়েছে অনুমতি: OD-18 সই, আর OD-11/OD-21 সিদ্ধান্ত যা inbox-এর `effective_date`, `schema_version` ও পরে `acknowledged_at` ব্যবহারের ধরন ঠিক করে।
- `P1-12` দিয়েছে: audit লেখার `AccountingAudit::log(...)`, error-এর একটাই উৎস `AccountingError` enum (এখানে নতুন `EVT_*` code যোগ হবে), আর `X-Correlation-Id`।
- `P2-12` দিয়েছে: কাজের Posting পথ ও Phase 2-এর journal lifecycle, যার ওপর পরে `DraftBuilder` (`P4-06`) দাঁড়ায়।

**Relation: পরের task (কে অপেক্ষা করছে)**
Pack বলে এই task `P4-03`, `P4-05`, `P4-06`, `P4-09`-কে অবরুদ্ধ করে। এছাড়া দেখা যায়:
- `P4-03` (Consumer job): `InboxService::receive` দিয়ে event তোলে; `status = 'received'` row `FOR UPDATE SKIP LOCKED` দিয়ে দখল করে; `InboxTransitionService` দিয়ে অবস্থা বদলায়; `idx_acc_fei_claim` index ব্যবহার করে।
- `P4-05` (mapping): inbox-এর `source_type`/`event_type` ধরে mapping; আটকে থাকা (`held_mapping`) event গোনে।
- `P4-06` (`DraftBuilder`): inbox row পড়ে Draft বানায়, `received` -> `draft_created`; `acc_finance_event_journal_links.inbox_id` composite FK `(company_id, inbox_id)` এই task-এর `uq_acc_fei_company_id`-কে লক্ষ্য করে (তাই এই unique বাদ দেওয়া যাবে না)।
- `P4-07`: expand migration দিয়ে এই table-এ `acknowledged_at`, `ack_attempts`, `ack_error` যোগ করে।
- `P4-09` (ops screen): release, retry, ignore, conflict সমাধান সব এই task-এর transition table মেনে চলে; `acc_finance_event_actions.inbox_id` composite FK।
- `P4-10` (RC8), `P5-03` (monitoring: `held_events`, `failed_events`, `ack_failing`) inbox-এর অবস্থা পড়ে।
- `P4-12` পুরো suite (AC4.1) আর `P5-*` ops।

**Related tables / schema**

**`acc_finance_event_inbox`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `source_type` | VARCHAR(40) | না | যেমন `asset_finance_event` |
| `source_id` | VARCHAR(64) | না | মূল record-এর id, string যাতে যেকোনো PK ধরন চলে |
| `event_type` | VARCHAR(40) | না | যেমন `write_off` |
| `schema_version` | SMALLINT UNSIGNED | না | Default 1 |
| `effective_date` | DATE | হ্যাঁ | অবৈধ payload ধরার জন্য null চলে |
| `amount` | DECIMAL(20,4) | হ্যাঁ |  |
| `currency` | CHAR(3) | হ্যাঁ |  |
| `payload` | JSON | না | মূল তথ্য হুবহু |
| `payload_hash` | CHAR(64) | না | `payload`-এর canonical SHA-256 |
| `conflict_payload` | JSON | হ্যাঁ | একই identity ভিন্ন payload এলে |
| `status` | VARCHAR(16) | না | Default `received` |
| `error_code` | VARCHAR(40) | হ্যাঁ |  |
| `error_message` | VARCHAR(500) | হ্যাঁ |  |
| `attempts` | SMALLINT UNSIGNED | না | Default 0 |
| `next_retry_at` | TIMESTAMP | হ্যাঁ |  |
| `received_at`, `processed_at` | TIMESTAMP |  |  |
| `version` | INT UNSIGNED | না |  |
| `created_at`, `updated_at` |  | না |  |

Constraint ও index:
- `uq_acc_fei_identity (company_id, source_type, source_id, event_type)`: এটাই **R4.1, DB স্তরে**।
- `uq_acc_fei_company_id (company_id, id)`: অন্য table-এর composite FK-র লক্ষ্য।
- `ck_acc_fei_status`: `received`, `draft_created`, `held_mapping`, `held_currency`, `conflict`, `failed`, `ignored`।
- `idx_acc_fei_claim (company_id, status, next_retry_at)`, `idx_acc_fei_received (company_id, received_at)`।

**Business rules**
- R4.1 থেকে R4.3 (Blueprint)। একই identity `(company_id, source_type, source_id, event_type)` মানে একই event: যত বারই আসুক একটাই row।
- মূল `payload` একবার লিখলে আর বদলায় না। একমাত্র ব্যতিক্রম: `conflict` সমাধানে মানুষ নতুন payload গ্রহণ (`accept`) করলে `payload`-এ `conflict_payload` বসে, audit সহ (`P4-09`)।
- অবস্থা বদল **শুধু একটা service দিয়ে**: `InboxTransitionService` (Journal state machine `P2-01`-এর মতো)। অন্য কোনো code সরাসরি `status` update করবে না; একটা static test এটা ধরবে।
- `EVT_*` code সাধারণত inbox-এর অবস্থা হিসেবে থাকে, user-এর request-এর error নয়। শুধু ops screen থেকে নিজে retry করলে HTTP response আসে (`EVT_STATE_INVALID` 409)।

**অবস্থা বদলের নিয়ম (`InboxTransitionService`):**

| থেকে | যায় | কে | কখন |
| --- | --- | --- | --- |
| (নতুন) | `received` | গ্রহণ | event প্রথমবার এল |
| `received` | `draft_created` | Consumer | Draft Journal তৈরি হলো |
| `received` | `held_mapping`, `held_currency`, `failed` | Consumer | নিয়ম অনুযায়ী (`P4-03`) |
| `received`, `draft_created`, `held_*` | `conflict` | গ্রহণ | একই identity ভিন্ন payload |
| `held_mapping`, `held_currency`, `failed`, `ignored` | `received` | মানুষ (release বা retry, `P4-09`) | সমস্যা ঠিক করার পর |
| `held_mapping`, `held_currency`, `failed` | `ignored` | মানুষ | কারণ সহ |
| `conflict` | `received` | মানুষ | নতুন payload গ্রহণ করে (পুরোটা আবার প্রক্রিয়া) |
| `conflict` | `ignored` | মানুষ | নতুন payload প্রত্যাখ্যান |
| `draft_created` | `received` | System | লিঙ্কযুক্ত Draft journal `cancelled` হলে (R4.10) |

অন্য যেকোনো জোড়া: নতুন code `EVT_STATE_INVALID` (409)।

**DB flow: গ্রহণ (`InboxService::receive(SourceEvent)`)**
1. transaction শুরু।
2. `INSERT ... ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)`; নতুন হলে `status = 'received'`।
3. নতুন না হলে row `SELECT ... FOR UPDATE`: `payload_hash` সমান হলে কিছু না বদলে আগের অবস্থা ফেরত (`EVT_DUPLICATE`, 200)। ভিন্ন হলে `status = 'conflict'` আর `conflict_payload` বসানো, মূল `payload` অক্ষত (`EVT_CONFLICT`)।
4. Audit (`accounting.event.received` বা `.conflict`)।
5. commit।

**Task scope**
1. Migration `acc_finance_event_inbox` (সব constraint ও index সহ)।
2. Model ও repository (interface সহ)।
3. `InboxService::receive(SourceEvent)`: ওপরের DB flow, `payload_hash` = `payload`-এর canonical (key-ক্রম স্থির করা) JSON-এর SHA-256।
4. `InboxTransitionService`: উপরের transition table কোডে একটাই উৎস হিসেবে; `apply(inboxId, toStatus, actor|system, ctx)`; অবৈধ জোড়ায় `EVT_STATE_INVALID`; প্রতিটা বদলে `version + 1`, `WHERE version = ?`।
5. Error: `EVT_DUPLICATE`, `EVT_CONFLICT`, `EVT_STATE_INVALID` `AccountingError`-এ।
6. Static test: `acc_finance_event_inbox.status` set করা শুধু `InboxTransitionService`-এর মধ্যে (এবং `receive`-এর `INSERT`/conflict পথে)।

**Out of scope**
Consumer job (`P4-03`), Asset থেকে পড়া (`P4-04`), Draft তৈরি (`P4-06`), ops API ও UI (`P4-09`), `acknowledged_at`/`ack_*` column (`P4-07`)।

**Contract**
- `InboxService::receive(SourceEvent): InboxReceiveResult` (নতুন, duplicate, conflict জানায়)।
- `InboxTransitionService::apply(...)`।
- কোনো public HTTP endpoint নেই এই task-এ।

**Error**
`EVT_DUPLICATE` (200, আগের ফল), `EVT_CONFLICT` (409), `EVT_STATE_INVALID` (409, নতুন)

**Audit**
`accounting.event.received`, `accounting.event.conflict`

**Acceptance**
AC4.1

**Test**
- একই event ১০ বার পাঠালে একটাই row, আগের ফল ফেরত।
- একই identity ভিন্ন payload: `status = 'conflict'`, `conflict_payload` বসে, মূল `payload` অক্ষত।
- প্রতিটা বৈধ ও অবৈধ অবস্থা-জোড়া (সব অবস্থা × সব অবস্থা matrix)।
- দুই connection থেকে একসাথে গ্রহণ (MySQL-এ concurrency test)।

**Deliverable**
Migration, `InboxService`, `InboxTransitionService`, model, error case, test।

**Sub-tasks**
migration, receive, transition table, conflict, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC4.1 সবুজ।
- DB-তে `uq_acc_fei_identity` সরাসরি SQL দিয়ে ভাঙার চেষ্টা ব্যর্থ (test প্রমাণ)।

**Architecture constraints**
- অবস্থা বদলের একমাত্র পথ `InboxTransitionService`।
- মূল `payload` কোনো সাধারণ code বদলাবে না।
- `uq_acc_fei_company_id` বাদ দেওয়া যাবে না (`P4-06`, `P4-09` এর composite FK-র লক্ষ্য)।

**Open points (Pack-এ অস্পষ্ট)**
- Pack বলেনি `P2-12`-এর ওপর এই task কেন নির্ভর করে। ধরে নেওয়া হয়েছে Phase 2 (journal ও posting) পুরো সবুজ থাকা Phase 4 শুরুর শর্ত; tech lead নিশ্চিত করবেন।
- Transition table-এ `failed`, `ignored` বা `conflict` অবস্থায় থাকা identity-তে **আবার ভিন্ন payload** এলে কী হবে বলা নেই (`conflict`-এ যাওয়ার সারিতে শুধু `received`, `draft_created`, `held_*` আছে)। একই payload এলে `EVT_DUPLICATE`। ভিন্ন payload এই তিন অবস্থায় এলে প্রস্তাব: `conflict_payload` হালনাগাদ বা প্রত্যাখ্যান, সিদ্ধান্ত লাগবে।
- `payload_hash`-এর canonical রূপ (key sorting, number format) Pack-এ সংজ্ঞায়িত নয়, `P2-12`-এর `posted_hash` canonical JSON-এর সাথে একই পদ্ধতি নেওয়াই যুক্তিসঙ্গত, কিন্তু নিশ্চিত করতে হবে।

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

---

## ACC-P4-04 — Asset event reader (adapter)

Project
ERPFlow
Module
Accounting Module
Type
Feature (service)
Title
Asset event reader (adapter) — শুধু-পড়া
Branch Name
`feature/accounting-asset-event-reader`
View Order
55
Description
**Summary**
Asset-এর outbox (`asset_finance_events`) থেকে write-off event শুধু পড়ার একটা adapter লাগবে। Accounting সেখানে কিছুই লেখবে না; consumer ও Draft builder `FinanceEventSource` interface দিয়ে event পাবে।

**Task serial**
`ACC-P4-04 — Asset event reader (adapter)` · Feature

**Depends on**
- `ACC-P4-01` (OD-21: `effective_date` ও `schema_version` কোথা থেকে আসবে)

**Relation: আগের task**
- `P4-01` OD-21-এর সিদ্ধান্ত দিয়েছে। (ক) হলে Asset-এর outbox-এ নতুন `effective_date` ও `schema_version` column থাকবে (যোগ করবে `P4-07`) আর এই adapter সেখান থেকে পড়বে। (খ) হলে এই adapter Asset-এর `asset_write_offs` table থেকে তারিখ পড়বে আর `schema_version = 1` ধ্রুবক।
- বিদ্যমান `asset_finance_events` outbox (Asset module-এর, Pack-এর table index-এ "বিদ্যমান") যা Asset নিজের transaction-এ `pending` row দিয়ে লেখে। Accounting এর অস্তিত্ব জানে না।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P4-03` (Consumer job): `FinanceEventSource::fetchPending(companyId, intake_batch_size)` ডাকে। Pack-এ এই task `P4-03`-কে অবরুদ্ধ করে।
- `P4-10` (RC8): এই task-এর reader class-এ আলাদা read-only method `fetchAcceptedIds` যোগ করে RC8.5-এর জন্য (Asset-এ `accepted` অথচ Accounting-এ inbox row নেই কি না দেখতে)। সেই method-ও একই "শুধু-পড়া" static test মানবে।
- `P4-07` (Asset-এ ফেরত): Asset-এর দিকের `FinanceEventAcknowledger` আলাদা class; এই reader শুধু পড়ে। OD-21 (ক) হলে `P4-07`-এর migration-এর column এই reader পড়ে।
- `P4-12`: AC4.8 (Asset-এর আচরণ অপরিবর্তিত) ও static test।

**Related tables / schema**
কোনো নতুন table নেই। পড়া হয় বিদ্যমান `asset_finance_events`: শুধু `event = 'write_off'`, `status = 'pending'` row। Column যা Pack-এ উল্লেখিত: `id`, `company_id`, `event`, `status`, `serialized_asset_id`, `reference_id`। বাকি column payload-এ হুবহু যায়।

কোনো লেখা নেই। OD-21 (খ)-তে Asset-এর write-off source record (`asset_write_offs`) থেকে শুধু `SELECT`।

**Business rules**
- Accounting Asset-এর outbox শুধু **পড়ে**; কখনো `insert`, `update`, `delete` বা model `save` নয়।
- শুধু committed row দেখা: `fetchPending` একটা নির্দিষ্ট isolation level (REPEATABLE READ বা READ COMMITTED, নির্দিষ্ট করে লেখা) ব্যবহার করে। Asset-এর নিজের transaction শেষ না হওয়া event আসে না।
- শুধু `write_off` event আসে। `disposal`, `acquisition` ইত্যাদি আসে না।
- অন্য company-র row আসে না (`WHERE company_id = ?`)।
- অবৈধ তথ্যের জন্য কোনো business error নেই; সব `integrity_flags`-এ।
- R4.4: event-এর `company_id` আর সংশ্লিষ্ট asset ও write-off source-এর `company_id` মিলতে হবে।

**DB flow**
1. `AssetFinanceEventSource::fetchPending(companyId, limit)`:
   `SELECT * FROM asset_finance_events WHERE company_id = ? AND event = 'write_off' AND status = 'pending' ORDER BY id LIMIT ?`।
2. প্রতিটা row `SourceEvent`-এ রূপান্তর: `source_type = 'asset_finance_event'`, `source_id` = ওই row-এর `id`, `schema_version = 1` (adapter-এ ধ্রুবক, OD-21 (খ)-তে), `payload` = row-এর সব column হুবহু।
3. `effective_date`: OD-21 (ক) হলে Asset-এর যোগ করা column থেকে। (খ) হলে `reference_id` ধরে Asset-এর write-off source record থেকে তারিখ পড়া (শুধু `SELECT`)। দুটোই না পেলে `effective_date = null` (ফলে পরে `EVT_PAYLOAD_INVALID`)।
4. Company যাচাই (R4.4): event-এর `serialized_asset_id`-র asset আর write-off source-এর `company_id` পড়ে event-এর `company_id`-র সাথে মেলানো। না মিললে `integrity_flags.company_mismatch = true`।
5. `fetchOne(sourceId)`: একটা id ধরে একই রূপান্তর।

**Task scope**
1. Interface `FinanceEventSource`: `sourceType(): string`, `fetchPending(companyId, limit): iterable<SourceEvent>`, `fetchOne(sourceId): ?SourceEvent`।
2. DTO `SourceEvent`: `company_id`, `source_type`, `source_id`, `event_type`, `schema_version`, `effective_date`, `amount`, `currency`, `dimensions` (cost center), `payload`, `occurred_at`, `integrity_flags` (যেমন `company_mismatch`)।
3. `AssetFinanceEventSource` (ওপরের DB flow)।
4. তারিখ নির্ণয় (OD-21 অনুযায়ী দুই রূপ)।
5. Company যাচাই।
6. **শুধু-পড়া সুরক্ষা:** এই class-এ কোনো `insert`, `update`, `delete` বা model `save` নেই। একটা static test এই class-এর source-এ এসব নিষিদ্ধ শব্দ খোঁজে। বিদ্যমান Asset code অপরিবর্তিত।
7. Source-এর container binding: `source_type` ধরে সঠিক implementation পাওয়া যায় (পরে `P4-07`-এর acknowledger-এর জন্যও একই কৌশল)।

**Out of scope**
Asset-এ কোনো লেখা বা পরিবর্তন (`P4-07`); inbox-এ ঢোকানো (`P4-03`, `P4-02`); `fetchAcceptedIds` (`P4-10` যোগ করে)।

**Contract**
Interface ও DTO (ওপরে)। কোনো HTTP endpoint নেই।

**Permission**
প্রযোজ্য নয় (ভেতরের service)।

**Error**
কোনো business error নেই। অবৈধ তথ্য `integrity_flags`-এ।

**Audit**
কোনো নিজস্ব audit নেই (event-এর audit inbox-এ, `P4-02`)।

**Acceptance**
AC4.8

**Test**
- Pending ও non-pending row: শুধু `pending` আসে।
- শুধু `write_off` আসে (`disposal`, `acquisition` নয়)।
- অন্য company-র row আসে না।
- Company অমিল ধরা (`integrity_flags.company_mismatch`)।
- তারিখ-পাওয়া ও না-পাওয়া (OD-21-এর দুই রূপ)।
- Static test: class-এ নিষিদ্ধ শব্দ নেই।
- Asset-এর নিজের transaction শেষ না হওয়া row দেখা যায় না।

**Deliverable**
Interface, DTO, `AssetFinanceEventSource`, test।

**Sub-tasks**
interface ও DTO, adapter, তারিখ নির্ণয়, company যাচাই, static test, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC4.8 (এই অংশ) সবুজ।
- বিদ্যমান Asset code-এর diff খালি।

**Architecture constraints**
- Adapter শুধু পড়ে; লেখার কোনো পথ নেই।
- Source-নিরপেক্ষ interface: `P4-03` শুধু `FinanceEventSource` চেনে, Asset-এর class নয়।

**Open points (Pack-এ অস্পষ্ট)**
- Isolation level: REPEATABLE READ নাকি READ COMMITTED, Pack "নির্দিষ্ট করে" বলেছে কিন্তু কোনটা বলেনি। বাছাই implementation-এ, PR-এ লিখিত থাকতে হবে।
- `dimensions` (cost center) এবং `amount`, `currency`, `occurred_at` কোন column থেকে আসে তা `asset_finance_events`-এর column তালিকা ছাড়া জানা যায় না (Pack-এ নেই)। `P0-02`/Asset code দেখে নিশ্চিত করতে হবে।
- `payload` = "row-এর সব column" বলা আছে, OD-21 (খ)-তে আনা write-off source-এর তারিখ payload-এ যোগ হবে কি না স্পষ্ট নয়।

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

---

## ACC-P4-03 — Consumer job

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + job)
Title
Consumer job — event তোলা, প্রক্রিয়া করা, heartbeat
Branch Name
`feature/accounting-event-consumer-job`
View Order
56
Description
**Summary**
একটা scheduled consumer job source থেকে event তুলে inbox-এ আনবে, প্রতিটি `received` row আলাদা transaction-এ process করবে, আর process kill বা crash-এর পরও নিরাপদে resume হবে — event হারানো বা duplicate journal-এর পথ থাকবে না।

**Task serial**
`ACC-P4-03 — Consumer job` · Feature

**Depends on**
- `ACC-P4-02` (inbox ও transition service)
- `ACC-P4-04` (`FinanceEventSource` ও Asset adapter)
- `ACC-P1-12` (`AccountingJob` base, company context)
- `ACC-P1-01` (`AccountingFeatureService`, flag)

**Relation: আগের task**
- `P4-02` দিয়েছে: `acc_finance_event_inbox`, `InboxService::receive` (event গ্রহণ, `EVT_DUPLICATE`), `InboxTransitionService`, আর `idx_acc_fei_claim` index যার ওপর দখল-query চলে।
- `P4-04` দিয়েছে: `FinanceEventSource::fetchPending(companyId, limit)` এবং `SourceEvent` DTO।
- `P1-12` দিয়েছে: `AccountingJob` base (constructor-এ `companyId` বাধ্যতামূলক, `TenantContext` বসানো ও `finally`-তে পরিষ্কার)।
- `P1-01` দিয়েছে: `AccountingFeatureService`-এর `sourceEnabled(companyId, source)` (flag `Setting` model-এ, default `false`)।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P4-06` (`DraftBuilder`): Pack অনুযায়ী এই task `P4-06`-কে অবরুদ্ধ করে। Consumer প্রতিটা দখল করা row-এর জন্য `DraftBuilder::build(row)` ডাকে।
- `P4-12`: AC4.5, AC4.6, AC4.10-এর test।
- `P4-09` (ops screen): `acc_intake_heartbeats` পড়ে "Consumer-এর স্বাস্থ্য-বক্স" দেখায় (সর্বশেষ কখন চলেছে, কত পিছিয়ে)।
- `P5-03` (monitoring): `consumer_lag` alert `acc_intake_heartbeats` পড়ে, `monitor_consumer_lag_minutes`-এর সাথে তুলনা করে।
- **`P4-11`:** Pack-এর ব্যতিক্রম: এই task-এর code আগে লেখা যায়, কিন্তু **চালানো যায় শুধু `P4-11` (তিন স্তরের flag) শেষ হলে**। Flag চালু করার মাধ্যম `P4-11` দেয়।

**Related tables / schema**

**`acc_intake_heartbeats`** (company প্রতি একটা row)

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `company_id` | PK, FK | না |  |
| `last_started_at`, `last_finished_at` | TIMESTAMP | হ্যাঁ |  |
| `last_error` | VARCHAR(500) | হ্যাঁ |  |
| `processed_count`, `held_count`, `failed_count` | INT UNSIGNED | না | সর্বশেষ run-এর |

বিদ্যমান: `acc_finance_event_inbox` (`P4-02`, পড়া ও লেখা)। পড়া: `asset_finance_events` (`P4-04`-এর মাধ্যমে), `acc_settings` (settings নিচে)।

**Settings যা এই task পড়ে:** `intake_batch_size` (default 50), `intake_retry_backoff_seconds` (default 60, 300, 900, 3600), `intake_max_attempts` (default 5), `event_currency_policy` (`hold` বা `ignore`, default `hold`), flag `event_intake.enabled`, `event_intake.sources.asset_write_off`।

**Business rules**
- R4.11, R4.12 (Blueprint)।
- একই company-তে একসাথে দুটো consumer চলে না।
- প্রতিটা row নিজের আলাদা transaction-এ প্রক্রিয়া হয়; একটার ব্যর্থতা বাকিদের থামায় না।
- Source থেকে তোলা আবার ঘটলে (যেমন Asset-এ ফেরত জানানো ব্যর্থ হয়ে `pending` থেকে গেলে) `receive` শুধু `EVT_DUPLICATE` দেয়, ক্ষতি নেই।
- একটা run সর্বোচ্চ ৫০ সেকেন্ড (schedule প্রতি মিনিটে, তাই overlap এড়াতে)।
- Flag run চলার মধ্যেও প্রতিটা loop-এর শুরুতে আবার যাচাই; বন্ধ হলে চলমান run পরের loop-এ থামে।
- এই job কখনো `PostingService` ডাকে না (AC4.4)।

**ফলের শ্রেণিবিভাগ (Draft তৈরির ফল অনুযায়ী inbox অবস্থা):**

| ফল | inbox অবস্থা |
| --- | --- |
| Draft তৈরি সফল | `draft_created` (`DraftBuilder`, `P4-06`) |
| `EVT_MAPPING_MISSING` | `held_mapping` |
| `EVT_CURRENCY_MISMATCH` | `event_currency_policy` অনুযায়ী `held_currency` (`hold`) বা `ignored` (`ignore`) |
| `EVT_PAYLOAD_INVALID`, `EVT_COMPANY_MISMATCH`, amount শূন্য বা ঋণাত্মক | `failed` (retry নেই) |
| অপ্রত্যাশিত exception | `attempts + 1`, `next_retry_at` = `intake_retry_backoff_seconds`-এর `attempts`-তম মান; `attempts >= intake_max_attempts` হলে `failed`, নতুন code `EVT_PROCESSING_ERROR` |

**DB flow: এক run**
1. company context (`P1-12`)।
2. Flag আবার পড়া (`sourceEnabled`), বন্ধ হলে থামা।
3. `acc_intake_heartbeats`-এ শুরু (`last_started_at`)।
4. **তোলা:** `FinanceEventSource::fetchPending(companyId, intake_batch_size)` (`P4-04`) থেকে প্রতিটা event `InboxService::receive` (`P4-02`)।
5. **প্রক্রিয়া, row দখল:** `SELECT id FROM acc_finance_event_inbox WHERE company_id = ? AND status = 'received' AND (next_retry_at IS NULL OR next_retry_at <= NOW()) ORDER BY id LIMIT ? FOR UPDATE SKIP LOCKED`।
6. **প্রতিটা row নিজের আলাদা transaction-এ** `DraftBuilder::build(row)` (`P4-06`)। একটা row-এর ব্যর্থতা বাকিদের থামায় না।
7. প্রতিটা loop-এর শুরুতে flag আর সময়সীমা (৫০ সেকেন্ড) যাচাই।
8. শেষে heartbeat-এ ফল (`last_finished_at`, `processed_count`, `held_count`, `failed_count`, `last_error`)।

**Task scope**
1. Migration `acc_intake_heartbeats`।
2. Artisan command `accounting:consume-events`, schedule **প্রতি মিনিটে**: তিনটা flag চালু (`P4-11`) এমন প্রতিটা company-র জন্য `ConsumeFinanceEventsJob(companyId)` dispatch।
3. **একই company-তে একসাথে দুটো নয়:** job-এর শুরুতে `GET_LOCK('acc_intake_{companyId}', 0)`; না পেলে চুপচাপ বেরিয়ে যায়; শেষে `RELEASE_LOCK` (`finally`-তে)।
4. তোলা (ওপরের step 4)।
5. দখল ও প্রক্রিয়া (step 5-6), `DraftBuilder` ডাকা `P4-06`-এর contract ধরে।
6. ফল শ্রেণিবিভাগ (ওপরের table) আর `InboxTransitionService` দিয়ে অবস্থা বদল।
7. Backoff হিসাব ও `EVT_PROCESSING_ERROR`।
8. Heartbeat লেখা।
9. Test।

**Out of scope**
Draft তৈরির ভেতরের যুক্তি (`P4-06`); flag চালু-বন্ধের API ও UI (`P4-11`); ops screen (`P4-09`); alert (`P5-03`); Asset-এ ফেরত জানানো (`P4-07`)।

**Contract**
`php artisan accounting:consume-events`; `ConsumeFinanceEventsJob(companyId)`। কোনো HTTP endpoint নেই। Heartbeat পড়ার API `P4-09`-এর অংশ।

**Permission**
প্রযোজ্য নয় (background job)।

**Error**
`EVT_PROCESSING_ERROR` (নতুন, তথ্য হিসেবে inbox `failed`)। বাকি `EVT_*` `DraftBuilder` থেকে আসে ও এখানে শ্রেণিবদ্ধ হয়।

**Audit**
`accounting.event.*` (`P4-02`, `P4-06` লেখে; এই job `.failed`, `.held` বদলের সময় একই `AccountingAudit` ব্যবহার করে)

**Acceptance**
AC4.5, AC4.6, AC4.10

**Test**
- দুটো job একসাথে: দ্বিতীয়টা চুপচাপ বেরিয়ে যায়।
- Flag চলতে চলতে বন্ধ: run পরের loop-এ থামে।
- একটা row ভাঙলে বাকিরা চলে।
- মাঝপথে process kill করে আবার চালানো: row `received` থেকে যায়, একটাই Draft হয়।
- Backoff-এর ক্রম (`attempts`-তম মান), `intake_max_attempts`-এ `failed` ও `EVT_PROCESSING_ERROR`।
- দুই company-র event মেশে না (company context)।
- MySQL-এ `SKIP LOCKED` ও `GET_LOCK` সত্যিকারের আচরণ।

**Deliverable**
Migration, command, job, heartbeat।

**Sub-tasks**
migration, command ও schedule, advisory lock, তোলা, দখল ও প্রক্রিয়া, শ্রেণিবিভাগ, heartbeat, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC4.5, AC4.6, AC4.10 (এই job-সংক্রান্ত অংশ) সবুজ।
- Flag বন্ধ থাকলে scheduler কোনো job dispatch করে না (প্রমাণিত)।

**Architecture constraints**
- এই job কখনো `PostingService` ডাকে না।
- সব job `AccountingJob` base-এর মধ্য দিয়ে company context-এ চলে।
- প্রতিটা row আলাদা transaction; একটা বড় transaction-এ পুরো batch নয়।

**Open points (Pack-এ অস্পষ্ট)**
- **Build order চক্র:** এই task `DraftBuilder` ডাকে, কিন্তু `DraftBuilder` (`P4-06`) এই task শেষ হওয়ার পর বানানো হয় (`P4-06` নির্ভর করে `P4-03`-এর ওপর)। প্রস্তাব: এই task-এ একটা `DraftBuilderContract` interface ও stub (সবসময় `held_mapping` বা no-op) রাখা, `P4-06` আসল implementation বসায়। Pack এটা বলেনি।
- `attempts`-তম মান বলতে `intake_retry_backoff_seconds` তালিকার দৈর্ঘ্য (৪) ছাড়িয়ে `attempts` গেলে (max 5) কোন মান, বলা নেই (প্রস্তাব: শেষ মান)।
- "amount শূন্য বা ঋণাত্মক" -> `failed`; কোন error code (প্রস্তাব `EVT_PAYLOAD_INVALID`) Pack স্পষ্ট বলেনি।
- `GET_LOCK` MySQL connection-ভিত্তিক; queue worker-এর connection pool/reuse-এ lock কীভাবে ছাড়ে তা implementation-এ যাচাই করতে হবে।

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

---

## ACC-P4-05 — Account mapping (schema, API, UI)

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service + api + ui)
Title
Account mapping — schema, API ও UI
Branch Name
`feature/accounting-event-account-mapping`
View Order
57
Description
**Summary**
Admin বা finance manager UI/API দিয়ে ঠিক করবেন কোন event-এর কোন অংশ কোন account-এ যাবে; সংবেদনশীল বদলে দ্বিতীয় জনের approval লাগবে (`ACC-P1-04` পথ)। Draft তৈরির account hard-code থাকবে না — সব `acc_account_mappings`-এ।

**Task serial**
`ACC-P4-05 — Account mapping (schema, API, UI)` · Feature

**Depends on**
- `ACC-P4-02` (inbox: আটকে থাকা event গোনা, `source_type`/`event_type` ধরন)
- `ACC-P1-06` (`acc_accounts`, composite FK লক্ষ্য `uq_acc_accounts_company_id`)
- `ACC-P1-04` (settings change request ও দ্বিতীয় অনুমোদন)
- `ACC-P2-10` (`SodGuard`, SoD service)
- OD: `OD-10` (`ACC-P4-01` বন্ধ করে)

**Relation: আগের task**
- `P4-02` দিয়েছে inbox, যেখানে `held_mapping` অবস্থার event আছে। এই task-এর UI সেই সংখ্যা দেখায় ও ছাড়ার প্রস্তাব দেয়।
- `P1-06` দিয়েছে `acc_accounts` ও তার composite unique `(company_id, id)`; `acc_account_mappings.account_id`-র composite FK এর লক্ষ্য। Account-এর `is_group`, `is_active`, `type` নিয়ম যাচাইয়ের উৎস।
- `P1-04` দিয়েছে `acc_setting_change_requests` (status `pending/approved/rejected/cancelled`), `settings_require_second_approval` চালু থাকলে সংবেদনশীল key-র সরাসরি লেখা বন্ধ করার পদ্ধতি, `SET_STALE_REQUEST` এবং "অনুরোধকারী নিজে অনুমোদন করতে পারে না" নিয়ম। Pack-এর `sensitive_settings` default-এ `account_mappings` আছে। এই task `mapping:` prefix-এর জন্য একটা applier নিবন্ধন করে।
- `P2-10` দিয়েছে `SodGuard` ও setting `sod_mapping_editor_cannot_approve`; এই task যে `updated_by` রাখে সেটাই SoD-র ভিত্তি (`P4-08` ব্যবহার করে)।
- `P4-01` OD-10 বন্ধ করেছে: কোন account (Debit/Credit) আর `amount` একমাত্র কি না। সেই মান এখানে প্রথম mapping-এর মান।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P4-06` (`DraftBuilder`): Pack অনুযায়ী এই task `P4-06`-কে অবরুদ্ধ করে। `MappingRequirements` পড়ে দুটো mapping আনে ও `mapping_snapshot` বানায়।
- `P4-11` (flag): Setup Checklist `I01_mappings` ("প্রয়োজনীয় সব mapping বৈধ") `MappingRequirements` ও `acc_account_mappings` ব্যবহার করে `event_intake.enabled` চালুর শর্ত যাচাই করে।
- `P4-09` (ops screen): `POST /accounting/intake/release-held` (`mapping_key` ধরে) mapping বসানোর পর এই task-এর UI থেকেও ডাকা হয়; তাই এই UI-র "আটকে থাকা event ছাড়ার প্রস্তাব" `P4-09` শেষ হলে পূর্ণ কাজ করে।
- `P4-08` (SoD): `acc_account_mappings.updated_by` Draft তৈরির সময় snapshot-এ যায়।
- `P4-12`: AC4.2, AC4.11; `P5-03` ও `P5-08`: mapping না থাকলে `held_events`।

**Related tables / schema**

**`acc_account_mappings`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `source_type` | VARCHAR(40) | না |  |
| `event_type` | VARCHAR(40) | না |  |
| `mapping_key` | VARCHAR(40) | না | `debit_account`, `credit_account` |
| `account_id` | FK | না | Composite FK `(company_id, account_id)` |
| `is_active` | BOOLEAN | না |  |
| `version` | INT UNSIGNED | না |  |
| `created_by`, `updated_by`, `created_at`, `updated_at` |  |  |  |

Unique: `uq_acc_am (company_id, source_type, event_type, mapping_key)`। Index: `idx_acc_am_account (company_id, account_id)`।

পড়া/ব্যবহার: `acc_accounts` (`P1-06`), `acc_setting_change_requests` (`P1-04`), `acc_settings` (`sensitive_settings`, `settings_require_second_approval`), `acc_finance_event_inbox` (আটকে থাকা event গোনা)।

**Business rules**
- **প্রয়োজনীয় mapping-এর তালিকা** কোডে `MappingRequirements`: `asset_finance_event` + `write_off` -> `debit_account`, `credit_account`। এটাই Setup Checklist-এর `I01`-এর আর `DraftBuilder`-এর ভিত্তি।
- **যাচাই:** Account ওই company-র, active, leaf (`group` নয়) -> নইলে `JRN_ACCOUNT_INVALID`।
- **সতর্কতা (error নয়):** `debit_account` Expense বা Asset-এর বাইরে হলে; `credit_account` Asset-এর বাইরে হলে।
- **বদলের প্রভাব (R4.14):** নতুন event নতুন mapping পায়। আগে তৈরি Draft অপরিবর্তিত। `held_mapping` অবস্থার event আপনা-আপনি ছাড়া পায় না: UI সংরক্ষণের পর জানায় "৩টা event আটকে আছে, এখন ছাড়বেন?" (`P4-09`-এর release)।
- **SoD:** যে mapping বদলেছে তার নাম `acc_account_mappings.updated_by`-এ। Draft তৈরির সময় সেই মান journal-এর সাথে জোড়া `mapping_snapshot`-এ সংরক্ষণ (`P4-06`), যাতে পরে mapping আবার বদলালেও SoD-র তথ্য ঠিক থাকে।
- Mapping বদল সংবেদনশীল: `settings_require_second_approval` (default true) চালু থাকলে সরাসরি লেখা নয়।

**DB flow: বসানো বা বদলানো**
1. `account_mappings` `sensitive_settings` তালিকায় আছে আর `settings_require_second_approval` চালু: সরাসরি লেখা নয়। `ACC-P1-04`-এর change request তৈরি, key `mapping:asset_finance_event.write_off.debit_account` ধরনের, `proposed_value` = account id। P1-04-এর service-এ একটা **`ChangeApplier` রেজিস্ট্রি**, যেখানে এই card `mapping:` prefix-এর জন্য একটা applier নিবন্ধন করে (আলাদা approval table লাগে না)।
2. অনুমোদনে (অন্য একজন, `accounting.settings.approve`): transaction, mapping row `FOR UPDATE`, `version` মেলানো (না মিললে `SET_STALE_REQUEST`), `UPDATE` বা `INSERT`, audit আগে ও পরে সহ (`accounting.mapping.changed`)।
3. `settings_require_second_approval` বন্ধ থাকলে একই লেখা সরাসরি, একই transaction-কাঠামোয়।
4. Deactivate: `is_active = false`, একই পথ।

**Task scope**
1. Migration `acc_account_mappings` (composite FK সহ)।
2. `MappingRequirements` (কোডে, `asset_finance_event` + `write_off`)।
3. Validator (account যাচাই + সতর্কতা)।
4. `ChangeApplier` রেজিস্ট্রি (যদি `P1-04` আগে না রেখে থাকে সেখানেই) + `mapping:` applier।
5. API (ওপরের Contract)।
6. UI (নিচে)।
7. Test।

**Out of scope**
Draft তৈরি (`P4-06`); আটকে থাকা event ছাড়ার কাজ নিজে (`P4-09`-এর `release-held`); SoD-র approve-এ প্রয়োগ (`P4-08`); `flag:` applier (`P4-11`)।

**Contract**
- `GET /accounting/mappings`
- `PUT /accounting/mappings/{source}/{event}/{key}` (`account_id`, `version`, `reason`)
- `POST /accounting/mappings/{source}/{event}/{key}/deactivate` (Pack-এ `POST .../deactivate` লেখা)

**UI**
1. প্রয়োজনীয় mapping-এর তালিকা (source ও event ধরে)।
2. প্রতিটার অবস্থা (বসানো, বাকি)।
3. Account picker (leaf ও active)।
4. সতর্কতা (Debit/Credit account type-এর অসামঞ্জস্য)।
5. History drawer।
6. অপেক্ষমাণ অনুমোদনের অনুরোধ।
7. আটকে থাকা event ছাড়ার প্রস্তাব ("৩টা event আটকে আছে, এখন ছাড়বেন?")।

**Permission**
`accounting.mapping.manage`। অনুমোদনের জন্য `accounting.settings.approve` (`P1-04`-এর পথ)।

**Error**
`JRN_ACCOUNT_INVALID`, `JRN_CONFLICT`, `SET_STALE_REQUEST`

**Audit**
`accounting.mapping.changed` (তৈরি, বদল, বন্ধ; আগে ও পরে সহ)

**Acceptance**
AC4.2, AC4.11

**Test**
- বৈধ ও অবৈধ account (অন্য company, inactive, group)।
- দ্বিতীয় অনুমোদন ছাড়া বদল হয় না।
- নিজে অনুমোদন (নিষেধ, `JRN_SOD_VIOLATION`)।
- বদলের পর পুরোনো Draft অপরিবর্তিত।
- আটকে থাকা event-এর সংখ্যা ঠিক।
- Stale অনুরোধ (`SET_STALE_REQUEST`); দুজন একসাথে `version` ভাঙা (`JRN_CONFLICT`)।
- সরাসরি SQL-এ অন্য company-র account বসানো DB FK আটকায়।

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

**Sub-tasks**
migration, requirements, validator, applier, API, UI, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC4.2, AC4.11 (mapping-অংশ) সবুজ।
- সব তালিকা ও বাটন `can()` ও permission দেখে।

**Architecture constraints**
- Mapping কোডে বাঁধা নয়; `MappingRequirements` শুধু প্রয়োজনীয় তালিকা, account নয়।
- আলাদা approval table বানানো নয়; `P1-04`-এর change request পুনর্ব্যবহার।
- UI-তে business rule নকল নয়; server-ই আসল বাধা।

**Open points (Pack-এ অস্পষ্ট)**
- `ChangeApplier` রেজিস্ট্রি `P1-04`-এর স্পেক-এ নেই (Pack বলে "P1-04-এর service-এ একটা রেজিস্ট্রি, এই card নিবন্ধন করে")। ধরে নেওয়া: রেজিস্ট্রি না থাকলে এই task-ই ছোট skeleton যোগ করে; `P4-11` ও `P5-06` একই রেজিস্ট্রিতে `flag:` applier দেয়।
- `P1-04`-এর অনুমোদন-flow (step 6) `acc_settings` row-এর `version` মিলিয়ে stale ধরে; mapping-এর stale-check `acc_account_mappings.version`-এ করতে হবে। applier-এর contract-এ "stale-check কোথায়" ঠিক করা দরকার।
- `P4-05` (ক্রম 35) `P4-09` (ক্রম 37)-এর আগে; UI-র "ছাড়ার প্রস্তাব" বোতাম `release-held` API পাওয়া পর্যন্ত লুকিয়ে বা disabled রাখতে হবে।
- ডিফল্ট role-ভিত্তিক `accounting.mapping.manage`-এর বরাদ্দ Pack-এ (Blueprint ৪.৫-এ) আছে, এখানে নেই।

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

---

## ACC-P4-06 — Event থেকে Draft Journal

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service)
Title
Event থেকে Draft Journal — `DraftBuilder` ও event-journal link
Branch Name
`feature/accounting-event-draft-builder`
View Order
58
Description
**Summary**
`received` inbox row থেকে `DraftBuilder` সম্পূর্ণ, balanced, validation-pass করা Draft Journal বানাবে। কিছু incomplete থাকলে journal তৈরি হবে না — event `held_*` বা `failed`-এ আটকে থাকবে। Accountant শুধু Draft দেখে submit করবেন; auto-post নেই।

**Task serial**
`ACC-P4-06 — Event থেকে Draft Journal` · Feature

**Depends on**
- `ACC-P4-03` (consumer job, যা row দখল করে `DraftBuilder::build` ডাকে)
- `ACC-P4-05` (account mapping, `MappingRequirements`)
- `ACC-P2-02` (Draft তৈরি, `JournalService::createDraft` ও Draft সম্পাদনার নিয়ম)
- `ACC-P2-03` (`SaveValidator`)
- OD: `OD-10` (লাইনের নিয়ম), `OD-3` (currency)

**Relation: আগের task**
- `P4-03` দিয়েছে: দখল করা inbox row (`FOR UPDATE SKIP LOCKED`), প্রতি row আলাদা transaction, আর ফল শ্রেণিবিভাগ (এই task থেকে ছোঁড়া `EVT_*` error গুলো consumer inbox অবস্থায় রূপ দেয়)।
- `P4-05` দিয়েছে: `acc_account_mappings` ও `MappingRequirements`; `DraftBuilder` প্রতিটা প্রয়োজনীয় key-র mapping পড়ে ও snapshot রাখে। `updated_by` যায় snapshot-এ (SoD-র জন্য)।
- `P2-02` দিয়েছে: journal তৈরির প্রোগ্রামেটিক পথ (`createDraft`), এবং Draft সম্পাদনার সংশোধিত নিয়ম: `journal_type = 'event_generated'` Draft-এ `created_by` null, তাই যেকোনো `accounting.journal.create`-ওয়ালা সম্পাদনা করতে পারে, SoD-র Maker = `submitted_by`।
- `P2-03` দিয়েছে: `SaveValidator` (account একই company-র ও active leaf, Debit/Credit নিয়ম, currency = base currency)। Event-journal বানাতে `createDraft` এটা চালায়।
- `P1-09` দিয়েছে: `acc_journals`-এর `journal_type = 'event_generated'`, `source_type/source_id/source_event` column, generated column `source_active` ও `uq_acc_journals_source_active (company_id, source_active)` (cancelled বাদে, R4.10), আর `acc_journal_lines`।
- `P4-01` দিয়েছে OD-10 (line-নিয়ম) ও OD-3 (currency)।

**Relation: পরের task (কে অপেক্ষা করছে)**
Pack: এই task `P4-07`, `P4-08`, `P4-10`-কে অবরুদ্ধ করে। এছাড়া `P4-09` (link ও journal-লিংক ops screen-এ) এবং `P4-12`।
- `P4-07` (Asset-এ ফেরত): Draft commit-এর পর (`afterCommit`, OD-11 (ক)) `AcknowledgeEventJob` dispatch করে।
- `P4-08` (approval ও SoD): `acc_finance_event_journal_links.mapping_snapshot.updated_by` পড়ে।
- `P4-10` (RC8): inbox `draft_created` row ↔ সক্রিয় link ↔ journal মিলায়; পোস্টকৃত event-journal-এর `total_debit` ↔ inbox `amount`।
- `P4-09` (ops screen): বিস্তারিত পাতায় "লিঙ্কযুক্ত journal (লিংক)"।
- `P4-12`: AC4.2, AC4.3, AC4.4, AC4.7।

**Related tables / schema**

**`acc_finance_event_journal_links`**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `inbox_id` | FK | না | Composite FK `(company_id, inbox_id)` |
| `journal_id` | FK | না | Composite FK `(company_id, journal_id)` |
| `is_live` | BOOLEAN | না | Default true। Draft বাতিল হলে false |
| `live_key` | BIGINT (generated, stored) | হ্যাঁ | `CASE WHEN is_live = 1 THEN inbox_id END` |
| `mapping_snapshot` | JSON | না | যে mapping থেকে বানানো: প্রতিটা key-র `account_id`, mapping row-এর `version` ও `updated_by`, সময় |
| `created_at` | TIMESTAMP | না |  |

Unique: `uq_acc_fejl_live (company_id, live_key)` (একটা event-এর একটাই সক্রিয় journal), `uq_acc_fejl_journal (company_id, journal_id)` (একটা journal একটাই event-এর)।

ব্যবহার/লেখা: `acc_journals`, `acc_journal_lines`, `acc_journal_status_history` (Section ৯.৪ ধাপ 8-এ), `acc_finance_event_inbox` (`draft_created`)। পড়া: `acc_account_mappings`, `acc_accounts`, `acc_settings` (`base_currency`)।

**Write-off-এর লাইন-নিয়ম (OD-10 বন্ধ হলে):**

| Line | Account | Debit | Credit | Dimension |
| --- | --- | --- | --- | --- |
| 1 | Mapping-এর `debit_account` | `amount` |  | event-এর cost center |
| 2 | Mapping-এর `credit_account` |  | `amount` | event-এর cost center |

**Business rules**
- R4.1 থেকে R4.10, R4.14 (Blueprint)।
- **অসম্পূর্ণ journal কখনো নয়।** কিছু বাকি থাকলে event `held_*` বা `failed`, journal বানানো হয় না।
- **নিজে post কখনো নয় (AC4.4)।** Draft কখনো `posted` নয়।
- `journal_date = effective_date` (তৈরির তারিখ নয়, R4.8)।
- দুই worker একই row-এ এলে একটাই Draft হয় (`uq_acc_journals_source_active` + link-এর `uq_acc_fejl_live`)।
- Event-তৈরি Draft সংশোধন: যেকোনো `journal.create`-ওয়ালা সংশোধন করতে পারে। amount বদলালে audit-এ আগে ও পরে, আর UI-তে "event-এর amount থেকে আলাদা" চিহ্ন। Post হওয়ার পর RC8 (`P4-10`) পার্থক্য ধরে।
- Draft `cancelled` হলে (R4.10) link `is_live = false` আর inbox `draft_created` -> `received`, ফলে event আবার প্রক্রিয়া হয় ও নতুন Draft বানায়। চাইলে মানুষ আগে `ignore` করতে পারে (`P4-09`)।

**DB flow: `DraftBuilder::build(inboxRow)`** (একটাই transaction, `P4-03` দখল করা row-এ)
1. row `FOR UPDATE`; `status = 'received'` না হলে বেরিয়ে যাওয়া।
2. Payload যাচাই: `effective_date` আছে, `amount > 0`, `currency` আছে, `integrity_flags` ফাঁকা। নইলে `EVT_PAYLOAD_INVALID` বা `EVT_COMPANY_MISMATCH` -> `failed`।
3. `currency = base_currency` না হলে `EVT_CURRENCY_MISMATCH`।
4. `MappingRequirements` অনুযায়ী দুটো mapping পড়া, active ও বৈধ। না থাকলে `EVT_MAPPING_MISSING` (কোন key বাকি তা `error_message`-এ) -> `held_mapping`।
5. লাইন বানানো (উপরের line-নিয়ম)।
6. `JournalService::createDraft`: `journal_type = 'event_generated'`, `journal_date = effective_date`, `narration = "Asset write-off #" + source_id`, `reference_no = "AWO-" + source_id`, `source_type`, `source_id`, `source_event` বসানো, `created_by` null। `SaveValidator` চলে। Account অবৈধ বেরোলে (যেমন mapping-এর account এখন inactive) `EVT_MAPPING_MISSING` ধরনে `held_mapping`।
7. `INSERT` journal-এ `uq_acc_journals_source_active` ভাঙলে (দুই worker একসাথে): আগের journal পড়ে সেটাই ব্যবহার, নতুন কিছু না বানিয়ে।
8. `INSERT` link (`mapping_snapshot` সহ); `acc_journal_status_history` (from null -> `draft`, Section ৯.৪)।
9. `InboxTransitionService`: `received` -> `draft_created`।
10. Audit।
11. commit।

**Draft বাতিল হলে:** Journal `cancelled` হলে একটা listener (`JournalCancelled`) transaction-এ link `is_live = false` আর inbox `draft_created` -> `received`।

**Task scope**
1. Migration `acc_finance_event_journal_links` (generated column, দুই unique, composite FK)।
2. `DraftBuilder::build(inboxRow)` (ওপরের ১১ ধাপ)।
3. Payload যাচাই, currency যাচাই, mapping পড়া, লাইন বানানো।
4. `createDraft` ডাকা ও concurrency পথ (step 7)।
5. Link ও `mapping_snapshot`।
6. `JournalCancelled` listener।
7. Test।

**Out of scope**
Consumer job (`P4-03`); mapping CRUD (`P4-05`); Asset-এ ফেরত (`P4-07`); approval/SoD-র শাখা (`P4-08`); Submit, approve, post (Phase 2-এর পথ)।

**Contract**
`DraftBuilder::build(InboxRow): DraftBuildResult` (সফল / held / failed + কারণ)। `JournalCancelled` listener। কোনো HTTP endpoint নেই।

**Permission**
প্রযোজ্য নয় (system job)। Draft সম্পাদনা `accounting.journal.create` (`P2-02`)।

**Error**
`EVT_PAYLOAD_INVALID`, `EVT_COMPANY_MISMATCH`, `EVT_CURRENCY_MISMATCH`, `EVT_MAPPING_MISSING` (সবই inbox অবস্থা হিসেবে, কোনো HTTP error নয়)

**Audit**
`accounting.event.draft_created`, `accounting.event.held`, `accounting.event.failed`

**Acceptance**
AC4.2, AC4.3, AC4.4, AC4.7

**Test**
- স্বাভাবিক Draft (দুই line, balanced, `journal_date = effective_date`, cost center)।
- প্রতিটা বাদ-যাওয়া mapping (`debit_account`, `credit_account`)।
- Inactive account (mapping-এর account এখন inactive) -> `held_mapping`।
- Currency অমিল -> `EVT_CURRENCY_MISMATCH`।
- দুই worker একই row-এ: একটাই Draft, দ্বিতীয়টা আগেরটাই ব্যবহার করে।
- Draft বাতিলের পর আবার Draft (link `is_live = false`, নতুন link)।
- Amount শূন্য।
- Link ও `mapping_snapshot`-এর সঠিকতা (`account_id`, `version`, `updated_by`)।
- Draft কখনো `posted` নয় (static: `DraftBuilder` থেকে `PostingService`-এ পথ নেই)।

**Deliverable**
Migration, `DraftBuilder`, listener।

**Sub-tasks**
migration, payload যাচাই, currency, mapping, লাইন, `createDraft`, concurrency পথ, link, `JournalCancelled` listener, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC4.2, AC4.3, AC4.4, AC4.7 সবুজ।
- সরাসরি SQL-এ দ্বিতীয় সক্রিয় link/journal বসাতে গেলে DB constraint আটকায় (test প্রমাণ)।

**Architecture constraints**
- `DraftBuilder` থেকে `PostingService`-এ কোনো পথ নেই।
- একটা row = একটা transaction; Draft, link ও inbox অবস্থা একসাথে commit বা কিছুই না।
- Lock ক্রম Pack ৯.১ মানে।

**Open points (Pack-এ অস্পষ্ট)**
- `source_event` column-এর মান কী হবে (প্রস্তাব: inbox-এর `event_type`, `source_type`/`source_id` inbox-এর সমান) Pack-এ সরাসরি লেখা নেই।
- `JournalCancelled` event কোন card dispatch করে (`P2-01` transition service বা `P2-02` cancel) Pack-এ বলা নেই; নিশ্চিত করতে হবে `P2-01`/`P2-02` এটা ছুঁড়ছে।
- `P4-06`-এর লেখা তালিকায় `acc_journal_status_history` আলাদা করে নেই, কিন্তু Section ৯.৪ ধাপ 8 তে আছে; এখানে ৯.৪ অনুসরণ করা হয়েছে।
- OD-3 অন্য সিদ্ধান্তে গেলে (শুধু `hold` নয়) step 3 ও `P4-03`-এর শ্রেণিবিভাগ বদলাবে।
- `dimensions` (cost center) event-এ না থাকলে line-এ null যাবে কি না Pack বলেনি।

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

---

## ACC-P4-11 — তিন স্তরের flag ও প্রথমবার চালু

Project
ERPFlow
Module
Accounting Module
Type
Feature (service + api + ui)
Title
তিন স্তরের flag ও প্রথমবার চালু
Branch Name
`feature/accounting-intake-flags`
View Order
59
Description
**Summary**
Event intake তিন স্তরের flag (`accounting.enabled`, `event_intake.enabled`, `event_intake.sources.asset_write_off`) দিয়ে নিরাপদে on/off করতে হবে; প্রথমবার চালাতে দুজনের সম্মতি। Consumer শুধু তখনই চলবে যখন checklist ও flag সব ঠিক।

**Task serial**
`ACC-P4-11 — তিন স্তরের flag ও প্রথমবার চালু` · Feature

**Depends on**
- `ACC-P1-01` (`AccountingFeatureService`, `Setting`-ভিত্তিক flag)
- `ACC-P1-05` (Setup Checklist, `S01` থেকে `S10`, `I01` থেকে `I03`)
- `ACC-P1-04` (change request ও দ্বিতীয় অনুমোদন, `ChangeApplier` রেজিস্ট্রি)
- `ACC-P4-05` (mapping ও `MappingRequirements`, `I01`-এর ভিত্তি)

**Relation: আগের task**
- `P1-01` দিয়েছে: `accounting.enabled`, `event_intake.enabled`, `event_intake.sources.*` flag যা `acc_settings`-এ নয়, ERPFlow-এর `Setting` model-এ থাকে; `AccountingFeatureService::enabled/intakeEnabled/sourceEnabled` (সব default `false`) এবং middleware `ACC_MODULE_DISABLED`। এই task সেই service-এ চালু-বন্ধের পূর্ণ নিয়ম ও API বসায়।
- `P1-05` দিয়েছে: `ChecklistService::evaluate`, ধাপ `S01`-`S10` (core), `S11`, আর `I01_mappings`, `I02_currency_policy`, `I03_source_flags` (intake group), এবং `canEnable()`। Pack-এ `S01` থেকে `S10` সবুজ না হলে `accounting.enabled` চালু যায় না (`SET_INCOMPLETE`)।
- `P1-04` দিয়েছে: change request (`acc_setting_change_requests`), "অনুরোধকারী নিজে অনুমোদন নয়" নিয়ম, `accounting.settings.approve`, এবং `ChangeApplier` রেজিস্ট্রি যেখানে `P4-05` `mapping:` applier দিয়েছে। এই task `flag:` prefix-এর applier যোগ করে।
- `P4-05` দিয়েছে: mapping ও `MappingRequirements` যা ছাড়া `I01` সবুজ হয় না।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P4-03` (Consumer job): Pack বলে এই task `P4-03`-কে **চালানোর জন্য** অবরুদ্ধ করে। Consumer শুধু `sourceEnabled` দেখে।
- `P4-12`: AC4.5 (`IntakeFlagMatrixTest`)।
- `P5-06` (Release gate): `accounting.enabled` চালু করে (দুজন, `flag:accounting.enabled` key) ও এই task-এর `flag:` applier ও `ChecklistService` নিয়ম পুনর্ব্যবহার করে। P5-06 এই task-এর flag service-এর ওপর নির্ভর করে।
- `P5-08` (Pilot চালু): ধাপ 5 এ `event_intake.enabled` ও `sources.asset_write_off` চালু হয় এই task-এর "প্রথমবারের নিয়ম" মেনে।

**Related tables / schema**
কোনো নতুন table নেই। Flag `Setting` model-এ (group `accounting`/`event_intake`), `acc_setting_change_requests` (`P1-04`), `acc_setup_status` (`P1-05`), `activity_logs`।

**তিন flag ও চালুর শর্ত:**

| Flag | সংরক্ষণ | চালুর শর্ত |
| --- | --- | --- |
| `accounting.enabled` | `Setting` (`P1-01`) | Setup Checklist `S01`-`S10` সবুজ |
| `event_intake.enabled` | `Setting` | `accounting.enabled` চালু + Checklist `I01`, `I02` সবুজ |
| `event_intake.sources.asset_write_off` | `Setting` | `event_intake.enabled` চালু + `I03` সবুজ + OD-18 অনুমোদিত (ডকুমেন্টে লেখা, system যাচাই করে না) |

**Business rules**
- `AccountingFeatureService::intakeEnabled(company) = enabled && intake.enabled`; `::sourceEnabled(company, source) = intakeEnabled && sources.x`। Consumer শুধু `sourceEnabled` দেখে (`P4-03`)।
- **চালু করা:** `accounting.enable.manage` permission, Checklist শর্ত, **প্রথমবার** দ্বিতীয় জনের অনুমোদন।
- **প্রথমবার** = এই company-র এই flag কখনো চালু ছিল না। সরাসরি লেখা নয়, change request (key `flag:event_intake.enabled`), অন্য একজন (`accounting.settings.approve`) অনুমোদন করলে তবে চালু। পরের বার (বন্ধ করার পর আবার) একজনেই চালাতে পারে।
- **বন্ধ করা:** সঙ্গে সঙ্গে, অনুমোদন ছাড়া (জরুরি নিরাপত্তা), `reason` বাধ্যতামূলক। Consumer-এর বর্তমান run পরের loop-এ থামে। আগের Draft, inbox কিছু মোছা হয় না।
- Flag Default সবসময় `false`। Flag বন্ধ করলে data অক্ষত থাকে।

**DB flow: চালু**
`POST /accounting/flags/{flag}` (`enabled = true`, `reason`):
1. `accounting.enable.manage` permission যাচাই।
2. Checklist শর্ত (`ChecklistService`)।
3. প্রথমবার হলে সরাসরি লেখা নয়: `P1-04`-এর change request (key `flag:event_intake.enabled`), অন্য একজন (`accounting.settings.approve`) অনুমোদন করলে তবে চালু (`ChangeApplier` `flag:` prefix)। পরের বার একজনেই।
4. `Setting` লেখা।
5. Audit `accounting.flag.enabled`।

**DB flow: বন্ধ**
`POST /accounting/flags/{flag}` (`enabled = false`, `reason` বাধ্যতামূলক): permission, `Setting` লেখা, audit `accounting.flag.disabled`; অনুমোদন ছাড়া।

**Task scope**
1. `AccountingFeatureService`-এ `intakeEnabled`, `sourceEnabled`-এর চূড়ান্ত নিয়ম (ওপরে)।
2. চালুর শর্ত যাচাই (`ChecklistService` ব্যবহার করে)।
3. প্রথমবারের অনুমোদন: `flag:` applier (`ChangeApplier` রেজিস্ট্রিতে)।
4. বন্ধ করা।
5. API `POST /accounting/flags/{flag}`।
6. UI।
7. Test।

**Out of scope**
`accounting.enabled` চালুর Release gate ও তিন গেট (`P5-06`); consumer নিজে (`P4-03`); alert (`P5-03`)।

**Contract**
`POST /accounting/flags/{flag}` (`enabled`, `reason`)। Service `AccountingFeatureService`।

**UI**
Settings-এর "Event intake" অংশ: তিন flag-এর অবস্থা, প্রতিটার শর্ত (সবুজ বা লাল তালিকা), "চালু" বাটন শর্ত পূরণে সক্রিয়, "বন্ধ" সবসময় সক্রিয়।

**Permission**
`accounting.enable.manage` (চালু বা বন্ধ), `accounting.settings.approve` (প্রথমবারের অনুমোদন)

**Error**
`SET_INCOMPLETE`, `ACC_FORBIDDEN`, `JRN_SOD_VIOLATION`

**Audit**
`accounting.flag.enabled`, `accounting.flag.disabled`

**Acceptance**
AC4.5

**Test**
- তিন flag-এর সব ২³ সমন্বয়ে consumer কী করে (`IntakeFlagMatrixTest`)।
- শর্ত পূরণ না হলে চালু হয় না (`SET_INCOMPLETE`, কোন ধাপ বাকি তার তালিকা সহ)।
- প্রথমবার নিজে অনুমোদন (নিষেধ, `JRN_SOD_VIOLATION`)।
- বন্ধ করলে চলমান run থামে।
- বন্ধের পর data অক্ষত (Draft, inbox, audit)।
- পরের বার একজনেই চালু করা যায়।

**Deliverable**
Service, API, UI।

**Sub-tasks**
service, চালুর শর্ত, প্রথমবারের অনুমোদন (applier), বন্ধ, UI, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC4.5 সবুজ।
- কোনো flag কোনো code-পথে Default `true` নয়।

**Architecture constraints**
- Flag `acc_settings`-এ নয়, `Setting` model-এ (`P1-01`-এর সিদ্ধান্ত)।
- বন্ধ করা কখনো অনুমোদনের পেছনে আটকে থাকে না।
- আলাদা approval table নয়; change request পুনর্ব্যবহার।

**Open points (Pack-এ অস্পষ্ট)**
- "প্রথমবার" নির্ধারণের উৎস Pack বলেনি। `acc_enablement_log` Phase 5-এ (`P5-06`) তৈরি হয়, তাই Phase 4-এ উৎস হতে পারে `activity_logs`-এর `accounting.flag.enabled` event বা `Setting` ইতিহাস। সিদ্ধান্ত লাগবে।
- `P5-06` `acc_enablement_log`-এ intake flag-ও রাখে (`flag` column-এ `event_intake.enabled` ইত্যাদি), কিন্তু এই task আগে বানানো হয়; P5-06-এর সময় এই task-এর flag service-কে ওই log-এ লেখাতে হবে কি না Pack বলেনি।
- `ChecklistService` Pack-এ "নতুন" লেখা, যদিও `P1-05` আগেই `ChecklistService::evaluate` ও `I01`-`I03` checker (`checker ×14`) বানায়; এই task শুধু "flag-চালুর শর্ত যাচাই" method যোগ করে ধরা হয়েছে।
- `P1-05` `POST /accounting/enable` endpoint-এর কথা বলে (পূর্ণ রূপ `P5-06`), এখানে `POST /accounting/flags/{flag}`; দুটো একই কাজ করলে একটা অন্যটার alias কি না ঠিক করতে হবে।
- `P1-04`-এর stale-check `acc_settings.version` ভিত্তিক, flag `Setting` model-এ; `flag:` applier-এর stale-check কীভাবে হবে তা নিশ্চিত করতে হবে।

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

---

## ACC-P4-07 — Asset-এ ফেরত জানানো ও Asset-এর ছোট পরিবর্তন

Project
ERPFlow
Module
Accounting Module
Type
Feature (service + job, Accounting ও Asset দুই দিকে)
Title
Asset-এ ফেরত জানানো (`accepted`) ও Asset-এর ছোট পরিবর্তন
Branch Name
`feature/accounting-asset-acknowledge`
View Order
60
Description
**Summary**
Draft commit-এর পর (OD-11 (ক)) বা Post-এর পর (OD-11 (খ)) Asset-এ outbox event `accepted` mark করতে হবে — Accounting সরাসরি Asset table ছুঁবে না, শুধু `FinanceEventAcknowledger` interface। Acknowledge fail হলেও journal/inbox অক্ষত; পরে retry।

**Task serial**
`ACC-P4-07 — Asset-এ ফেরত জানানো ও Asset-এর ছোট পরিবর্তন` · Feature

**Depends on**
- `ACC-P4-06` (Draft journal ও inbox `draft_created`)
- `ACC-P4-01` (OD-11 `accepted` কখন, OD-21 `effective_date`/`schema_version` কোথা থেকে)

**Relation: আগের task**
- `P4-06` দিয়েছে inbox `draft_created` অবস্থা, link, এবং commit-এর পর (`afterCommit`) ধরার জায়গা। OD-11 (ক) হলে এই `afterCommit`-ই `AcknowledgeEventJob` dispatch-এর মুহূর্ত।
- `P4-01` দিয়েছে OD-11 ও OD-21, যা এই task-এর দুটো শাখা ঠিক করে (নিচে)।
- `P4-02` দিয়েছে inbox table যেখানে এই task expand migration করে। `P4-04`-এর reader আলাদা (পড়ার জন্য), এই task লেখার জন্য আলাদা interface।
- `P2-12`-এর `JournalPosted` event (Section ৯.২ ধাপ 14): OD-11 (খ) হলে এই event-এ job চালে।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P4-12`: AC4.8 (`AssetBehaviorParityTest`), AC4.13 (`AcknowledgeFailureTest`) ও `AC4.10`-এর process-kill দৃশ্য (Draft তৈরির পর, ফেরতের আগে kill)।
- `P4-10` (RC8.5): Asset-এ `accepted` ↔ Accounting inbox, আর `draft_created` কিন্তু Asset-এ এখনো `pending` ও `ack_attempts` শেষ -> ব্যর্থতা। এই task-এর `acknowledged_at`, `ack_attempts` সেখানে পড়া হয়।
- `P5-03` (monitoring): `ack_failing` alert এই task-এর `ack_attempts` ও `ack_error` পড়ে।
- `P4-13`: runbook-এর "ফেরত-জানানোর ব্যর্থতা" অংশ।

**Related tables / schema**

**Accounting-এর দিক, expand migration `acc_finance_event_inbox`-এ:**

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `acknowledged_at` | TIMESTAMP | হ্যাঁ | Asset-এ সফল জানানোর সময় |
| `ack_attempts` | SMALLINT UNSIGNED | না | Default 0 |
| `ack_error` | VARCHAR(500) | হ্যাঁ |  |

**Asset-এর দিক (Asset module-এ, শুধু যেটুকু দরকার):**
- বিদ্যমান `asset_finance_events` (`status`: `pending` -> `accepted`)। কোনো journal id বা GL column যোগ হয় না (Asset-এর outbox ledger-সদৃশ নয়, এটা ইচ্ছাকৃত)।
- **OD-21 (ক) হলে:** Migration `asset_finance_events.effective_date DATE NULL`, `schema_version SMALLINT UNSIGNED NOT NULL DEFAULT 1`।

**Business rules**
- R4.13: ফেরত জানানো ব্যর্থ হলে journal বা inbox-এর অবস্থা বদলায় না; পরে আবার চেষ্টা।
- Acknowledger **idempotent**: `UPDATE ... WHERE status = 'pending'`; affected rows 0 হলে আগে থেকেই `accepted` (ঠিক আছে) বা অন্য অবস্থা (সতর্কতা log)।
- **ধ্রুব নিয়ম:** Asset-এর write-off-এর business rule, executor-এর আচরণ আর বিদ্যমান test অপরিবর্তিত। এই পরিবর্তনের জন্য Asset-এর বিদ্যমান test suite সবুজ আর একটা আচরণ-তুলনা test (পরিবর্তনের আগে ও পরে write-off চালিয়ে asset-এর অবস্থা আর outbox-এর row একই, নতুন column বাদে)।
- Accounting সরাসরি Asset-এর table ছোঁয় না; শুধু interface-এর মাধ্যমে।

**DB flow: `AcknowledgeEventJob(inboxId)`**
1. company context (`P1-12`)।
2. inbox `FOR UPDATE`; `status = 'draft_created'` আর `acknowledged_at IS NULL` না হলে বেরিয়ে যাওয়া।
3. **OD-11 (ক):** commit-এর পর (`P4-06`-এর `afterCommit`) dispatch। **(খ):** `JournalPosted` event-এ, সংশ্লিষ্ট link ধরে।
4. `acknowledger->accepted(source_id)`।
5. সফল হলে `acknowledged_at = NOW()`।
6. ব্যর্থ হলে `ack_attempts + 1`, `ack_error`, backoff সহ আবার চেষ্টা। ব্যর্থতা journal বা inbox-এর অবস্থা বদলায় না।

**Asset-দিকের লেখা (`AssetFinanceEventAcknowledger::accepted(eventId)`):**
`UPDATE asset_finance_events SET status = 'accepted' WHERE id = ? AND status = 'pending'`।

**Task scope**
1. Accounting expand migration (তিন column)।
2. Interface `FinanceEventAcknowledger`: `accepted(sourceId): void` (idempotent); `source_type` অনুযায়ী container থেকে সঠিক implementation।
3. Job `AcknowledgeEventJob(inboxId)` (ওপরের flow), backoff সহ।
4. Asset-এ `AssetFinanceEventAcknowledger` (Asset-এর নতুন service, Accounting-এর interface বাস্তবায়ন করে)।
5. OD-21 (ক) হলে: Asset migration (`effective_date`, `schema_version`), `AssetFinanceEventWriter::write` write-off-এর ক্ষেত্রে `effective_date` বসায় (write-off-এর আসল তারিখ), বিদ্যমান `pending` row-এর জন্য একবার চালানো idempotent backfill (source record থেকে তারিখ)। Unique key আর বাকি schema অপরিবর্তিত।
6. আচরণ-তুলনা test।
7. OD-11-এর দুই রূপের test।

**Out of scope**
Asset-এর business rule বদল; Asset-এ journal id বা GL column; OD-22 (বাতিল বা সংশোধিত write-off) এর Asset-দিকের আরও কাজ (এই Pack-এ নেই); Asset-এর অন্য event (disposal, acquisition)।

**Contract**
Interface `FinanceEventAcknowledger::accepted(sourceId)`; Job `AcknowledgeEventJob(inboxId)`। কোনো HTTP endpoint নেই।

**Permission**
প্রযোজ্য নয় (background)।

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

**Audit**
`accounting.asset_status.sent`, `accounting.asset_status.failed`

**Acceptance**
AC4.8, AC4.13

**Test**
- সফল `accepted`; একই ফেরত দুইবার (idempotent)।
- Asset-এ ব্যর্থতা: journal ঠিক থাকে, inbox `draft_created`, `ack_attempts` বাড়ে, পরে আবার চেষ্টায় সফল।
- Asset-এর বিদ্যমান test suite সবুজ।
- `AssetBehaviorParityTest`: আচরণ-তুলনা (asset-এর অবস্থা ও outbox row আগে-পরে একই, নতুন column বাদে)।
- OD-11-এর দুই রূপ: (ক) Draft-এ, (খ) Post-এ।
- Backfill idempotent (দুইবার চালালে একই ফল)।

**Deliverable**
Interface, job, migration, Asset-এর acknowledger ও (প্রযোজ্য হলে) migration ও writer-এর পরিবর্তন, backfill।

**Sub-tasks**
interface, job, acknowledger, OD-21 migration ও writer, backfill, আচরণ-তুলনা test, test

**Definition of done**
- সাধারণ DoD পূরণ (এই task-এ Asset-দিকের কাজ অনুমোদিত ব্যতিক্রম)।
- AC4.8, AC4.13 সবুজ।
- Asset-এর বিদ্যমান suite সবুজ, আচরণ-তুলনা test সবুজ।
- সব migration expand-only ও পিছনে সামঞ্জস্যপূর্ণ।

**Architecture constraints**
- Accounting Asset-এর table সরাসরি ছোঁয় না; Asset-এর নিজের acknowledger দিয়ে।
- Asset-এর outbox-এ ledger-সদৃশ কিছু (journal id, GL column) যোগ নয়।
- ব্যর্থ ফেরত জানানো Accounting-এর data কখনো বদলায় না।

**Open points (Pack-এ অস্পষ্ট)**
- `ack_attempts`-এর সর্বোচ্চ সীমা ও backoff এখানে "backoff সহ আবার" বলা আছে, মান নেই (`intake_max_attempts`/`intake_retry_backoff_seconds` প্রক্রিয়ার জন্য; ack-এর জন্য ওই setting নাকি আলাদা, বলা নেই)। `P4-10` (RC8.5) ও `P5-03` (`ack_failing`) "`ack_attempts` শেষ" ধরে তাই সীমা ঠিক করা জরুরি।
- OD-11 (খ)-তে `AcknowledgeEventJob`-এর step 2-এর শর্ত (`status = 'draft_created'`) Post-এর সময়ও সত্য থাকবে ধরা হয়েছে; Draft `cancelled` হলে inbox `received`-এ ফেরে, তখন কী হবে বলা নেই।
- OD-22 (খ)/(গ) হলে Asset-এ আরও কাজ লাগবে, যা এই Pack-এ নেই।
- Asset-এর `accepted` ছাড়া অন্য অবস্থা (`rejected` ইত্যাদি) আছে কি না Pack-এ নেই, তাই "অন্য অবস্থা" সতর্কতা log ছাড়া কিছু নির্ধারিত নয়।

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

---

## ACC-P4-08 — Event-journal-এর approval ও SoD

Project
ERPFlow
Module
Accounting Module
Type
Feature (service)
Title
Event-journal-এর approval ও SoD
Branch Name
`feature/accounting-event-journal-sod`
View Order
61
Description
**Summary**
Event-generated journal (`journal_type = 'event_generated'`) সাধারণ journal-এর মতোই Submit → approve → post পথে যাবে। যে mapping edit করেছে (`mapping_snapshot.updated_by`) সে সেই journal approve করতে পারবে না — `SodGuard`-এ নতুন শাখা।

**Task serial**
`ACC-P4-08 — Event-journal-এর approval ও SoD` · Feature

**Depends on**
- `ACC-P4-06` (`acc_finance_event_journal_links.mapping_snapshot`)
- `ACC-P2-08` (approval request ও সিদ্ধান্ত)
- `ACC-P2-10` (`SodGuard`)

**Relation: আগের task**
- `P4-06` দিয়েছে `mapping_snapshot` (প্রতিটা key-র `account_id`, mapping `version`, `updated_by`, সময়) যা এই task পড়ে। `P4-05` এর `updated_by` সেখানে ঢোকায়।
- `P2-08` দিয়েছে submit/approve/reject/withdraw/return পথ, `ApprovalResolver` ও request-এর `rule_snapshot`; Event-journal এই একই পথ ধরে চলে, আলাদা কিছু নেই।
- `P2-10` দিয়েছে `SodGuard::assert(action, actor, subject)` ও setting `sod_mapping_editor_cannot_approve` (default true) যা ঠিক এই task-এর জন্য `P2-10`-এ রাখা হয়েছিল; `P2-10` "অবরুদ্ধ করে" তালিকায় `P4-08` আছে।
- `P2-06` আগে থেকেই approval rule-এ `journal_type = 'event_generated'` (বা `any`) সমর্থন করে।
- `P2-02` Draft সম্পাদনার নিয়ম: `created_by` null হলে Maker = `submitted_by`।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P4-12`: Pack অনুযায়ী এই task শুধু `P4-12`-কে অবরুদ্ধ করে; AC4.11 (`MappingSodTest`) এর test।
- `P5-01` UAT-এর U11 (Asset write-off থেকে Draft) ও U13 (অনুমতিহীন চেষ্টা) পরোক্ষভাবে এই নিয়ম ছোঁয়।

**Related tables / schema**
কোনো নতুন table নেই। পড়া: `acc_finance_event_journal_links.mapping_snapshot`, `acc_journals.submitted_by`, `acc_approval_rules`/`acc_approval_requests.rule_snapshot` (rule-এর জন্য)।

**Business rules**
1. Approval rule `journal_type = 'event_generated'` (বা `any`) তৈরি করা যায় (`P2-06` আগে থেকেই সমর্থন করে)। **কোনো rule স্বয়ংক্রিয়ভাবে বানানো হয় না।** Rule না থাকলে `APR_NO_RULE` (setting `approval_no_rule_behavior` default `block` অনুযায়ী)।
2. `SodGuard::assert('approve', actor, journal)`-এ নতুন শাখা: `journal.source_type` থাকলে লিঙ্কের `mapping_snapshot.updated_by` পড়ে, approver সেই ব্যক্তি হলে `sod_mapping_editor_cannot_approve` অনুযায়ী `JRN_SOD_VIOLATION`।
3. Event-journal-এ Maker = `submitted_by` (কারণ `created_by` null)। তাই SoD "Maker নিজে approve নয়" (`sod_maker_cannot_approve`) `submitted_by`-র ওপর।
4. Event-journal Post-এর সময়ও SoD `sod_maker_cannot_post` `submitted_by`-র ওপর।
5. Setting `sod_mapping_editor_cannot_approve` বন্ধ করলে নিয়মটি আর খাটে না।

**DB flow**
কোনো নতুন লেখা নেই। পড়া:
- `acc_finance_event_journal_links.mapping_snapshot` (journal-এর লিঙ্ক ধরে, `uq_acc_fejl_journal`)
- `acc_journals.submitted_by`

**Task scope**
1. `SodGuard`-এ mapping-editor শাখা।
2. `submitted_by`-ভিত্তিক Maker নিয়ম (approve ও post দুই জায়গায়, `P2-08`/`P2-12` যেখানে `SodGuard` ডাকে)।
3. Rule না থাকলে `APR_NO_RULE` আচরণ test দিয়ে প্রমাণ।
4. Test।

**Out of scope**
নতুন table, নতুন approval engine, rule স্বয়ংক্রিয় তৈরি, mapping বদলানোর flow (`P4-05`), নতুন permission।

**Contract**
`SodGuard::assert(string $action, User $actor, mixed $subject): void` (বিদ্যমান signature, শাখা যোগ)।

**Permission**
প্রযোজ্য নয় (বিদ্যমান `accounting.journal.approve`, `accounting.journal.post`)।

**Error**
`JRN_SOD_VIOLATION` (403), `APR_NO_RULE` (422)

**Audit**
`accounting.guard.denied` (কোন নিয়ম ভাঙল সহ, `P2-10` লেখে)

**Acceptance**
AC4.11

**Test**
- Mapping-বদলকারী ওই journal approve করতে পারে না (`JRN_SOD_VIOLATION`)।
- `sod_mapping_editor_cannot_approve` বন্ধ করলে পারে।
- `submitted_by`-ভিত্তিক Maker-নিয়ম: যে submit করেছে সে নিজে approve/post করতে পারে না।
- Rule না থাকলে আটকে (`APR_NO_RULE`)।
- অন্য mapping-বদলকারী (যে বদলায়নি) approve করতে পারে।

**Deliverable**
`SodGuard`-এর শাখা, test।

**Sub-tasks**
guard শাখা, `submitted_by` নিয়ম, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC4.11 সবুজ।
- `SodGuard` ছাড়া অন্য কোথাও SoD-নিয়ম নকল নয়।

**Architecture constraints**
- SoD নিয়ম একটাই জায়গায় (`SodGuard`), বদ্ধ তালিকা।
- নতুন approval engine বা table নয়।

**Open points (Pack-এ অস্পষ্ট)**
- `mapping_snapshot` এ দুটো key (`debit_account`, `credit_account`) আলাদা `updated_by` হতে পারে। Pack বলেছে "সেই ব্যক্তি", ধরে নেওয়া হয়েছে snapshot-এর **যেকোনো** key-র `updated_by` হলেই আটকাবে। নিশ্চিত করতে হবে।
- `APR_NO_RULE` তৈরি হলে কে/কীভাবে rule বানাবে তা Setup Checklist `S07` (Phase 2 চালু হলে) আর `P2-06` করে; Event-journal-এর জন্য আলাদা rule ছাড়া কীভাবে `any` rule কাজ করবে তা `P2-07` resolver থেকে।

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

---

## ACC-P4-09 — Inbox-এর ops screen ও কাজের ইতিহাস

Project
ERPFlow
Module
Accounting Module
Type
Feature (schema + service + api + ui)
Title
Inbox-এর ops screen ও কাজের ইতিহাস
Branch Name
`feature/accounting-intake-ops-screen`
View Order
62
Description
**Summary**
Finance ops-এর জন্য inbox ops screen: আটকে থাকা (`held_*`), failed, conflict event দেখা, release/retry/ignore, conflict accept/reject — প্রতিটি action-এ reason ও `acc_finance_event_actions` ইতিহাস। Consumer heartbeat-ও এক পাতায়।

**Task serial**
`ACC-P4-09 — Inbox-এর ops screen ও কাজের ইতিহাস` · Feature

**Depends on**
- `ACC-P4-02` (inbox, `InboxTransitionService`)
- `ACC-P4-03` (heartbeat, consumer-এর স্বাস্থ্য)
- `ACC-P4-05` (mapping: bulk release `mapping_key` ধরে)
- `ACC-P4-06` (link ও journal-লিংক)

**Relation: আগের task**
- `P4-02` দিয়েছে inbox row ও transition table। এই task সেই table-এর "মানুষ" শাখার সব বদল (release/retry: `held_*`, `failed`, `ignored` -> `received`; `ignore`; conflict সমাধান) এক জায়গায় আনে। `EVT_STATE_INVALID` ওখান থেকে।
- `P4-03` দিয়েছে `acc_intake_heartbeats` যা "Consumer-এর স্বাস্থ্য-বক্স"-এ দেখানো হয় (`last_started_at`, `last_finished_at`, `last_error`, গণনা)।
- `P4-05` দিয়েছে mapping; mapping বসানোর পর `POST /accounting/intake/release-held` (`mapping_key` ধরে) সেই `held_mapping` event ছাড়ে। `P4-05`-এর UI এই API-কে ডাকে।
- `P4-06` দিয়েছে `acc_finance_event_journal_links` (বিস্তারিত পাতায় journal-লিংক) ও `JournalCancelled` listener (সিস্টেমের `draft_created` -> `received`)।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P4-12`: Pack অনুযায়ী এই task শুধু `P4-12`-কে অবরুদ্ধ করে; AC4.2, AC4.10।
- `P4-13` runbook-এর "আটকে থাকা event / ব্যর্থ / conflict / consumer থেমে গেলে" অংশ এই screen ধরে লেখা হয়, `P4-13` demo-ও।
- `P5-03` (monitoring): `held_events`, `failed_events` alert-এর link এই screen-এ।
- `P5-07` training (Finance-এর নির্দেশিকা: "intake-এর আটকে থাকা event")।

**Related tables / schema**

**`acc_finance_event_actions`** (append-only, `Schema::appendOnly`)

| Column | ধরন | Null | নোট |
| --- | --- | --- | --- |
| `id` | PK | না |  |
| `company_id` | FK | না |  |
| `inbox_id` | FK | না | Composite FK |
| `action` | VARCHAR(16) | না | `retry`, `release`, `ignore`, `accept_conflict`, `reject_conflict` |
| `from_status`, `to_status` | VARCHAR(16) | না |  |
| `actor_id` | FK users | না |  |
| `reason` | VARCHAR(500) | হ্যাঁ | `ignore`, `accept_conflict`, `reject_conflict`-এ বাধ্যতামূলক |
| `created_at` | TIMESTAMP | না |  |

Index: `idx_acc_fea_inbox (company_id, inbox_id, created_at)`।

পড়া: `acc_finance_event_inbox`, `acc_finance_event_journal_links`, `acc_journals`, `acc_intake_heartbeats`, `acc_account_mappings`।

**Business rules**
- সব বদল `InboxTransitionService` দিয়ে (`P4-02`); যে অবস্থা থেকে সরাসরি বৈধ নয় তাতে `EVT_STATE_INVALID`।
- `ignore`, `accept_conflict`, `reject_conflict`-এ `reason` বাধ্যতামূলক।
- আগে থেকেই `received` হলে release/retry কিছু না করে সফল (idempotent)।
- `release`/`retry`-এ `attempts = 0`, `next_retry_at = null`, `error_*` মোছা।
- প্রতিটা কাজ `acc_finance_event_actions`-এ ও audit-এ।

**DB flow: release বা retry**
1. idempotency (`Idempotency-Key`)।
2. transaction।
3. inbox row `FOR UPDATE`।
4. `InboxTransitionService` দিয়ে `received`, `attempts = 0`, `next_retry_at = null`, `error_*` মোছা। অবৈধ অবস্থা থেকে `EVT_STATE_INVALID`।
5. `acc_finance_event_actions` row।
6. Audit (`accounting.event.released` বা `.retried`)।
7. commit।

**DB flow: ignore**
একই কাঠামো; `reason` বাধ্যতামূলক; `status = 'ignored'`। Audit `accounting.event.ignored`।

**DB flow: conflict সমাধান**
- `accept`: transaction-এ `payload` = `conflict_payload`, `payload_hash` নতুন, `conflict_payload = null`, `received`। আগে থেকে Draft থাকলে (`draft_created` থেকে `conflict`-এ এসে থাকলে) সেটা বাতিল করে নতুনটা বানানো হবে।
- `reject`: `conflict_payload = null`, আগের অবস্থায় ফেরা (Draft থাকলে `draft_created`, নইলে `received`), `ignored` নয়।

**Task scope**
1. Migration `acc_finance_event_actions` (+ append-only trigger helper)।
2. Service: release/retry, ignore, conflict সমাধান, bulk release (`mapping_key` ধরে)।
3. API (নিচে)।
4. UI (নিচে)।
5. Test।

**Out of scope**
Consumer-এর চলা (`P4-03`); Draft তৈরি (`P4-06`); mapping CRUD (`P4-05`); alert (`P5-03`)।

**Contract**
- `GET /accounting/intake/events?status=&source=&error=&from=&to=`
- `GET /accounting/intake/events/{id}`
- `POST /accounting/intake/events/{id}/retry`, `.../release`, `.../ignore`, `.../resolve-conflict` (`accept` বা `reject`)
- `POST /accounting/intake/release-held` (`mapping_key` ধরে একসাথে, যেমন mapping বসানোর পর)

**UI**
1. গণনা-টাইল: প্রতিটা অবস্থায় কতগুলো।
2. তালিকা (filter সহ), প্রতিটায় কত দিন ধরে আটকে, কারণ।
3. বিস্তারিত পাতা: payload (সুন্দর করে), error, চেষ্টার সংখ্যা, লিঙ্কযুক্ত journal (লিংক), কাজের ইতিহাস।
4. বাটন অবস্থা অনুযায়ী (`can()`): Release, Retry, Ignore (কারণ সহ), Conflict সমাধান (পুরোনো ও নতুন payload পাশাপাশি)।
5. Consumer-এর স্বাস্থ্য-বক্স: সর্বশেষ কখন চলেছে (`acc_intake_heartbeats`), কত পিছিয়ে।
6. গত ৩০ দিনে ignored event-এর তালিকা (Head of Finance পর্যালোচনার জন্য)।

**Permission**
দেখা: `accounting.intake.view`। কাজ: `accounting.intake.manage`

**Error**
`EVT_STATE_INVALID`, `ACC_FORBIDDEN`

**Audit**
`accounting.event.retried`, `accounting.event.released`, `accounting.event.ignored`

**Acceptance**
AC4.2, AC4.10

**Test**
- প্রতিটা বৈধ ও অবৈধ কাজ (transition matrix, মানুষের সারি)।
- `ignore` কারণ ছাড়া -> ব্যর্থ।
- Conflict-এর দুই পথ (`accept`, `reject`; Draft থাকা ও না থাকা দুই অবস্থায়)।
- একই `release` দুইবার (idempotent)।
- Bulk release (`mapping_key` ধরে সবগুলো `held_mapping`)।
- অন্য company-র event (নিষেধ, `ACC_COMPANY_FORBIDDEN`)।
- `acc_finance_event_actions`-এ সরাসরি `UPDATE`/`DELETE` ব্যর্থ।

**Deliverable**
Migration, service, API, UI।

**Sub-tasks**
migration, release ও retry, ignore, conflict, bulk release, API, UI চারটা অংশ, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC4.2, AC4.10 (ops-অংশ) সবুজ।
- সব বাটন ও API permission ও `can()` মানে।

**Architecture constraints**
- অবস্থা বদলের একমাত্র পথ `InboxTransitionService`।
- Actions table append-only।
- UI-তে business rule নকল নয়।

**Open points (Pack-এ অস্পষ্ট)**
- `reject_conflict`-এ "আগের অবস্থায় ফেরা" মানে `conflict`-এ আসার আগের অবস্থা; inbox-এ আগের অবস্থা সংরক্ষণের কোনো column নেই। প্রস্তাব: লিঙ্কের অস্তিত্ব দেখে নির্ণয় (সক্রিয় link থাকলে `draft_created`, নইলে `received`) বা `acc_finance_event_actions.from_status`; নিশ্চিত করতে হবে।
- `accept` conflict-এ "আগে থেকে Draft থাকলে সেটা বাতিল করে নতুনটা বানানো হবে": কে বাতিল করে (এই service নাকি `JournalCancelled` listener) এবং Draft ইতিমধ্যে `submitted`/`approved`/`posted` হলে কী হবে Pack-এ নেই। `posted` Draft বাতিল করা যায় না (Reversal-ই একমাত্র পথ)।
- `ignored` -> `received` (transition table-এ আছে) ops screen-এর কোন বাটন এটা করে (release/retry) নিশ্চিত নয়।
- Bulk release `mapping_key` ধরে: একই `mapping_key` একাধিক source/event-এ থাকলে কোনগুলো ছাড়া পড়বে, তা `source_type`/`event_type` সহ ধরতে হতে পারে।

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

---

## ACC-P4-10 — Reconciliation RC8 (Event থেকে ledger পর্যন্ত)

Project
ERPFlow
Module
Accounting Module
Type
Feature (service)
Title
Reconciliation RC8 — Event থেকে ledger পর্যন্ত
Branch Name
`feature/accounting-recon-rc8`
View Order
63
Description
**Summary**
Reconciliation check RC8 (`P3-13` framework-এ) যোগ করতে হবে: inbox ↔ link ↔ journal মিল, duplicate নেই, posted amount = inbox amount, Asset `accepted` ↔ Accounting `draft_created`/ack সামঞ্জস্য — Asset থেকে ledger পর্যন্ত chain-এ trust।

**Task serial**
`ACC-P4-10 — Reconciliation RC8 (Event থেকে ledger পর্যন্ত)` · Feature

**Depends on**
- `ACC-P4-06` (inbox ↔ link ↔ journal)
- `ACC-P3-13` (Reconciliation framework)

**Relation: আগের task**
- `P3-13` দিয়েছে: framework (প্রতিটা check আলাদা class, নিজের snapshot transaction), `acc_reconciliation_runs` (append-only; `check_code` `RC1`-`RC9`, `result` `pass/fail/error/skipped`, `detail`, `triggered_by`), রাতের job, dashboard, `ReconciliationFailed` event। RC8 সেখানে "Phase 4" হিসেবে রাখা ছিল (`P3-13` শুধু আটটা check class, RC8 বাদে)।
- `P4-06` দিয়েছে `acc_finance_event_journal_links`, journal-এর `event_generated` ধরন ও inbox `draft_created`। RC8.1-RC8.4 এই table গুলো join করে।
- `P4-04` দিয়েছে Asset reader; RC8.5-এর জন্য এই task সেই reader-এ আলাদা read-only method `fetchAcceptedIds` যোগ করে।
- `P4-07` দিয়েছে `acknowledged_at`, `ack_attempts` (RC8.5 পড়ে)।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P4-12`: Pack অনুযায়ী এই task `P4-12`-কে অবরুদ্ধ করে; AC4.12 (`EventReconciliationTest`)।
- `P5-03` (monitoring): `recon_failed` alert RC1-RC9 যেকোনোটার `fail`-এ; এই task-এর RC8 তার অংশ (`depends on P4-10`)।
- `P3-11` (Period বন্ধ): `recon_pass` check "RC1 থেকে RC7, আর RC8 যদি intake চালু থাকে, RC9" তে RC8 ধরা হয়।
- `P5-01` UAT U12 (dashboard সব সবুজ, একটা ভাঙলে লাল, AC3.10, AC4.12) ও `P5-08` ধাপ 6 (Asset intake-এর পর্যবেক্ষণ শর্ত "RC8 সবুজ")।

**Related tables / schema**
কোনো নতুন table নেই। ফল `acc_reconciliation_runs`-এ (বিদ্যমান, `P3-13`)। পড়া: `acc_finance_event_inbox`, `acc_finance_event_journal_links`, `acc_journals`, `asset_finance_events` (reader দিয়ে)।

**উপ-যাচাই:**

| উপ-যাচাই | কী মেলায় | ব্যর্থ হলে `detail`-এ |
| --- | --- | --- |
| RC8.1 | প্রতিটা `draft_created` inbox row-এর একটা সক্রিয় link আর একটা অ-বাতিল journal আছে | inbox id-র তালিকা |
| RC8.2 | প্রতিটা `event_generated` journal (পোস্টকৃত) একটা সক্রিয় link-এ জোড়া | journal id-র তালিকা |
| RC8.3 | পোস্টকৃত event-journal-এর `total_debit` = inbox-এর `amount` | (journal, inbox, পার্থক্য) |
| RC8.4 | কোনো inbox identity-র দুটো সক্রিয় journal নেই (DB constraint থাকলেও স্বাধীন যাচাই) | inbox id |
| RC8.5 | Source-এর দিকে: Asset-এ `accepted` অথচ Accounting-এ inbox row বা `draft_created` নেই, বা উল্টো `draft_created` কিন্তু Asset-এ এখনো `pending` আর `ack_attempts` শেষ | (source id, কারণ) |
| RC8.6 (সতর্কতা, `fail` নয়) | `draft_created` Draft যা `monitor_approval_stuck_days`-এর বেশি আটকে | journal id |

**Business rules**
- RC8.1 থেকে RC8.5-এর যেকোনো একটা ব্যর্থ হলে RC8 `fail`।
- RC8.6 শুধু `detail.warnings`-এ, `fail` নয়।
- P3-13-এর framework-এর একটা check class; একই রাতের job ও dashboard-এ।
- RC8.1-RC8.4 স্বাধীন যাচাই, যদিও DB constraint আছে।

**DB flow**
1. Snapshot transaction।
2. RC8.1 থেকে RC8.4 শুধু Accounting-এর table-এ join।
3. RC8.5 Asset-এর outbox পড়ে (`P4-04`-এর reader দিয়ে, আলাদা read-only method `fetchAcceptedIds`)।
4. কোনো লেখা নেই, শুধু ফল `acc_reconciliation_runs`-এ।

**Task scope**
1. `RC8` check class (ছয়টা উপ-যাচাই)।
2. Source-দিকের reader method `fetchAcceptedIds` (read-only; একই static test)।
3. Framework-এ নিবন্ধন (রাতের job, manual run, period close-এ দেখা, dashboard)।
4. Test।

**Out of scope**
Framework নিজে (`P3-13`); alert (`P5-03`); মেলেনি এমন data ঠিক করা (সেটা মানুষের কাজ, `P4-09`)।

**Contract**
P3-13-এর বিদ্যমান API (`POST /accounting/reconciliation/run`, `GET .../latest`, `GET .../runs`) এ RC8 যোগ হয়। নতুন endpoint নেই।

**Permission**
`accounting.reconcile.run` (`P3-13`-এর মতো)

**Error**
`RPT_RECON_FAILED` (তথ্য হিসেবে, error নয়, report-এর সাথে)

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

**Acceptance**
AC4.12

**Test**
- সুস্থ data-য় `pass`।
- প্রতিটা উপ-যাচাইয়ের জন্য ইচ্ছা করে ভাঙা data-য় `fail` (শুধু test পরিবেশে)।
- RC8.6 শুধু warning দেয়, `fail` নয়।
- `error` পথ (check নিজেই ব্যর্থ)।

**Deliverable**
`RC8` check class, test।

**Sub-tasks**
ছয়টা উপ-যাচাই, source-দিকের reader method, framework-এ নিবন্ধন, test

**Definition of done**
- সাধারণ DoD পূরণ।
- AC4.12 সবুজ।
- RC8 `P3-13`-এর dashboard-এ ধূসর (skipped) না থেকে ফল দেখায় (intake চালু থাকলে)।

**Architecture constraints**
- কোনো লেখা নেই (ফল ছাড়া)।
- Asset-এ কিছু লেখা যাবে না; reader শুধু-পড়া।
- নতুন framework নয়; `P3-13`-এর class কাঠামো।

**Open points (Pack-এ অস্পষ্ট)**
- `monitor_approval_stuck_days` registry-তে Phase 5 setting হিসেবে চিহ্নিত, কিন্তু RC8.6 Phase 4-এ এটা পড়ে। Registry (`P1-03`) সব key একসাথে রাখে ধরে নেওয়া; নিশ্চিত করতে হবে key Phase 4 শুরুতেই registry-তে আছে।
- RC8.5-এ "`ack_attempts` শেষ" মানে কোন সীমা, `P4-07`-এর open point দেখুন।
- Intake চালু না থাকলে (flag বন্ধ) RC8 `skipped` হবে কি না Pack বলেনি; `P3-11` শর্ত "RC8 যদি intake চালু থাকে" সেদিকে ইঙ্গিত দেয়।

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

---

## ACC-P4-12 — Phase 4-এর test suite

Project
ERPFlow
Module
Accounting Module
Type
Test
Title
Phase 4-এর test suite ও regression
Branch Name
`feature/accounting-phase4-tests`
View Order
64
Description
**Summary**
Phase 4-এর সব Acceptance Criteria (AC4.1–AC4.13) এক suite-এ automated test দিয়ে cover করতে হবে, সাথে write-off থেকে Post পর্যন্ত একটা full E2E। Asset suite ও পুরো ERPFlow regression সবুজ — তবেই Phase 4 exit।

**Task serial**
`ACC-P4-12 — Phase 4-এর test suite` · Test

**Depends on**
- `ACC-P4-02` থেকে `ACC-P4-11` সব (P4-02, 03, 04, 05, 06, 07, 08, 09, 10, 11)

**Relation: আগের task**
প্রতিটা Phase 4 task নিজের test লিখেছে। এই task সেগুলোকে AC ধরে এক জায়গায় আনে, ফাঁক ধরে, failure-injection helper বানায়, আর Asset ও বিদ্যমান ERPFlow regression দেখে। বিশেষ করে: `P4-02` (replay, state), `P4-03` (flag, isolation, process kill), `P4-04` (reader, static test), `P4-05` (mapping), `P4-06` (Draft, concurrency), `P4-07` (acknowledge, parity), `P4-08` (SoD), `P4-09` (ops), `P4-10` (RC8), `P4-11` (flag matrix)।

**Relation: পরের task (কে অপেক্ষা করছে)**
- `P4-13` (runbook, demo, release প্রস্তুতি)।
- **Phase 5 শুরু:** Phase 4 test সবুজ না হলে Phase 5 শুরু নয়।
- `P5-05` (শেষ security ও performance যাচাই) সরাসরি এই task-এর ওপর (`depends on P4-12`)।

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

**AC থেকে test:**

| AC | Test class (প্রস্তাব) | মূল দৃশ্য |
| --- | --- | --- |
| AC4.1 | `EventReplayTest` | একই event ১০ বার, একটাই journal |
| AC4.2 | `MappingMissingTest` | অসম্পূর্ণ journal কখনো তৈরি হয় না |
| AC4.3 | `EventTraceabilityTest` | Event থেকে posted line আর উল্টো |
| AC4.4 | `NoAutoPostTest` | Consumer ও `DraftBuilder` থেকে `PostingService`-এ কোনো পথ নেই (static, code-ঘুরে) |
| AC4.5 | `IntakeFlagMatrixTest` | তিন স্তরের সব সমন্বয় |
| AC4.6 | `IntakeIsolationTest` | দুই company-র event মেশে না |
| AC4.7 | `CurrencyHoldTest` | `held_currency` |
| AC4.8 | Asset-এর বিদ্যমান suite আর `AssetBehaviorParityTest` | Asset-এর আচরণ অপরিবর্তিত |
| AC4.9 | সম্পূর্ণ regression | Project, Payroll, invoice, payment অক্ষত |
| AC4.10 | `IntakeFailureInjectionTest` | Draft তৈরির পর ও Asset-এ ফেরতের আগে process kill, তারপর আবার, কিছু হারায় না |
| AC4.11 | `MappingSodTest` | Mapping-বদলকারী approve করতে পারে না |
| AC4.12 | `EventReconciliationTest` | RC8 |
| AC4.13 | `AcknowledgeFailureTest` | ফেরত জানানো ব্যর্থ হলে কিছু নষ্ট হয় না |

**আরও একটা পূর্ণ শেষ থেকে শেষ test:** Asset-এ আসল write-off চালানো -> outbox -> consumer -> Draft -> Submit -> approve -> Post -> TB ও GL-এ আসা -> RC8 সবুজ।

**Business rules**
- **Golden suite-এর (Blueprint ১২.২) ৮টা সবকটা এই Phase-এ সবুজ।**
- সব test MySQL-এ (SQLite-এ trigger, generated column, composite FK, `SKIP LOCKED`, `GET_LOCK` আলাদা আচরণ করে)।
- Failure-injection helper (process kill, exception ছোঁড়া) test পরিবেশে।

**Task scope**
1. প্রতিটা test class (উপরের তালিকা)।
2. Failure-injection helper।
3. শেষ থেকে শেষ test।
4. CI-তে MySQL job; test report।
5. AC4.9: Project, Payroll, invoice, payment অক্ষত (সম্পূর্ণ বিদ্যমান ERPFlow suite সবুজ)।

**Out of scope**
নতুন feature; performance test (Phase 5 `P5-05`); UAT (`P5-01`)।

**Deliverable**
সব test class, শেষ থেকে শেষ test, failure-injection helper, test report।

**Sub-tasks**
প্রতিটা class, failure-injection helper, শেষ থেকে শেষ test

**Definition of done**
- AC4.1 থেকে AC4.13 সবুজ।
- Golden suite-এর ৮টা সবুজ।
- শেষ থেকে শেষ test সবুজ।
- Asset-এর বিদ্যমান suite ও পুরো ERPFlow regression সবুজ।
- MySQL CI job ব্যবহার হয়েছে।

**Architecture constraints**
- Concurrency, trigger, `SKIP LOCKED`, advisory lock test SQLite-এ চালানো যাবে না।
- Test-এর জন্য production code-এ দরজা খোলা নয়; failure injection শুধু test hook দিয়ে।

**Open points (Pack-এ অস্পষ্ট)**
- AC4.9-এর জন্য আলাদা কোনো Phase 4 task নির্দিষ্ট নেই; এই task-এর সম্পূর্ণ regression দিয়েই প্রমাণ হয়।
- Golden suite ৮টা, Blueprint ১২.২-এ সংজ্ঞায়িত (এই Pack-এ নেই); Phase 3 পর্যন্ত ৭টা বলা আছে, ৮ম টা Phase 4-এর, তার বিবরণ Blueprint থেকে নিতে হবে।
- Test class নামগুলো "প্রস্তাব"।

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

---

## ACC-P4-13 — Runbook, demo ও release প্রস্তুতি

Project
ERPFlow
Module
Accounting Module
Type
Operations (`ops`)
Title
Runbook, demo ও release প্রস্তুতি
Branch Name
`feature/accounting-phase4-runbook`
View Order
65
Description
**Summary**
Phase 4 merge gate: সব company-তে intake flag বন্ধ প্রমাণ, runbook-এ Intake অংশ (আটকে থাকা event, conflict, consumer থেমে গেলে, ack fail), accountant demo ×৪ দৃশ্যে লিখিত মতামত। তারপর Phase 5 (UAT) শুরু।

**Task serial**
`ACC-P4-13 — Runbook, demo ও release প্রস্তুতি` · Ops

**Depends on**
- `ACC-P4-12`

**Relation: আগের task**
`P4-12` প্রমাণ করেছে AC4.1-AC4.13, শেষ থেকে শেষ test ও golden suite সবুজ। এখন সেই প্রমাণ নিয়ে merge gate, runbook ও মানুষের মতামত নেওয়া হবে। `P1-14` (Phase 1 runbook ও merge gate) ও `P2-17` (runbook হালনাগাদ) যে runbook শুরু করেছিল, এই task তাতে Intake অংশ যোগ করে।

**Relation: পরের task (কে অপেক্ষা করছে)**
- **Phase 5 শুরু:** Pack বলে এই task Phase 5 শুরুকে অবরুদ্ধ করে।
- সরাসরি `depends on P4-13`: `P5-01` (UAT pack ও সই) ও `P5-04` (Rollback ও জরুরি বন্ধ পরিকল্পনা)।
- `P5-07` (Training) Intake অংশের runbook ব্যবহার করে; `P5-08` শুরুর আগে সব flag বন্ধ প্রমাণ এই task থেকে।

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

**Business rules**
- Phase 4 exit criteria (Blueprint ৯.১০) পূরণ।
- `development`-এ push মানে production deploy, তাই merge gate কঠোর; সব flag সব company-তে বন্ধ থেকে merge।
- Asset-এর ভবিষ্যৎ event (disposal, acquisition) Phase 4-এ করা হয় না, শুধু কী কী লাগবে তার তালিকা (নথি)।

**Task scope**
1. **Runbook-এ Intake অংশ:** আটকে থাকা event, ব্যর্থ, conflict, consumer থেমে গেলে, কীভাবে নিরাপদে বন্ধ করা (flag বন্ধ, `P4-11`), ফেরত-জানানোর ব্যর্থতা (`P4-07`)।
2. **Accountant demo:** স্বাভাবিক write-off, mapping না থাকা ও ঠিক করে ছাড়া, duplicate event, conflict। মতামত লিখে রাখা।
3. **Asset-এর ভবিষ্যৎ event (disposal, acquisition)-এর জন্য কী কী লাগবে তার তালিকা** (Phase 4-এর বাইরে, শুধু নথি)।
4. **সব flag সব company-তে বন্ধ অবস্থায় release প্রস্তুত, প্রমাণসহ।**
5. **Merge gate** (সই করা checklist): flag Default বন্ধ প্রমাণ; প্রতিটা migration পিছনে সামঞ্জস্যপূর্ণ প্রমাণ; বিদ্যমান suite সবুজ; Project/Payroll/Commissioning-এর code অপরিবর্তিত; Asset-এর diff শুধু `P4-07`-এর অনুমোদিত পরিবর্তন।

**Out of scope**
Production-এ flag চালু করা (`P5-06`, `P5-08`); disposal/acquisition বাস্তবায়ন; UAT।

**Acceptance**
Phase 4 exit criteria (Blueprint ৯.১০)

**Test**
কোড test নেই। যাচাই: সব flag বন্ধ প্রমাণের স্ক্রিপ্ট/query ফল; demo-র চারটা দৃশ্য সম্পন্ন; runbook-এ প্রতিটা Intake পরিস্থিতির ধাপ আছে; merge checklist সই।

**Deliverable**
Runbook-এর Intake অংশ, demo-র মতামত, ভবিষ্যৎ event-এর তালিকা, সই করা merge checklist।

**Sub-tasks**
runbook Intake অংশ, demo ×4 দৃশ্য, ভবিষ্যৎ event তালিকা, flag-বন্ধ প্রমাণ, merge checklist

**Definition of done**
- সই করা merge checklist আছে।
- Accountant-এর লিখিত মতামত আছে।
- সব flag সব company-তে বন্ধ, প্রমাণসহ।
- Phase 4 exit criteria (Blueprint ৯.১০) পূরণ।

**Architecture constraints**
- Merge হবে শুধু flag বন্ধ রেখে।
- Phase 4-এ disposal/acquisition ঢোকানো যাবে না।

**Open points (Pack-এ অস্পষ্ট)**
- Phase 4 exit criteria (Blueprint ৯.১০) এই Pack-এ লেখা নেই; Blueprint থেকে তুলে checklist-এ বসাতে হবে।
- Runbook-এর Intake অংশের ঠিক কাঠামো ও "ফেরত-জানানোর ব্যর্থতা"-র ধাপ `P4-07`-এর ack সীমা (open point) ঠিক হলে চূড়ান্ত হবে।

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