# Accounting Module — Functional & Technical Implementation Blueprint

Oct 13, 2026 · @Riajul Hasan

## ১. Blueprint-এর উদ্দেশ্য আর আর্কিটেকচারের মূল নিয়ম

এই doc থেকে WBS, development task, sprint plan আর QA test plan বানানো যাবে। প্রতিটা Phase একই ১০ বিষয়ে লেখা: উদ্দেশ্য ও scope, functional requirement, operational workflow, data flow ও integration, business rule ও validation, role ও permission, data model ও dependency, exception ও edge case, audit ও reporting, আর acceptance ও deliverable। প্রতিটা Phase-এর শেষে WBS-এর ছাঁচে task table আছে।

যেখানে সিদ্ধান্ত এখনো চূড়ান্ত নয়, সেখানে নিজে থেকে কিছু ধরে নেওয়া হয়নি। সেটা **Open Decision** হিসেবে চিহ্নিত (OD-নম্বর সহ), আর পুরো তালিকা ১১ নম্বর section-এ বিকল্প ও প্রভাব সহ আছে।

### ১.১ আর্কিটেকচারের অপরিবর্তনীয় নিয়ম

| # | নিয়ম | কী মানে |
| --- | --- | --- |
| A1 | Accounting আলাদা GL Engine | Chart of Accounts, journal, posting, ledger, report আর audit শুধু Accounting-এর |
| A2 | অন্য module ledger-এ লিখবে না | Project, Payroll, Asset বা অন্য কেউ সরাসরি ledger বা balance বদলাবে না |
| A3 | অন্য module শুধু Financial Event পাঠায় | নির্ধারিত ছাঁচে source transaction-এর তথ্য |
| A4 | Accounting event থেকে Draft Journal বানায় | গ্রহণ, যাচাই, duplicate আটকানো, mapping, তারপর draft |
| A5 | Draft কখনো নিজে post হয় না | অনুমোদিত accountant বা approver দেখে approve করার পর post |
| A6 | শুধু সফল post-এ ledger বদলায় | Draft, submitted, approved অবস্থার amount report-এ আসে না |
| A7 | Posted journal বদলানো বা মোছা যায় না | ভুল ঠিক করতে নিয়ন্ত্রিত Reversal বা Adjustment |
| A8 | পুরো traceability | Source transaction → Financial Event → Draft Journal → Approval → Posting → Ledger, প্রতিটা ধাপ জোড়া |
| A9 | সীমানা স্পষ্ট | Configuration, mapping, approval, posting, period closing, reconciliation, reporting আর audit-এর দায়িত্ব Accounting-এর, অন্য module-এর নয় |
| A10 | সবকিছু Admin Settings থেকে configurable | কোডে বাঁধা মান নেই, Section ৩ দেখুন |

### ১.২ শব্দভাণ্ডার

| শব্দ | মানে |
| --- | --- |
| Financial Event | অন্য module-এর পাঠানো নির্ধারিত ছাঁচের আর্থিক ঘটনার রেকর্ড (যেমন Asset write-off) |
| Source Transaction | যে মূল business লেনদেন থেকে event এসেছে (যেমন write-off-এর request) |
| Inbox | Accounting-এ event গ্রহণ ও প্রক্রিয়ার তালিকা |
| Mapping | কোন event-এর কোন অংশ কোন account-এ যাবে তার নিয়ম |
| Draft Journal | তৈরি হয়েছে কিন্তু এখনো ledger-এ যায়নি এমন journal |
| Post | Approve হওয়া journal ledger-এ বসানো |
| Reversal | মূল journal-এর উল্টো entry দিয়ে প্রভাব শূন্য করা |
| Adjustment | নতুন journal দিয়ে পার্থক্য মেলানো |
| Maker–Checker | যে বানায় আর যে অনুমোদন করে, দুজন আলাদা মানুষ |
| SoD | Segregation of Duties: একই ব্যক্তি পরস্পরবিরোধী কাজ করতে পারবে না |
| Idempotency | একই event বারবার এলেও ফলাফল একবারের মতোই হবে |
| Period Lock | কোনো সময়ের জন্য post বন্ধ রাখা |
| OD-নম্বর | Open Decision, ১১ নম্বর section-এ আছে |

### ১.৩ ERPFlow-এ এখন যা আছে (Phase 0 audit থেকে)

কোনো GL বা Accounting module নেই। কিন্তু company isolation (`BelongsToCompany`, `CompanyScope`), Platform `ApprovalEngine`, `activity_logs`, company-ভিত্তিক `Setting` আর feature flag-এর pattern, আর Asset-এর `asset_finance_events` outbox reuse করা যাবে। ApprovalEngine-এ amount threshold নেই, posted journal-এর DB-level immutability নেই, আর company-তে base currency বা fiscal year field নেই। এগুলো নতুন করে বানাতে হবে বা বাড়াতে হবে।

## ২. Phase breakdown: ক্রম, নির্ভরতা, শুরু ও শেষের শর্ত

Enterprise scope-এ Phase 6–17, release wave ও gate নিচের tab-এ; Phase 0–5 অপরিবর্তিত: ২.১ Enterprise Phase Breakdown (Phase 6–17)

কাজ ছয় Phase-এ, ঠিক এই ক্রমে। প্রতিটা Phase-এর শেষে একটা gate আছে, আর সেটা পার না হলে পরেরটা শুরু হয় না।

&#91;embedded content: Phase roadmap · 6 phases, 6 gates\]

বাঁ দিকে Phase, মাঝখানে সেটা কী দেয়, ডান দিকে পরের Phase-এ যাওয়ার gate। Phase 4 আলাদা করে দাগ দেওয়া, কারণ ওটা শুরুর আগে client-এর লিখিত change request-এর অনুমোদন চায়।

| Phase | উদ্দেশ্য | শুরুর শর্ত (Entry) | শেষের শর্ত (Exit) | নির্ভরতা |
| --- | --- | --- | --- | --- |
| 0. Discovery | কাঠামো যাচাই, architecture, সিদ্ধান্তের তালিকা | Scope doc অনুমোদিত | Blueprint ও ADR-এর লিখিত অনুমোদন, feature branch নিয়ম লেখা | — |
| 1. Foundation | Module, Admin Settings, CoA, opening balance Draft | Phase 0 exit, OD-3, OD-4, OD-13, OD-19 | সব AC1.x সবুজ, accountant নমুনা CoA ও opening Draft মেনেছে | Phase 0 |
| 2. Journal Engine | Journal-এর জীবনচক্র, approval, post, reversal | Phase 1 exit, OD-5, OD-6, OD-8, OD-14 | সব AC2.x সবুজ, accountant সত্যিকারের কয়েকটা journal চালিয়ে মেনেছে | Phase 1 |
| 3. GL, Period, Reports | Ledger, report, period বন্ধ, reconciliation | Phase 2 exit, OD-1, OD-2, OD-15, OD-16, OD-20 | সব AC3.x সবুজ, accountant নমুনা report pack-এ সই করেছে | Phase 2 |
| 4. Asset Pilot | Event inbox, mapping, write-off থেকে Draft | Phase 3 exit, change request অনুমোদন (OD-18), OD-3, OD-10, OD-11, OD-21, OD-22 | সব AC4.x সবুজ, flag বন্ধ অবস্থায় release প্রস্তুত | Phase 3 |
| 5. UAT ও Release | UAT, cutover মহড়া, company ধরে চালু | Phase 1–4 exit, OD-9, OD-17 | সব AC5.x সবুজ, pilot company-র পর্যবেক্ষণ শেষ, Finance owner-এর লিখিত অনুমোদন | Phase 1–4 |

**ক্রম নিয়ে কথা:**

- Phase গুলো ক্রমে চলে, একসাথে নয়। যা একটু আগে থেকে করা যায়: পরের Phase-এর নকশা ও সিদ্ধান্ত বন্ধ করা (OD), আর test data আর test পরিকল্পনা। কোড লেখা নয়।
- Phase 4 বাদ দিয়ে Phase 1 থেকে 3 আর 5-এর একটা অংশ করে নেওয়া যায়, তাহলে Asset ছাড়া manual accounting চালু করা সম্ভব। এটা সচেতনভাবে নেওয়ার বিকল্প।
- OD-1 আর OD-2 ছাড়া Phase 3-এর Cash Flow আটকে যায়, তাই ওই দুটো সবচেয়ে জরুরি সিদ্ধান্ত।

## ৩. Admin Settings: সবকিছু configurable

যেসব সিদ্ধান্ত client এখনো দেয়নি, সেগুলো কোডে ধরে না নিয়ে Admin Settings-এ রাখা হবে। Admin নিজে মান বসাবে। Company চালু করার আগে সব বাধ্যতামূলক setting পূরণ না হলে system আটকে দেবে (Setup Checklist)। ERPFlow-এর বিদ্যমান company-ভিত্তিক `Setting` (group, key, value) pattern ব্যবহার হবে, group = `accounting`।

### ৩.১ Setting তালিকা

| Setting key (প্রস্তাব) | অপশন | Default | Lock-after-post | Phase | সম্পর্কিত OD |
| --- | --- | --- | --- | --- | --- |
| `accounting.enabled` | চালু / বন্ধ | বন্ধ | না | 1 | — |
| `fiscal_year_start` | মাস ও দিন, company ধরে | নেই (Admin দেবে) | হ্যাঁ | 1 | OD-4 |
| `base_currency` | Currency code, company ধরে | নেই | হ্যাঁ | 1 | OD-3 |
| `currency_precision` | দশমিকের সংখ্যা | নেই | হ্যাঁ | 1 | OD-3 |
| `accounting_basis` | Accrual / Cash | নেই | হ্যাঁ | 1 | OD-1, OD-16 |
| `coa_code_format` | code-এর দৈর্ঘ্য, ধরন অনুযায়ী পরিসর | খোলা | না | 1 | OD-7 |
| `system_accounts` | Opening Balance Equity, Retained Earnings account | নেই | হ্যাঁ | 1 | OD-7 |
| `opening_cutover_date` | একটা তারিখ | নেই | হ্যাঁ | 1 | OD-9 |
| `voucher_format` | prefix, company code, বছর, অঙ্কের সংখ্যা, বছরে নতুন করে শুরু কি না | নেই | হ্যাঁ | 2 | OD-8 |
| `duplicate_reference_mode` | সতর্কতা / আটকে দেওয়া | সতর্কতা | না | 2 | — |
| `attachment_required` | বাধ্যতামূলক / ঐচ্ছিক, কত টাকার ওপরে | ঐচ্ছিক | না | 2 | — |
| `backdate_limit_days` | কত দিন আগের তারিখে journal চলবে | সীমাহীন | না | 2 | OD-5 |
| `approval_rules` | নিচে ৩.২ | খালি | না (নতুন journal-এ কার্যকর) | 2 | OD-6 |
| `period_lock_mode` | বন্ধ / সতর্কতা / কঠোর | সতর্কতা | না | 3 | OD-5 |
| `cashflow_method` | Direct / Indirect | নেই | না | 3 | OD-2 |
| `cash_equivalent_accounts` | account-এ টিক | খালি | না, audit সহ | 3 | OD-2 |
| `cashflow_class` (প্রতি account) | Operating / Investing / Financing | খালি | না, audit সহ | 3 | OD-2 |
| `event_intake.enabled` | চালু / বন্ধ, company ধরে | বন্ধ | না | 4 | — |
| `event_intake.sources.asset_write_off` | চালু / বন্ধ | বন্ধ | না | 4 | OD-10 |
| `account_mappings` | event-এর অংশ → account | খালি | না | 4 | OD-10 |
| `event_currency_policy` | আটকে রাখা / বাতিল | আটকে রাখা | না | 4 | OD-3 |

### ৩.২ Approval rule-এর configuration

প্রতিটা rule-এ থাকবে:

- Company, transaction type (manual, opening, reversal, adjustment, event-generated)
- Amount-এর সীমা (সর্বনিম্ন, সর্বোচ্চ)
- ধাপ (level) আর প্রতি ধাপের approver (role বা নির্দিষ্ট user)
- ধাপগুলো একে একে (sequential) না একসাথে (parallel)
- Condition: নির্দিষ্ট account, cost center বা বিভাগের জন্য আলাদা approver
- Self-approval: অনুমোদিত / নিষিদ্ধ
- Delegation: ছুটিতে থাকলে কাজ অন্যকে দেওয়া, শুরু ও শেষ তারিখ সহ
- Emergency override: কে করতে পারবে, কারণ লেখা বাধ্যতামূলক কি না, পরে কে দেখবে
- কোনো rule না মিললে কী হবে: আটকে যাবে (Default) / একটা fallback approver

### ৩.৩ Lock নিয়ম

যে setting হিসাবের মূল মান বদলে দেয় (আর্থিক বছর, currency, accounting basis, voucher ছাঁচ, system account, cutover তারিখ), সেটা কোনো company-র প্রথম journal post হওয়ার পর বদলানো যাবে না। বদলাতে হলে আলাদা, লিখিত অনুমোদিত migration প্রক্রিয়া লাগবে, যা এই blueprint-এর বাইরে। বাকি setting বদলানো যাবে, তবে প্রতিটা বদলের ইতিহাস থাকবে।

### ৩.৪ Setting বদলানোর নিয়ম

- আলাদা permission: `accounting.settings.view`, `accounting.settings.manage`
- সংবেদনশীল setting (accounting basis, currency, cash equivalent, mapping) বদলাতে দ্বিতীয় একজনের অনুমোদন লাগতে পারে (Maker–Checker), এটা নিজেই একটা configurable চালু/বন্ধ
- প্রতিটা বদলে audit log: কে, কখন, কোন company, পুরোনো মান, নতুন মান, কারণ
- বদলের আগে system দেখায় কী কী প্রভাবিত হবে (যেমন cash equivalent বদলালে Cash Flow report-এর ফল বদলাবে)

### ৩.৫ Setup Checklist (company চালুর শর্ত)

`accounting.enabled` চালু করতে নিচের সবকটা সবুজ হতে হবে:

1. Fiscal year, base currency, precision, accounting basis ঠিক করা
2. CoA তৈরি আর system account নির্ধারিত
3. Voucher ছাঁচ ঠিক করা
4. কমপক্ষে একটা approval rule আছে
5. Role-গুলোর permission দেওয়া আছে
6. Cutover তারিখ ঠিক আর opening balance post হয়েছে (বা Admin পরে করবে বলে স্পষ্ট করে এড়িয়ে গেছে)
7. Report চালানোর জন্য দরকারি mapping (cash equivalent ইত্যাদি) ঠিক, নয়তো Cash Flow বন্ধ থাকবে, অন্য report চলবে

Cash basis আর Direct পদ্ধতি দুটোই আলাদা logic, তাই প্রথম version-এ কোনগুলো বানানো হবে সেটা OD-16-এ client-এর সাথে ঠিক করতে হবে। বানানো না হলে setting-এ ওই অপশন দেখাবে না।

## ৪. সব Phase-এ সাধারণ নিয়ম

নিচের নিয়মগুলো প্রতিটা Phase-এ খাটে। Phase-ভিত্তিক section-এ এগুলো আর পুনরাবৃত্তি করা হয়নি, শুধু নতুন বা আলাদা অংশ লেখা হয়েছে।

### ৪.১ Financial Event-এর ছাঁচ (Phase 4-এ পুরো ব্যবহার, কিন্তু এখন থেকে নকশা ধরে রাখতে হবে)

অন্য module Accounting-কে যে event পাঠাবে তার ন্যূনতম field:

| Field | মানে | বাধ্যতামূলক |
| --- | --- | --- |
| `company_id` | কোন company | হ্যাঁ |
| `source_type` | কোন ধরনের source (যেমন `asset_write_off`) | হ্যাঁ |
| `source_id` | Source-এর মূল ID | হ্যাঁ |
| `event_type` | ঘটনার ধরন | হ্যাঁ |
| `schema_version` | ছাঁচের সংস্করণ | হ্যাঁ |
| `effective_date` | লেনদেনের আসল ব্যবসায়িক তারিখ | হ্যাঁ |
| `amount` ও `currency` | টাকার অঙ্ক ও currency | হ্যাঁ |
| `dimensions` | cost center, project ইত্যাদি | না |
| `payload` | বাকি তথ্য, mapping-এর জন্য | হ্যাঁ |
| `occurred_at` | কখন ঘটেছে | হ্যাঁ |

Asset-এর বর্তমান outbox-এ `effective_date` আর `schema_version` নেই। Phase 4-এর আগে এটা Asset-এ যোগ করা বা Accounting-এ adapter লেখা, দুটোর একটা ঠিক করতে হবে (OD-11-এর সাথে জড়িত)।

### ৪.২ Source Identity আর Idempotency

- Event-এর পরিচয় = `(company_id, source_type, source_id, event_type)`। এই চারটা মিলে unique।
- একই event বারবার এলে নতুন inbox row বা journal হবে না, আগেরটার status ফেরত যাবে।
- System-তৈরি journal-এ একই চারটা field থাকবে এবং unique constraint DB-তেই থাকবে।
- মূল event-এর payload বদলে একই identity-তে আবার এলে সেটা আটকে `conflict` অবস্থায় রাখা হবে, নতুন journal হবে না।

### ৪.৩ Traceability শৃঙ্খল

প্রতিটা posted line থেকে নিচের দিকে নামা যাবে:

1. Posted line → Journal
2. Journal → Approval request আর সিদ্ধান্ত
3. Journal → (system-তৈরি হলে) Inbox record
4. Inbox record → Financial Event
5. Financial Event → Source transaction

উল্টো দিকেও কাজ করতে হবে: source transaction থেকে খুঁজে বের করা যাবে তার journal এখন কোন অবস্থায়।

### ৪.৪ Company context

সব Accounting model `BelongsToCompany` ব্যবহার করবে। Queue বা console job-এ company context স্পষ্ট করে বসাতে হবে, কারণ `CompanyScope` console-এ enforce না হলে সব company-র data খুলে যেতে পারে। প্রতিটা job-এর শুরুতে company বসানো আর শেষে পরিষ্কার করা বাধ্যতামূলক, test দিয়ে প্রমাণ করতে হবে।

### ৪.৫ Role আর Permission (সব Phase মিলিয়ে)

নিচের role-গুলো প্রস্তাব। আসল role ERPFlow-এর বিদ্যমান RBAC-তে বসবে। প্রতিটা permission company ধরে আলাদা।

| Permission slug (প্রস্তাব) | কী করা যায় | Maker–Checker |
| --- | --- | --- |
| `accounting.settings.view` / `manage` | Setting দেখা / বদলানো | সংবেদনশীল setting-এ লাগতে পারে |
| `accounting.coa.view` / `manage` | CoA দেখা / বদলানো | না |
| `accounting.journal.view` | Journal দেখা | না |
| `accounting.journal.create` | Draft বানানো ও submit | Maker |
| `accounting.journal.approve` | Approve / reject | Checker |
| `accounting.journal.post` | Post করা | Checker (policy অনুযায়ী) |
| `accounting.journal.reverse` | Reversal / adjustment শুরু | Maker, approval লাগতে পারে |
| `accounting.period.manage` | Period বন্ধ / খোলা | আলাদা অনুমোদন |
| `accounting.mapping.manage` | Event mapping বদলানো | সংবেদনশীল, audit সহ |
| `accounting.intake.manage` | Inbox দেখা, আটকে থাকা event মেলানো, ব্যর্থ event পুনরায় চালানো | না |
| `accounting.report.view` / `export` | Report দেখা / নামানো | না |
| `accounting.audit.view` | Audit log দেখা | না |

**SoD নিয়ম (সব configurable):** যে journal বানিয়েছে সে নিজে approve বা post করতে পারবে না (Default চালু)। যে mapping বদলায় সে ওই mapping থেকে বানানো journal approve করতে পারবে না (Default চালু)। Setting বদলায় যে, সে ওই বদলের approve করতে পারবে না।

### ৪.৬ Error আর Status কোড

সব validation error একটা স্থির code আর পরিষ্কার বার্তা দেবে, যাতে UI আর test এক ভাষায় কথা বলে।

| Code (প্রস্তাব) | কখন |
| --- | --- |
| `JRN_UNBALANCED` | Debit আর Credit মেলেনি |
| `JRN_ACCOUNT_INVALID` | Account ওই company-র নয় বা inactive |
| `JRN_LINE_INVALID` | Debit আর Credit একসাথে, বা ঋণাত্মক, বা দুটোই শূন্য |
| `JRN_DUPLICATE_REF` | একই reference আগে ব্যবহার হয়েছে |
| `JRN_NOT_APPROVED` | Approve ছাড়া post |
| `JRN_SOD_VIOLATION` | নিজের journal নিজে approve বা post |
| `JRN_PERIOD_LOCKED` | বন্ধ period-এ post |
| `JRN_IMMUTABLE` | Posted journal বদলানোর চেষ্টা |
| `JRN_STATE_INVALID` | ভুল অবস্থা থেকে ভুল কাজ |
| `JRN_CONFLICT` | একই সময়ে অন্য কেউ বদলে ফেলেছে (version মেলেনি) |
| `EVT_DUPLICATE` | একই event আবার এসেছে |
| `EVT_CONFLICT` | একই identity, ভিন্ন payload |
| `EVT_MAPPING_MISSING` | Mapping নেই, event আটকে |
| `EVT_CURRENCY_MISMATCH` | Event-এর currency আর company-র base currency আলাদা |
| `SET_LOCKED` | Lock হয়ে যাওয়া setting বদলানোর চেষ্টা |
| `SET_INCOMPLETE` | Setup Checklist অসম্পূর্ণ |

### ৪.৭ সাধারণ Concurrency নিয়ম

প্রতিটা journal আর setting row-এ version number থাকবে। Update করার সময় দেখা অবস্থার version না মিললে `JRN_CONFLICT`। Post একটাই DB transaction-এ হবে এবং journal row lock করে শুরু হবে, যাতে একই journal দুজন একসাথে post করতে না পারে।

## ৫. Phase 0: Discovery আর Architecture

**অবস্থা:** read-only repository audit শেষ (১৩ অক্টোবর ২০২৬)। Architecture proposal আর policy decision register লেখা আছে। Phase 1 শুরুর আগে কয়েকটা অনুমোদন বাকি।

### ৫.১ উদ্দেশ্য ও scope

**উদ্দেশ্য:** কোড লেখার আগে ERPFlow-এর বিদ্যমান কাঠামো যাচাই করা, Accounting-এর architecture ঠিক করা আর কোন সিদ্ধান্ত কোন কাজ আটকে রাখে তা চিহ্নিত করা।

**Scope-এর ভেতরে:** repository audit, ADR, data model proposal, policy decision register, Asset pilot-এর যোগ্যতা যাচাই, backlog আর test কৌশল।

**Scope-এর বাইরে:** কোনো code, migration, commit বা branch বদল।

### ৫.২ যা শেষ হয়েছে (audit-এর ফল)

| বিষয় | ফল |
| --- | --- |
| Company isolation | `BelongsToCompany` আর `CompanyScope` আছে, reuse যোগ্য |
| Approval | Platform `ApprovalEngine` আছে, কিন্তু amount threshold নেই |
| Audit | `activity_logs` আছে, DB-level immutability নেই |
| GL, CoA, report | কিছুই নেই, সব নতুন |
| Asset outbox | `asset_finance_events`: write-off ও disposal production-এ লেখা হয়, acquisition হয় না, status বদলানোর consumer নেই |
| Feature flag | company-ভিত্তিক `Setting` pattern আছে |
| CI/CD | `development` branch-এ push হলে green CI-এর পর production deploy হয় |

### ৫.৩ Phase 1 শুরুর আগে যা বাকি (Entry criteria)

1. এই blueprint আর ADR-এর অনুমোদন।
2. Accounting-এর কাজ feature branch-এ হবে, `development`-এ সরাসরি নয়, এই সিদ্ধান্ত লিখিত।
3. Module-এর নাম ঠিক (প্রস্তাব: `Accounting`)।
4. OD-3 (currency), OD-4 (আর্থিক বছর), OD-13 (posted journal-এর DB-level immutability-র উপায়) এর অন্তত প্রাথমিক উত্তর। বাকিগুলো Admin Settings-এ রাখা যায়, তাই Phase 1 আটকাবে না।

### ৫.৪ Deliverable

Audit report, ADR, data model proposal, policy register, এই blueprint।

### ৫.৫ WBS (Phase 0-এর বাকি কাজ)

| Workstream | Task | নির্ভরতা | Deliverable | Acceptance |
| --- | --- | --- | --- | --- |
| Governance | Blueprint ও ADR review ও অনুমোদন | Audit report | সই করা অনুমোদন | লিখিত অনুমোদন আছে |
| Governance | Feature branch আর release নিয়ম লেখা | CI/CD audit | Branching ও merge নিয়ম doc | Team সম্মত |
| Governance | OD register-এ owner ও তারিখ বসানো | Blueprint | Owner সহ OD list | প্রতিটা OD-র owner আছে |
| Design | DB-level immutability-র উপায় ঠিক করা (OD-13) | Audit | Design note | DB স্তরে posted row আটকানো প্রমাণ করা যায় |
| Design | Posted representation ঠিক করা (OD-14) | ADR | Design note | Reconciliation কৌশল লেখা |
| Client | Setup Checklist ও decision list পাঠানো | OD register | Client-কে পাঠানো doc | Client পেয়েছে ও মেনে নিয়েছে |

## ৬. Phase 1: Foundation ও Configuration

### ৬.১ Objective ও Scope

**উদ্দেশ্য:** Accounting module-এর ভিত্তি বানানো: নতুন module, company-ভিত্তিক settings, আলাদা Chart of Accounts, আর শুরুর balance ঢোকানোর জায়গা। এই Phase শেষে একটা company-কে Admin নিজে accounting-এর জন্য তৈরি করতে পারবে, কিন্তু লেনদেন post করা যাবে না।

**Scope-এর ভেতরে:**

- `Accounting` module-এর কাঠামো (backend, frontend, navigation, module status)
- Admin Settings (Section ৩-এর Phase 1 setting) আর Setup Checklist
- Company-ভিত্তিক Chart of Accounts
- Accounting period-এর কাঠামো (সতর্কতা-ভিত্তিক, কঠোর আটকানো নয়)
- Journal-এর table আর model (header, line, attachment), যাতে opening balance রাখা যায়
- Opening balance ঢোকানোর screen আর যাচাই
- Accounting-এর permission, audit log আর company isolation

**Scope-এর বাইরে:**

- Approval, post, reversal (Phase 2)। Opening balance Phase 1-এ শুধু Draft হিসেবে জমা থাকে, post হয় Phase 2 চালু হলে।
- General Ledger আর সব report (Phase 3)
- Event intake আর Asset (Phase 4)
- CoA বা opening balance bulk import। এটা scope doc-এ বাদ। দরকার হলে OD-19

### ৬.২ Functional Requirements

**ক. Module কাঠামো:** `Modules/Accounting` তৈরি, `modules_statuses.json`-এ যোগ, frontend-এ `registerModules.ts` আর navigation-এ জায়গা, `accounting.enabled` flag (Default বন্ধ)।

**খ. Settings ও Setup Checklist:** ৩.১-এর Phase 1 setting, ৩.৩-এর lock নিয়ম, ৩.৪-এর বদলানোর নিয়ম, ৩.৫-এর checklist। Checklist-এর প্রতিটা ধাপের অবস্থা (সবুজ, লাল, প্রযোজ্য নয়) screen-এ দেখা যাবে।

**গ. Chart of Accounts:**

- Account তৈরি: code, নাম, ধরন (Asset, Liability, Equity, Revenue, Expense), parent, চালু আছে কি না, `cashflow_class` ও `is_cash_equivalent` (ঐচ্ছিক, Phase 3-এর জন্য)
- Hierarchy: parent-child গাছ। শুধু leaf account-এ journal line বসবে, group account-এ নয়
- Account বদলানো, বন্ধ করা (inactive)
- Company-র মধ্যে code unique, company-ভেদে একই code চলবে
- System account চিহ্নিত করা (Opening Balance Equity, Retained Earnings)

**ঘ. Accounting period কাঠামো:** আর্থিক বছর আর মাসের ভিত্তিতে period তৈরি হবে। Phase 1-এ period শুধু তথ্য হিসেবে থাকে, `period_lock_mode` Phase 3 পর্যন্ত কার্যকর নয়।

**ঙ. Opening Balance:**

- Cutover তারিখ ধরে একটা বিশেষ ধরনের journal (opening journal)
- Account ধরে Debit বা Credit amount হাতে লেখা
- মোট Debit = মোট Credit না মিললে সংরক্ষণ বা submit করতে না দেওয়া
- Cutover তারিখের পর পরিবর্তন করতে হলে নতুন opening journal নয়, Phase 2-এর Adjustment
- অতীতের লেনদেন migrate করা এই Phase-এ নেই

**চ. Permission আর audit:** ৪.৫-এর settings, coa আর journal.create permission। প্রতিটা বদলে audit log।

### ৬.৩ Operational Workflow: একটা company চালু করা

| ধাপ | কে | কী করে | যাচাই / অনুমোদন |
| --- | --- | --- | --- |
| 1 | Admin | Company-র জন্য `accounting.enabled` চালুর চেষ্টা | Checklist অসম্পূর্ণ থাকলে `SET_INCOMPLETE` |
| 2 | Admin | আর্থিক বছর, currency, precision, accounting basis বসায় | Currency code বৈধ কি না, precision সীমার মধ্যে কি না |
| 3 | Accountant / Finance Manager | CoA বানায়, system account চিহ্নিত করে | Code unique, type ঠিক, parent-এর type মেলে |
| 4 | Admin | Voucher ছাঁচ, cutover তারিখ ঠিক করে | ছাঁচের উদাহরণ দেখে নিশ্চিত করা |
| 5 | Admin | Role-গুলোতে permission দেয় | Checklist-এ অন্তত একজন maker, একজন checker |
| 6 | Accountant | Opening balance ঢোকায়, Draft হিসেবে সংরক্ষণ | মোট Debit = মোট Credit |
| 7 | Admin | Checklist সবুজ হলে `accounting.enabled` চালু | সব ধাপ সবুজ |

চালু হওয়ার পর lock হওয়া setting আর বদলানো যাবে না। কিন্তু Phase 2 শেষ না হওয়া পর্যন্ত কোনো journal post হয় না, তাই প্রথম post-এর আগে lock কার্যকর হয় না।

### ৬.৪ Data Flow ও Integration

| দিক | কী |
| --- | --- |
| ইনপুটের উৎস | Admin ও accountant-এর হাতে লেখা। ERPFlow-এর অন্য module থেকে কোনো data আসে না |
| পড়া হয় (read-only) | Company master (`companies`), Configuration-এর `cost_centers` (পরে dimension হিসেবে), Platform-এর `Setting`, RBAC |
| তৈরি বা বদলায় | `accounting_settings`, `accounts`, `accounting_periods`, `journals` (opening ধরনের Draft), `journal_lines`, `journal_attachments`, activity log |
| অন্য module-এ কিছু লেখে না | Project, Payroll, Asset-এর কিছুই ছোঁয়া হবে না |
| Asset-এর জন্য রাস্তা খোলা রাখা | Journal-এ `source_type`, `source_id` field ফাঁকা থাকবে, unique constraint সহ (৪.২) |

### ৬.৫ Business Rules ও Validations

| # | নিয়ম | আচরণ |
| --- | --- | --- |
| R1.1 | Account code company-র মধ্যে unique | `unique(company_id, code)`, ভাঙলে error |
| R1.2 | Account type পরিবর্তন | কোনো posted line থাকলে আটকানো, না থাকলে চলবে |
| R1.3 | Parent-child | Child-এর type parent-এর সমান হতে হবে। Parent leaf হলে এবং তাতে line থাকলে child বসানো যাবে না |
| R1.4 | Account মোছা | Line থাকলে মোছা যাবে না, শুধু inactive। Line না থাকলে মোছা যাবে |
| R1.5 | Account inactive করা | Closing balance শূন্য না হলে সতর্কতা। Admin নিজের সীমা ঠিক করতে পারে (OD-7) |
| R1.6 | System account | মোছা বা inactive করা যাবে না, type বদলানো যাবে না |
| R1.7 | Opening journal-এ শুধু leaf, active account | অন্যথায় `JRN_ACCOUNT_INVALID` |
| R1.8 | Opening journal-এর তারিখ = cutover তারিখ | অন্য তারিখ হলে আটকানো |
| R1.9 | Opening balance-এ সংখ্যা precision-এর বাইরে গেলে | Round নয়, error দেওয়া |
| R1.10 | প্রতিটা Debit বা Credit line-এ শুধু একটা দিক, ঋণাত্মক নয় | `JRN_LINE_INVALID` |
| R1.11 | প্রতি company-তে একটাই active opening journal | দ্বিতীয়টা চাইলে আগেরটা বাতিল (Draft হলে) বা Adjustment (post হলে, Phase 2) |
| R1.12 | Setting lock | ৩.৩ অনুযায়ী `SET_LOCKED` |
| R1.13 | Company isolation | অন্য company-র account বা journal অনুরোধ করলে সরাসরি অস্বীকার, ফাঁকা ফল নয় |

**Idempotency:** Setting আর account তৈরির API-তে client-পাঠানো idempotency key থাকবে, যাতে network ভেঙে আবার চাপলে দুইটা account না হয়।

**Error handling:** ৪.৬-এর code ব্যবহার, প্রতিটা error-এ কোন field-এ সমস্যা তা বলা।

### ৬.৬ User Roles ও Permissions

| কাজ | Admin | Finance Manager | Accountant | Auditor |
| --- | --- | --- | --- | --- |
| Setting দেখা | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ |
| Setting বদলানো | হ্যাঁ | না | না | না |
| সংবেদনশীল setting বদলে দ্বিতীয় অনুমোদন | Checker হিসেবে অন্য Admin বা Finance Manager | হ্যাঁ | না | না |
| CoA দেখা | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ |
| CoA বদলানো | হ্যাঁ | হ্যাঁ | না | না |
| Opening balance Draft লেখা | না | হ্যাঁ | হ্যাঁ | না |
| Opening balance দেখা | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ |
| `accounting.enabled` চালু | হ্যাঁ | না | না | না |

**Maker–Checker:** সংবেদনশীল setting (accounting basis, currency, system account, cutover তারিখ) বদলাতে Maker একজন, Checker অন্য একজন। এটা `settings.require_second_approval` দিয়ে চালু বা বন্ধ করা যায়। **SoD:** যে CoA বদলায় সে নিজে তার opening balance approve করতে পারবে না (Phase 2-এ কার্যকর)।

সব permission company ধরে। এক company-তে Admin হলেও অন্য company-তে কিছুই দেখতে পাবে না।

### ৬.৭ Data Model ও Dependencies

| Entity | মূল field | সম্পর্ক |
| --- | --- | --- |
| `accounting_settings` | company\_id, group, key, value, version, updated\_by | company-র সাথে এক-থেকে-অনেক, `(company_id, key)` unique |
| `accounts` | id, company\_id, code, name, type, parent\_id, is\_leaf, is\_active, is\_system, cashflow\_class, is\_cash\_equivalent | parent নিজের সাথে, company-র সাথে |
| `accounting_periods` | id, company\_id, fiscal\_year, period\_key, start\_date, end\_date, status | company-র সাথে, `(company_id, period_key)` unique |
| `journals` | id, company\_id, journal\_type (opening এখন), voucher\_no, date, status, narration, reference\_no, source\_type, source\_id, version | company-র সাথে, source identity unique |
| `journal_lines` | id, journal\_id, account\_id, debit, credit, line\_narration, cost\_center\_id | journal ও account-এর সাথে, check: Debit বা Credit একটাই |
| `journal_attachments` | journal\_id, file | journal-এর সাথে |
| `accounting_setup_status` | company\_id, step, state | company-র সাথে (checklist) |
| `activity_logs` (বিদ্যমান) | actor, action, subject, before, after | সব entity-র সাথে |

**Dependency:**

- Phase 0-এর সব entry criteria শেষ হতে হবে।
- Company master, RBAC, `Setting`, `activity_logs` Platform থেকে আসে। এগুলোর নিয়ম বদলালে Phase 1 প্রভাবিত।
- DB-level immutability-র উপায় (OD-13) journal table-এর design-এ ধরা থাকতে হবে, না হলে Phase 2-এ migration ভাঙবে।
- Currency আর আর্থিক বছর (OD-3, OD-4) ঠিক না হলে সেগুলো Admin-কে বসাতে হবে, code default দেবে না।

### ৬.৮ Exception ও Edge Cases

| পরিস্থিতি | সিস্টেমের আচরণ |
| --- | --- |
| Checklist অসম্পূর্ণ, তবুও চালু করার চেষ্টা | `SET_INCOMPLETE`, কোন ধাপ বাকি তা দেখায় |
| একই setting দুজন একসাথে বদলায় | version না মিললে দ্বিতীয় জন `JRN_CONFLICT`-এর মতো error পায়, নতুন মান দেখে আবার চেষ্টা করতে হয় |
| Opening balance-এ Debit আর Credit মেলেনি | সংরক্ষণ আটকে, পার্থক্য কত তা দেখায় |
| Opening balance-এ inactive বা group account | `JRN_ACCOUNT_INVALID` |
| Opening journal সংরক্ষণের সময় network ভেঙে আবার চাপা | Idempotency key-এর কারণে একটাই Draft হয় |
| Account inactive করার সময় balance শূন্য নয় | সতর্কতা, নিশ্চিত করলে চলে (OD-7-এর setting অনুযায়ী আটকে দিতেও পারে) |
| Lock হওয়া setting বদলানোর চেষ্টা | `SET_LOCKED`, কারণ আর বিকল্প (migration প্রক্রিয়া) দেখায় |
| অন্য company-র account ID দিয়ে opening line বানানোর চেষ্টা | API-তে অস্বীকার, audit-এ লেখা |
| Cutover তারিখ আর্থিক বছরের শুরুর আগে বা পরে অস্বাভাবিক জায়গায় | সতর্কতা, Admin নিশ্চিত করলে চলবে |
| একই code-এর account দুই company-তে | অনুমোদিত |
| System account মুছতে বা বন্ধ করতে চাওয়া | আটকানো |
| Module বন্ধ করে আবার চালু করা | Data অক্ষত, শুধু access বন্ধ ও চালু হয়, audit-এ লেখা |

### ৬.৯ Audit Trail ও Reporting

**যা audit log-এ যাবে:** setting বদল (পুরোনো ও নতুন মান সহ), account তৈরি, বদল, inactive, opening journal-এর Draft তৈরি ও বদল, `accounting.enabled` চালু বা বন্ধ, permission বদল, checklist-এর অবস্থা বদল, প্রত্যাখ্যাত (rejected) ও অস্বীকৃত (denied) কাজও।

**প্রতিটা log-এ:** কে, কোন company, কখন, কী কাজ, কোন object, আগের মান, পরের মান।

**এই Phase-এর screen ও tracking:**

- Setup Checklist: প্রতি company-র অবস্থা
- Setting history: কোন setting কখন কে বদলেছে
- CoA তালিকা ও hierarchy দেখা, active আর inactive আলাদা ভাবে
- Opening balance Draft-এর সারাংশ: মোট Debit, মোট Credit, পার্থক্য
- Audit log দেখার screen (filter: user, তারিখ, কাজ)

আনুষ্ঠানিক financial report এই Phase-এ নেই, সেগুলো Phase 3-এ।

### ৬.১০ Acceptance Criteria ও Deliverables

| # | শর্ত | কীভাবে প্রমাণ হবে |
| --- | --- | --- |
| AC1.1 | অন্য company-র data কেউ দেখতে বা বদলাতে পারে না | Company isolation test: দুই company, ভিন্ন user, API ও UI দুই স্তরেই |
| AC1.2 | প্রতিটা company-র নিজের CoA | দুই company-তে একই code, আলাদা account |
| AC1.3 | Opening balance না মিললে সংরক্ষণ হয় না | Unbalanced test, পার্থক্য দেখানো |
| AC1.4 | Lock নিয়ম কাজ করে | Lock হওয়া setting বদলের চেষ্টা `SET_LOCKED` পায় |
| AC1.5 | Checklist অসম্পূর্ণ থাকলে চালু হয় না | `SET_INCOMPLETE` test |
| AC1.6 | Permission ছাড়া setting বা CoA বদলানো যায় না | Permission matrix test |
| AC1.7 | সব বদল audit log-এ আছে | Log-এ কে, কখন, আগে ও পরে মান পাওয়া যায় |
| AC1.8 | Queue ও console-এ company context ঠিক | Job test যা context ছাড়া চললে ব্যর্থ হয় |
| AC1.9 | Journal table-এ DB-level guard-এর কাঠামো আছে | Design review, Phase 2-এ কার্যকর হবে |
| AC1.10 | `accounting.enabled` বন্ধ থাকলে কিছু দেখা বা করা যায় না | Flag-বন্ধ test |
| AC1.11 | বিদ্যমান Project, Payroll, Asset-এর কাজ অক্ষত | সম্পূর্ণ বিদ্যমান test suite পাস |

**Deliverable:** `Accounting` module-এর কাঠামো, Admin Settings screen আর Setup Checklist, CoA screen, opening balance Draft screen, permission ও audit, migration, test suite, runbook-এর প্রথম খসড়া।

**Exit criteria:** সব AC সবুজ, code review শেষ, feature branch থেকে `development`-এ merge-এর লিখিত অনুমোদন (flag বন্ধ রেখে), accountant কোনো একটা নমুনা company-র CoA আর opening Draft দেখে মেনে নিয়েছে।

### ৬.১১ WBS (Phase 1)

| Workstream | Feature | Task | নির্ভরতা | Deliverable | Acceptance |
| --- | --- | --- | --- | --- | --- |
| 1.1 Module scaffold | Module কাঠামো | `Modules/Accounting` তৈরি, status, service provider | Phase 0 | Module বুট করে | বন্ধ থাকলে কিছু লোড হয় না |
| 1.1 Module scaffold | Frontend | `registerModules.ts`, navigation, route | 1.1 | Menu আইটেম | Flag বন্ধ থাকলে menu নেই |
| 1.1 Module scaffold | Permission | Slug যোগ `config/actions.php`, role-এ বসানো | 1.1 | Permission তালিকা | Matrix test পাস |
| 1.2 Settings | Settings store | `accounting_settings` table, service, version | 1.1 | Setting API | AC1.4 |
| 1.2 Settings | Settings UI | Admin screen, lock দেখানো, history | 1.2 | UI | Lock হওয়া field বদলানো যায় না |
| 1.2 Settings | Second approval | Sensitive setting-এ Maker–Checker | 1.2 | Flow | Checker ছাড়া বদল হয় না |
| 1.2 Settings | Setup Checklist | `accounting_setup_status`, service, UI | 1.2 | Checklist | AC1.5 |
| 1.3 CoA | Model ও table | `accounts`, hierarchy, constraints | 1.1 | Table ও model | R1.1 থেকে R1.6 test |
| 1.3 CoA | CoA API ও UI | তৈরি, বদল, inactive, গাছ দেখা | 1.3 | Screen | AC1.2 |
| 1.3 CoA | System account | Opening Balance Equity, Retained Earnings চিহ্নিত | 1.3 | Seed ও নিয়ম | R1.6 |
| 1.4 Periods | Period কাঠামো | `accounting_periods`, তৈরির service | 1.2 | Period তালিকা | আর্থিক বছর অনুযায়ী ঠিক সংখ্যা |
| 1.5 Journal base | Journal table | `journals`, `journal_lines`, `journal_attachments`, DB constraint | 1.3 | Migration | Check constraint test |
| 1.5 Journal base | DB guard design | Posted row আটকানোর উপায় (OD-13) design ও প্রমাণ | OD-13 | Design note ও prototype | AC1.9 |
| 1.6 Opening balance | Opening entry | Cutover তারিখ ধরে Draft opening journal | 1.5 | Screen ও API | AC1.3 |
| 1.6 Opening balance | Idempotency | Idempotency key | 1.6 | Service | Retry test |
| 1.7 Audit | Activity log | সব কাজে `ActivityLogService` | 1.1 | Log events | AC1.7 |
| 1.8 Tenancy | Company context | Job ও console-এ context বসানো | 1.1 | Helper ও test | AC1.8 |
| 1.9 QA | Test suite | Isolation, permission, validation, flag test | সব | Test report | AC1.1 থেকে AC1.11 |
| 1.10 Release | Branch ও runbook | Feature branch নিয়ম, runbook খসড়া | Phase 0 | Doc | Review শেষ |

## ৭. Phase 2: Journal Engine ও Approvals

### ৭.১ Objective ও Scope

**উদ্দেশ্য:** হাতে লেখা journal-এর পুরো জীবনচক্র চালু করা: Draft থেকে Submit, Approve, Post, আর ভুল হলে Reversal বা Adjustment। এই Phase শেষে একজন accountant একটা লেনদেন লিখে অনুমোদন নিয়ে ledger-এর জন্য post করতে পারবে। Phase 1-এর opening balance Draft-ও এখানে post হবে।

**Scope-এর ভেতরে:**

- Journal-এর ধরন: manual, opening, reversal, adjustment
- Draft তৈরি ও সম্পাদনা, attachment, কপি করা, বাতিল করা
- Validation (save ও submit-এর সময় আলাদা)
- Configurable Approval: amount-এর সীমা, ধাপ, একে একে বা একসাথে, delegation, override
- Maker–Checker আর SoD
- Posting service (একটাই transaction, অপরিবর্তনীয়)
- Voucher নম্বর তৈরি
- Reversal আর Adjustment
- Approval queue, Draft তালিকা, status-ভিত্তিক তালিকা
- Posting-এর সময় period check-এর জায়গা (শুধু সতর্কতা), কঠোর আটকানো Phase 3-এ

**Scope-এর বাইরে:**

- General Ledger আর report (Phase 3)
- Period বন্ধ ও আবার খোলা (Phase 3)
- Event intake, mapping আর Asset (Phase 4)
- Journal import বা bulk upload। scope doc-এ নেই

### ৭.২ Functional Requirements

**ক. Journal-এর ধরন ও অবস্থা**

| অবস্থা | মানে |
| --- | --- |
| `draft` | লেখা চলছে, শুধু maker দেখে ও বদলাতে পারে |
| `submitted` | Approval-এর অপেক্ষায়, আর বদলানো যায় না |
| `approved` | সব ধাপে অনুমোদিত, post-এর অপেক্ষায় |
| `rejected` | কারো প্রত্যাখ্যানে ফেরত, মন্তব্য সহ |
| `posted` | Ledger-এর জন্য চূড়ান্ত, অপরিবর্তনীয় |
| `cancelled` | Draft বা rejected অবস্থায় maker বাতিল করেছে |
| `reversed` | Posted, আর তার reversal post হয়ে গেছে (শুধু চিহ্ন, ডেটা অক্ষত) |

**খ. Draft সম্পাদনা:** header (company, তারিখ, narration, reference নম্বর) আর অনেক line। প্রতিটা line-এ account, Debit বা Credit, line-এর বিবরণ, ঐচ্ছিক cost center। Attachment যোগ করা। Draft কপি করে নতুন Draft বানানো। Draft মুছে ফেলা (soft delete, audit-এ থাকে)।

**গ. Validation দুই স্তরে:**

- **Save-এ** (হালকা): Account বৈধ কি না, amount ঋণাত্মক নয়, precision ঠিক। অসম্পূর্ণ Draft রাখা যায়।
- **Submit-এ** (কঠোর): কমপক্ষে দুই line, মোট Debit = মোট Credit, প্রতিটা account active ও leaf, তারিখ বৈধ, দরকারি attachment আছে, duplicate reference পরীক্ষা, প্রযোজ্য approval rule পাওয়া গেছে।

**ঘ. Approval:** Submit-এ system rule খোঁজে (company + journal type + amount + condition)। মিললে approval request তৈরি, ধাপ ধরে approver-দের কাছে যায়। প্রতিটা সিদ্ধান্তে মন্তব্য, সময়, কে। Reject হলে `rejected`, maker ঠিক করে আবার submit করে (একই journal, নতুন submission নম্বর)। Maker নিজে withdraw করতে পারে, যদি কেউ এখনো সিদ্ধান্ত না নিয়ে থাকে। Delegation আর emergency override ৩.২-এর configuration অনুযায়ী।

**ঙ. Posting:** শুধু `approved` journal post হয়। একটাই DB transaction, row lock সহ। Post-এর সময় voucher নম্বর বসে, `posted_at`, `posted_by` লেখা হয়, DB স্তরে আর বদলানো বন্ধ হয়। Post-এর সফলতাই একমাত্র জিনিস যা ledger-এর উৎস বদলায়।

**চ. Reversal আর Adjustment:**

- **Reversal:** পুরো posted journal-এর উল্টো entry। Debit আর Credit অদলবদল, নতুন তারিখ (Admin বেছে নেয়, Default আজ), কারণ লেখা বাধ্যতামূলক। একটা journal-এর একটাই পূর্ণ reversal।
- **Adjustment:** আংশিক সংশোধন। নতুন journal, মূল journal-এর সাথে জোড়া, একাধিক হতে পারে।
- দুটোই নতুন Draft, সাধারণ approval ও post-এর মধ্য দিয়ে যায়।
- মূল journal কখনো বদলায় না। Reversal post হলে শুধু মূলটায় `reversed` চিহ্ন বসে।

**ছ. Opening journal post:** Phase 1-এর Draft opening journal এখানে submit, approve আর post হয়। Post হলে `opening_balance_posted` অবস্থা সেট হয়, আর তখন থেকে ৩.৩-এর lock নিয়ম কার্যকর হয়।

**জ. Queue ও তালিকা:** আমার Draft, আমার অনুমোদনের অপেক্ষায়, আমার প্রত্যাখ্যাত, অনুমোদিত কিন্তু post হয়নি, সব journal (filter সহ)।

### ৭.৩ Operational Workflow

**Workflow A: সাধারণ journal**

| ধাপ | কে | কী করে | যাচাই / অনুমোদন |
| --- | --- | --- | --- |
| 1 | Accountant (Maker) | Draft বানায়, line আর attachment যোগ করে | Save-এর হালকা validation |
| 2 | Maker | Submit চাপে | কঠোর validation, rule খোঁজা |
| 3 | System | Approval request তৈরি, প্রথম ধাপের approver ঠিক | Rule না মিললে `APR_NO_RULE`, আটকে |
| 4 | Approver (Checker) | Approve বা Reject, মন্তব্য সহ | SoD: নিজের journal নয় |
| 5 | System | সব ধাপ শেষ হলে `approved` | — |
| 6 | Post-এর অনুমতি আছে এমন user | Post চাপে | সব শর্ত আবার যাচাই, তারপর transaction |
| 7 | System | Voucher নম্বর বসায়, `posted` | Atomic, ব্যর্থ হলে কিছুই বদলায় না |

উদাহরণ: ৩০ হাজার টাকার ভাড়ার journal। Rule: ৫০ হাজারের নিচে এক ধাপ, Finance Manager। Maker submit করে, Finance Manager approve করে, আরেকজন (SoD মানলে) post করে। ৬ লাখ টাকার বেতনের journal-এ দুই ধাপ: Finance Manager তারপর Head of Finance।

**Workflow B: Reject আর আবার পাঠানো**

Approver Reject করে মন্তব্য লেখে (বাধ্যতামূলক)। Journal `rejected` হয়, maker-এর তালিকায় ফেরত আসে। Maker সংশোধন করে আবার submit করলে নতুন approval request তৈরি হয়, পুরোনোটার ইতিহাস থেকে যায়। আগের approver-রা আবার সিদ্ধান্ত দেয়।

**Workflow C: Reversal**

| ধাপ | কে | কী করে |
| --- | --- | --- |
| 1 | `journal.reverse` permission-ওয়ালা user | Posted journal খুলে Reverse চাপে, কারণ লেখে |
| 2 | System | উল্টো Debit-Credit সহ নতুন Draft তৈরি, মূলের সাথে জোড়া |
| 3 | সাধারণ | Submit, approve, post (rule অনুযায়ী) |
| 4 | System | Reversal post হলে মূল journal-এ `reversed` চিহ্ন |

উদাহরণ: ২৫,০০০-এর ভাড়া ভুল করে ২৫,০০,০০০ post হয়েছে। Reversal-এ Bank Debit ২৫,০০,০০০ আর Office Rent Credit ২৫,০০,০০০। তারপর ঠিক amount-এর নতুন journal। মূল ভুলটা ইতিহাসে থাকে।

### ৭.৪ Data Flow ও Integration

| দিক | কী |
| --- | --- |
| ইনপুটের উৎস | Maker-এর লেখা journal, Phase 1-এর opening Draft |
| পড়া হয় | `accounts`, `accounting_settings`, `accounting_periods`, Platform `ApprovalEngine`, RBAC, Company master |
| তৈরি বা বদলায় | `journals` (অবস্থা), `journal_lines`, `journal_status_history`, `approval_*` (বা Platform-এর approval table), voucher counter, `activity_logs` |
| ledger-এর উৎস | Posted `journal_lines`। Phase 3-এ এখান থেকেই GL ও report চলবে |
| অন্য module-এর সাথে | কোনো data আদান-প্রদান নেই |
| Phase 4-এর জন্য | `JournalService` হবে journal তৈরির একমাত্র পথ, যাতে event থেকে বানানো draft-ও একই validation আর approval পায় |

Approval-এর ভিত্তি Platform `ApprovalEngine` নাকি Accounting-এর নিজের rule layer, সেটা **OD-6**। বিকল্প ও প্রভাব ১১ নম্বর section-এ।

### ৭.৫ Business Rules ও Validations

| # | নিয়ম | আচরণ |
| --- | --- | --- |
| R2.1 | অবস্থার পরিবর্তন শুধু বৈধ পথে | Draft→Submitted→Approved→Posted; Submitted→Rejected→Draft→Submitted; Draft বা Rejected→Cancelled। অন্য কিছু `JRN_STATE_INVALID` |
| R2.2 | Submit-এ কমপক্ষে দুই line, মোট Debit = মোট Credit | `JRN_UNBALANCED` |
| R2.3 | প্রতিটা line-এর account ওই company-র, active, leaf | `JRN_ACCOUNT_INVALID` |
| R2.4 | প্রতি line-এ Debit বা Credit একটাই, শূন্যের বেশি | `JRN_LINE_INVALID` |
| R2.5 | Journal-এর তারিখ cutover তারিখের আগে হবে না | আটকানো |
| R2.6 | Backdate সীমা | `backdate_limit_days` পেরোলে আটকানো |
| R2.7 | Duplicate reference | `duplicate_reference_mode` অনুযায়ী সতর্কতা বা আটকানো |
| R2.8 | Attachment বাধ্যতামূলক হলে | না থাকলে submit আটকানো |
| R2.9 | Approver-এর যোগ্যতা | Rule-এর role বা user ছাড়া কেউ approve করতে পারবে না |
| R2.10 | SoD | Maker নিজে approve বা post করতে পারবে না (setting-এ বদলানো যায়) |
| R2.11 | Reject-এ মন্তব্য বাধ্যতামূলক | মন্তব্য ছাড়া reject হবে না |
| R2.12 | Post-এর আগে সবকিছু আবার যাচাই | Balance, account, period, permission, অবস্থা, version |
| R2.13 | Post idempotent | একই journal-এ post দুইবার চাপলে দ্বিতীয়বার আগের ফলই ফেরত, আবার post হয় না |
| R2.14 | Posted journal বদলানো বা মোছা যায় না | Application ও DB দুই স্তরেই, `JRN_IMMUTABLE` |
| R2.15 | Reversal | শুধু posted journal-এর, কারণ বাধ্যতামূলক, একটাই পূর্ণ reversal |
| R2.16 | Reversal আর Adjustment-এর company মূলের company-ই | অন্যটা আটকানো |
| R2.17 | Voucher নম্বর | Post-এর সময় বসে, company আর আর্থিক বছর ধরে পরপর ও অনন্য (OD-8 অনুযায়ী) |
| R2.18 | Concurrency | প্রতিটা বদলে `version` মেলাতে হয়, নইলে `JRN_CONFLICT` |
| R2.19 | Approval rule না মিললে | `APR_NO_RULE`, নিজে নিজে approve হয় না |
| R2.20 | Period check | `period_lock_mode` সতর্কতা হলে বন্ধ period-এ post করলে সতর্কতা দেখিয়ে চলতে দেওয়া, কঠোর হলে `JRN_PERIOD_LOCKED` (কার্যকর Phase 3-এ) |

**Error handling:** ৪.৬-এর code। নতুন code: `APR_NO_RULE` (rule নেই), `APR_NOT_ELIGIBLE` (approver-এর যোগ্যতা নেই), `APR_ALREADY_DECIDED` (এই ধাপে সিদ্ধান্ত হয়ে গেছে)।

**Idempotency:** Submit, approve, post আর reversal তৈরির প্রতিটা API idempotency key নেয়। একই key আবার এলে আগের ফলই ফেরত যায়।

### ৭.৬ User Roles ও Permissions

| কাজ | Accountant | Finance Manager | Head of Finance | Auditor | Admin |
| --- | --- | --- | --- | --- | --- |
| Draft বানানো ও submit | হ্যাঁ | হ্যাঁ | হ্যাঁ | না | কনফিগারযোগ্য |
| Approve বা reject | না | হ্যাঁ (rule অনুযায়ী) | হ্যাঁ (rule অনুযায়ী) | না | কনফিগারযোগ্য |
| Post | না | হ্যাঁ (policy অনুযায়ী) | হ্যাঁ | না | কনফিগারযোগ্য |
| Reversal বা Adjustment শুরু | না | হ্যাঁ | হ্যাঁ | না | কনফিগারযোগ্য |
| Approval rule বানানো | না | না | না | না | হ্যাঁ |
| Emergency override | না | না | হ্যাঁ, কারণ সহ | না | কনফিগারযোগ্য |
| সব journal দেখা | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ |
| Delegation দেওয়া | না | নিজের approval-এর জন্য | নিজের approval-এর জন্য | না | কারো জন্য |

এটা প্রস্তাব, আসল role ERPFlow-এর RBAC থেকে আসবে। **Maker–Checker:** প্রতিটা journal-এ Maker আর Checker আলাদা মানুষ। **SoD:** Maker নিজে approve বা post করতে পারে না। Approval rule যে বদলায় সে ওই rule-এ যাওয়া journal-এর approver হতে পারে না, এটাও Default চালু। Emergency override শুধু `accounting.journal.override` permission-ওয়ালা কেউ করতে পারবে, কারণ বাধ্যতামূলক, আর পরে আলাদা একজন সেটা পর্যালোচনা করবে।

### ৭.৭ Data Model ও Dependencies

| Entity | মূল field | সম্পর্ক |
| --- | --- | --- |
| `journals` (Phase 1-এ শুরু) | যোগ: submission\_no, submitted\_at, approved\_at, posted\_at, posted\_by, reverses\_journal\_id, adjusts\_journal\_id, reversal\_reason | নিজের সাথে (reversal ও adjustment) |
| `journal_status_history` | journal\_id, from\_status, to\_status, actor, at, comment | journal-এর সাথে এক-থেকে-অনেক |
| `approval_rules` | company\_id, journal\_type, min\_amount, max\_amount, mode (sequential বা parallel), self\_approval, active | company-র সাথে |
| `approval_rule_levels` | rule\_id, level, approver\_type (role বা user), approver\_id, condition (account, cost center) | rule-এর সাথে |
| `approval_requests` | journal\_id, submission\_no, rule\_id, status, current\_level | journal ও rule-এর সাথে |
| `approval_actions` | request\_id, level, actor, decision, comment, at, delegated\_from | request-এর সাথে |
| `approval_delegations` | delegator, delegate, from, to, company\_id | company-র সাথে |
| `voucher_counters` | company\_id, fiscal\_year, last\_no | company ও বছরের সাথে, row lock দিয়ে বাড়ে |

Approval table-গুলো Platform `ApprovalEngine`-এর table হবে নাকি Accounting-এর নিজস্ব, সেটা OD-6-এর ওপর। ওপরের নকশা সবচেয়ে খারাপ ক্ষেত্রের (Accounting-এর নিজস্ব) জন্য, Platform ব্যবহার হলে কিছু table বাদ যাবে।

**Dependency:**

- Phase 1 পুরো শেষ: CoA, settings, journal base table, DB guard design।
- OD-6 (approval-এর ভিত্তি), OD-8 (voucher নম্বর), OD-13 (DB guard) ঠিক হতে হবে।
- Platform `ApprovalEngine` আর RBAC পরিবর্তনের খবর রাখতে হবে।

### ৭.৮ Exception ও Edge Cases

| পরিস্থিতি | সিস্টেমের আচরণ |
| --- | --- |
| দুজন approver একসাথে একই ধাপে সিদ্ধান্ত দেয় | যে আগে row lock পায় তার সিদ্ধান্ত গণ্য, অন্যজন `APR_ALREADY_DECIDED` |
| Post দুজন একসাথে চাপে | Row lock-এ একজন post করে, অন্যজন আগের ফল পায় (idempotent) |
| Post চলার সময় DB ভেঙে গেল | Transaction rollback, journal `approved` থেকে যায়, voucher নম্বর খরচ হয় না |
| Approval চলাকালীন Rule বদলে গেল | চলমান request পুরোনো rule-এই চলে, নতুন submit নতুন rule পায় |
| Approver ছুটিতে, delegation নেই | Request আটকে থাকে, Admin বা Head of Finance নির্দিষ্ট approver বদলাতে পারে, audit সহ |
| Approver-এর permission তুলে নেওয়া হলো | তার অপেক্ষমাণ কাজ আর দেখা যায় না, Admin পুনরায় নির্ধারণ করে |
| Submit-এর পর Maker বদলাতে চায় | Withdraw করে Draft-এ ফেরত, শুধু যদি কেউ সিদ্ধান্ত না দিয়ে থাকে |
| Approve করা journal-এর account পরে inactive হয়ে গেল | Post-এর সময় আবার যাচাই, `JRN_ACCOUNT_INVALID`, journal `approved`-এই থেকে যায়, কারণ দেখিয়ে maker-কে জানানো |
| Reversal-এর আগেই মূল journal-এ অন্য adjustment | অনুমোদিত, কিন্তু reversal পূর্ণ পরিমাণই উল্টায়, তাই সতর্কতা দেখানো |
| একই journal দুইবার reverse করার চেষ্টা | আটকানো, আগের reversal দেখানো |
| বন্ধ period-এ reversal | OD-15: আজকের তারিখে reverse, নাকি মূলের তারিখে (বন্ধ period খুলে)। সিদ্ধান্ত না হওয়া পর্যন্ত `period_lock_mode` অনুযায়ী আচরণ |
| ভুল তারিখ বা অতীত তারিখ | `backdate_limit_days` অনুযায়ী |
| Approval rule-এ amount-এর ফাঁক (যেমন ১ থেকে ৫০ হাজার আর ৬০ হাজার থেকে, মাঝে ফাঁক) | Rule save করার সময় ফাঁক ও overlap ধরা পড়ে, Admin-কে দেখায় |
| বিদ্যমান approval থাকা journal-এর company-র module বন্ধ | Data অক্ষত, শুধু access বন্ধ |
| Attachment upload ব্যর্থ | Submit আটকে, Draft বজায় |

### ৭.৯ Audit Trail ও Reporting

**Audit log-এ যা যাবে:** journal তৈরি, প্রতিটা অবস্থা বদল, প্রতিটা approval সিদ্ধান্ত (মন্তব্য সহ), delegation, override (কারণ সহ ও পর্যালোচনার অবস্থা), post, voucher নম্বর বরাদ্দ, reversal ও adjustment-এর সংযোগ, প্রত্যাখ্যাত ও ব্যর্থ post-এর চেষ্টা, approval rule-এর বদল, SoD লঙ্ঘনের চেষ্টা।

**Journal-স্তরের ইতিহাস:** `journal_status_history` থেকে প্রতিটা journal-এ পুরো যাত্রা দেখা যায়: কে, কখন, কোন অবস্থা থেকে কোনটায়।

**এই Phase-এর report ও tracking:**

- Journal Register (ওই ধরনের সব journal, filter সহ)
- Approval Register: Pending, Approved, Rejected, Posted
- Approval-এ আটকে থাকা journal-এর তালিকা, কত দিন ধরে, কার কাছে
- Override তালিকা ও পর্যালোচনার অবস্থা
- Reversal আর Adjustment-এর জোড়া দেখা

Trial Balance, GL আর financial statement Phase 3-এ।

### ৭.১০ Acceptance Criteria ও Deliverables

| # | শর্ত | কীভাবে প্রমাণ হবে |
| --- | --- | --- |
| AC2.1 | Unbalanced journal submit বা post হয় না | Unit আর feature test |
| AC2.2 | Rule অনুযায়ী ধাপ আর approver মানা হয় | Amount ও condition অনুযায়ী ভিন্ন rule-এর test |
| AC2.3 | অনুমতিহীন কেউ approval এড়াতে পারে না | Permission আর SoD test, সরাসরি API-তেও |
| AC2.4 | শুধু approved journal post হয় | অন্য অবস্থায় post চেষ্টা `JRN_NOT_APPROVED` |
| AC2.5 | Posted journal চুপচাপ বদলানো বা মোছা যায় না | Application ও সরাসরি DB update-এর চেষ্টা, দুটোই ব্যর্থ |
| AC2.6 | Post atomic | Post-এর মাঝখানে জোর করে ব্যর্থতা এনে দেখানো যে কিছুই বদলায়নি |
| AC2.7 | Concurrent post ও approve নিরাপদ | একসাথে অনেক request পাঠিয়ে test |
| AC2.8 | Reversal মূল journal সংরক্ষণ করে আর জোড়া থাকে | Reversal test, মূল অক্ষত, চিহ্ন বসা |
| AC2.9 | Voucher নম্বর পরপর ও অনন্য | সমান্তরাল post-এ গর্ত বা repeat নেই |
| AC2.10 | সব সিদ্ধান্তে user ও সময় রেকর্ড | Audit log ও status history |
| AC2.11 | Idempotency | একই key-তে দ্বিতীয় call আগের ফল দেয় |
| AC2.12 | Opening journal post হয় এবং lock কার্যকর হয় | Post-এর পর lock হওয়া setting বদলের চেষ্টা `SET_LOCKED` |
| AC2.13 | Rule-এর ফাঁক ও overlap save-এ ধরা পড়ে | Validation test |
| AC2.14 | বিদ্যমান Project, Payroll, Asset-এর কাজ অক্ষত | সম্পূর্ণ regression suite |

**Deliverable:** journal-এর পুরো জীবনচক্রের API ও UI, approval configuration screen, posting service, reversal ও adjustment, voucher নম্বর service, queue আর register, status history, test suite, runbook-এর হালনাগাদ।

**Exit criteria:** সব AC সবুজ, accountant একটা নমুনা company-তে সত্যিকারের কয়েকটা journal (সাধারণ, reject-resubmit, reversal) সম্পূর্ণ চালিয়ে দেখে মেনে নিয়েছে, OD-6 আর OD-8 লিখিতভাবে বন্ধ।

### ৭.১১ WBS (Phase 2)

| Workstream | Feature | Task | নির্ভরতা | Deliverable | Acceptance |
| --- | --- | --- | --- | --- | --- |
| 2.1 Journal lifecycle | State machine | অবস্থা ও বৈধ পরিবর্তন service | Phase 1 | `JournalStateMachine` | R2.1 test |
| 2.1 Journal lifecycle | Draft CRUD | Header, line, attachment, কপি, বাতিল | 2.1 | API ও UI | AC2.1 |
| 2.1 Journal lifecycle | Validation | Save-এর হালকা ও submit-এর কঠোর validator | 2.1 | Validator | R2.2 থেকে R2.8 test |
| 2.1 Journal lifecycle | Concurrency | `version` ও row lock | 2.1 | নিয়ম ও test | AC2.7 |
| 2.2 Approval | Rule model ও UI | Rule, ধাপ, condition, ফাঁক ও overlap যাচাই | OD-6 | Admin screen | AC2.13 |
| 2.2 Approval | Approval resolver | Journal-এর জন্য প্রযোজ্য rule খোঁজা | 2.2 | Service | AC2.2 |
| 2.2 Approval | Request ও action | Sequential, parallel, মন্তব্য | 2.2 | Service ও UI | AC2.2 |
| 2.2 Approval | Delegation ও override | Delegation, override ও পর্যালোচনা | 2.2 | Service ও UI | AC2.10 |
| 2.2 Approval | SoD | Maker আর Checker পৃথক, rule-বদলকারী বাদ | 2.2 | নিয়ম | AC2.3 |
| 2.3 Posting | Posting service | Atomic post, row lock, idempotency | 2.1, 2.2 | `PostingService` | AC2.4, AC2.6, AC2.11 |
| 2.3 Posting | DB guard | Posted row আটকানো (OD-13 অনুযায়ী) | OD-13 | Migration ও test | AC2.5 |
| 2.3 Posting | Voucher service | Counter, ছাঁচ, row lock | OD-8 | Service | AC2.9 |
| 2.3 Posting | Period check hook | `PeriodGuard` (সতর্কতা) | Phase 1 periods | Hook | R2.20 |
| 2.4 Corrections | Reversal | উল্টো Draft তৈরি, জোড়া, চিহ্ন | 2.3 | Service ও UI | AC2.8 |
| 2.4 Corrections | Adjustment | আংশিক সংশোধন Draft | 2.3 | Service ও UI | AC2.8 |
| 2.5 Opening | Opening post | Phase 1-এর Draft submit থেকে post | 2.3 | Flow | AC2.12 |
| 2.6 Queues | Queue ও register | আমার Draft, অপেক্ষমাণ, Journal Register, Approval Register | 2.1, 2.2 | Screen | AC2.10 |
| 2.7 Audit | Log ও history | `journal_status_history`, `activity_logs` | 2.1 | Events | AC2.10 |
| 2.8 QA | Test suite | Lifecycle, permission, concurrency, atomicity, idempotency | সব | Test report | AC2.1 থেকে AC2.14 |
| 2.9 Release | Runbook ও UAT-এর প্রস্তুতি | Runbook হালনাগাদ, accountant demo | 2.8 | Doc ও demo | Exit criteria |

## ৮. Phase 3: General Ledger, Period Control ও Reports

### ৮.১ Objective ও Scope

**উদ্দেশ্য:** Post হওয়া data থেকে নির্ভরযোগ্য General Ledger আর financial report বানানো, আর সময়ের ভিত্তিতে হিসাব বন্ধ ও নিয়ন্ত্রণ করার ব্যবস্থা চালু করা। এই Phase শেষে accountant নিজের company-র Trial Balance, P&L, Balance Sheet আর Cash Flow দেখে মিলিয়ে নিতে পারবে।

**Scope-এর ভেতরে:**

- General Ledger আর account-ভিত্তিক drill-down
- Trial Balance, Profit & Loss, Balance Sheet, Cash Flow Statement
- Account Summary, Opening Balance Report, Audit Report
- Journal Register আর Approval Register-এর পূর্ণ রূপ (Phase 2-এ শুরু)
- Filter, Excel ও PDF export
- Period বন্ধ, আবার খোলা, আর `period_lock_mode` কার্যকর করা
- স্বয়ংক্রিয় reconciliation check

**Scope-এর বাইরে:**

- Group-level একত্রিত report, intercompany elimination (OD-12, প্রথম version-এর পরে)
- Budget, forecast, cost accounting
- Tax বা VAT-এর report, সরকারি জমার ফর্ম
- Bank statement মেলানো
- Event intake আর Asset (Phase 4)

**Cash Flow-এর শর্ত:** scope doc অনুযায়ী Cash Flow MVP-তে আবশ্যক, আর acceptance criteria-তেও আছে। তাই এটা শুধু খসড়া বা stub দিয়ে পার করা যাবে না। OD-1 (accounting basis) আর OD-2 (পদ্ধতি) বন্ধ না হলে Cash Flow-এর কাজ শুরু করা যাবে না, এবং Phase 3-এর exit-এও আটকাবে।

### ৮.২ Functional Requirements

**ক. Ledger-এর ভিত্তি:** শুধু `posted` journal-এর line। Draft, submitted, approved, rejected কখনো আসে না। Ledger-এর উৎস হবে posted `journal_lines` নিজেই (OD-14-এ Option A), আলাদা ledger table নয়। গতির জন্য period ধরে একটা **derived balance cache** (`accounting_period_balances`) রাখা যায়। এটা কখনো উৎস নয়, শুধু হিসাব দ্রুত করার জন্য, আর নতুন post এলে সংশ্লিষ্ট period-এর cache বাতিল হয়ে আবার তৈরি হয়।

**খ. General Ledger:** Account আর তারিখের পরিসর বেছে নিলে দেখায়: শুরুর balance, প্রতিটা লেনদেন (তারিখ, voucher, narration, Debit, Credit, চলতি balance), ওই সময়ের মোট Debit ও Credit, শেষের balance। প্রতিটা লেনদেনে click করলে মূল journal আর তার approval ইতিহাস খোলে।

**গ. Trial Balance:** সব account-এর শুরুর balance, Debit, Credit, শেষের balance। Hierarchy ধরে group account-এ যোগফল। শেষে মোট Debit = মোট Credit পরীক্ষা, না মিললে লাল সতর্কতা। বেছে নেওয়া তারিখ-পরিসর বা একটা তারিখ অনুযায়ী।

**ঘ. Profit & Loss:** নির্বাচিত সময়ের Revenue আর Expense, group অনুযায়ী উপমোট, নিট লাভ বা ক্ষতি। আগের সময়ের সাথে তুলনা ঐচ্ছিক।

**ঙ. Balance Sheet:** নির্দিষ্ট তারিখ অনুযায়ী Asset, Liability, Equity। Equity-তে থাকে Equity account-এর balance, আগের বছরগুলোর জমানো নিট লাভ আর চলতি বছরের নিট লাভ। সমীকরণ: Asset = Liability + Equity, না মিললে লাল সতর্কতা।

**চ. Cash Flow Statement:** `cashflow_method` setting অনুযায়ী। প্রয়োজন: নগদ বা নগদ-সমতুল্য account চিহ্নিত (`is_cash_equivalent`), প্রতিটা account-এর Operating, Investing বা Financing ভাগ (`cashflow_class`)। কোনো দরকারি account-এ ভাগ না থাকলে report চুপচাপ অনুমান করবে না, বরং কোন কোন account বাকি তার তালিকা দিয়ে error দেবে।

**ছ. অন্য report:** Account Summary (account ধরে মোট ও শেষের balance), Opening Balance Report (posted opening journal), Audit Report (reversal, adjustment, approval, post, override), Journal Register ও Approval Register-এর পূর্ণ filter।

**জ. Filter ও export:** Company, তারিখ বা period, account বা group, journal-এর অবস্থা (যেখানে প্রযোজ্য), cost center। Excel ও PDF-এ export। বড় export queue-তে চলবে, প্রস্তুত হলে ডাউনলোড। প্রতিটা export audit log-এ।

**ঝ. Period Control:**

| Period-এর অবস্থা | আচরণ |
| --- | --- |
| `open` | সব কাজ চলে |
| `soft_closed` | Post-এ সতর্কতা, অনুমতি থাকলে চলে |
| `closed` | `period_lock_mode` কঠোর হলে post আটকে, না হলে সতর্কতা |

Period বন্ধ করার আগে যাচাই (configurable): ওই period-এ কোনো `submitted` বা `approved` অথচ post না হওয়া journal নেই, Trial Balance মেলে, reconciliation check পাস। আবার খুলতে আলাদা permission, কারণ, আর অনুমোদন লাগে (Maker–Checker), এবং audit-এ লেখা হয়। হিসাব বন্ধের নিয়ম OD-5-এর ওপর নির্ভর।

**ঞ. Reconciliation check:** নিচের মিলগুলো স্বয়ংক্রিয়ভাবে পরীক্ষা হবে, ইচ্ছামতো চালানো যাবে, আর period বন্ধ করার সময়ও চলবে। ফল সংরক্ষিত থাকবে।

| # | মিল |
| --- | --- |
| RC1 | Trial Balance-এ মোট Debit = মোট Credit |
| RC2 | প্রতিটা account-এর GL-এর শেষের balance = Trial Balance-এর শেষের balance |
| RC3 | Balance Sheet: Asset = Liability + Equity |
| RC4 | Cash Flow-এর শেষের নগদ = Balance Sheet-এর নগদ account-এর যোগফল |
| RC5 | P&L-এর নিট লাভ = Trial Balance-এর Revenue ও Expense account-এর পার্থক্য |
| RC6 | Report-এর মোট = posted journal line-এর যোগফল (সরাসরি SQL দিয়ে স্বাধীন হিসাব) |
| RC7 | Derived cache আর মূল line-এর যোগফল এক |

### ৮.৩ Operational Workflow

**Workflow A: Report চালানো**

| ধাপ | কে | কী করে | যাচাই |
| --- | --- | --- | --- |
| 1 | Accountant বা Auditor | Report বেছে company, তারিখ, filter দেয় | Company-র permission আছে কি না |
| 2 | System | Setting আর mapping দরকার হলে (Cash Flow) যাচাই | অসম্পূর্ণ হলে কী বাকি তা বলে |
| 3 | System | Posted line থেকে (বা বৈধ cache থেকে) হিসাব | Cache পুরোনো হলে বাতিল করে নতুন হিসাব |
| 4 | System | Reconciliation check চালিয়ে ফলের পাশে দেখায় | মিল না থাকলে লাল চিহ্ন |
| 5 | User | Drill-down, export | Export অনুমতি, audit |

**Workflow B: Period বন্ধ করা (উদাহরণ: মার্চ শেষে)**

1. Finance Manager মার্চ বন্ধ করার অনুরোধ করে।
2. System যাচাই করে: মার্চের কোনো journal অনুমোদনের অপেক্ষায় আটকে নেই, Trial Balance মিলছে, RC1 থেকে RC7 সবুজ।
3. কোনো একটা ব্যর্থ হলে কারণ আর সংশ্লিষ্ট journal-এর তালিকা দেখায়, বন্ধ হয় না।
4. সবুজ হলে (সংবেদনশীল হলে দ্বিতীয় জনের অনুমোদনের পর) period `closed` হয়।
5. এপ্রিলে মার্চের তারিখে journal দিতে চাইলে `period_lock_mode` অনুযায়ী সতর্কতা বা আটকে।

**Workflow C: ভুল পাওয়া গেল বন্ধ period-এ**

Period আবার খোলার অনুরোধ (কারণ সহ), অনুমোদন, শুধু নির্দিষ্ট তারিখের জন্য খোলা (পুরো period নয়, যেটা সম্ভব হলে), সংশোধন journal post, আবার বন্ধ। প্রতিটা ধাপ audit-এ। কোন তারিখে reversal হবে সেটা OD-15।

### ৮.৪ Data Flow ও Integration

| দিক | কী |
| --- | --- |
| ইনপুটের উৎস | শুধু posted `journal_lines`, `accounts` (type, `cashflow_class`, `is_cash_equivalent`), `accounting_settings`, `accounting_periods` |
| প্রক্রিয়া | Account আর তারিখ ধরে যোগ, hierarchy roll-up, চিহ্ন নিয়ম (Asset ও Expense Debit-প্রধান, বাকি Credit-প্রধান) |
| তৈরি বা বদলায় | `accounting_period_balances` (cache), `accounting_reconciliation_runs`, period-এর অবস্থা, export ফাইল, audit log |
| Report-এর data সংরক্ষণ | সংরক্ষণ হয় না, প্রতিবার হিসাব হয়। শুধু cache আর reconciliation ফল সংরক্ষিত |
| অন্য module-এর সাথে | কোনো সরাসরি সংযোগ নেই। Phase 4-এর পর event থেকে বানানো journal post হলে তবেই report-এ আসবে |
| Cost center | Report-এ filter বা dimension হিসেবে পড়া যায় (Configuration module থেকে read-only) |

### ৮.৫ Business Rules ও Validations

| # | নিয়ম | আচরণ |
| --- | --- | --- |
| R3.1 | শুধু `posted` journal report-এ আসে | অন্য অবস্থা কখনো নয় |
| R3.2 | Reversal আর মূল journal দুটোই report-এ আসে, প্রভাব শূন্য | মূলটা লুকানো হয় না |
| R3.3 | তারিখের সীমা অন্তর্ভুক্ত (শুরু ও শেষ দিন দুটোই) | সব report-এ একই নিয়ম |
| R3.4 | চিহ্ন নিয়ম একই | Debit-প্রধান account-এর balance = Debit − Credit, বাকিগুলো উল্টো |
| R3.5 | Rounding নেই | Precision-এর বেশি সংখ্যা তৈরিই হয় না |
| R3.6 | Balance Sheet-এ চলতি ও আগের বছরের নিট লাভ ধরা হয় | OD-20-এর সিদ্ধান্ত না হওয়া পর্যন্ত গতিশীল হিসাব (বছর শেষে আলাদা closing journal ছাড়া) |
| R3.7 | Inactive account-এর balance থাকলে report-এ দেখা যায় | শূন্য balance-ওয়ালা দেখানো ঐচ্ছিক filter |
| R3.8 | Cash Flow-এ ভাগহীন account থাকলে | `RPT_MAPPING_INCOMPLETE`, কোন account বাকি তা সহ |
| R3.9 | Cash Flow-এর শেষের নগদ Balance Sheet-এর সাথে না মিললে | লাল চিহ্ন, report দেখায় কিন্তু "মেলেনি" লেখা |
| R3.10 | Export আর screen-এর সংখ্যা এক | Export screen-এর একই query থেকে |
| R3.11 | Company-র বাইরের data কখনো আসে না | সব query company-scoped |
| R3.12 | Cache বৈধ কি না | যে period-এ নতুন post হয়েছে তার cache `stale` হয়, report নতুন করে হিসাব করে |
| R3.13 | Period বন্ধের শর্ত পূরণ না হলে বন্ধ হয় না | `PRD_CLOSE_BLOCKED`, কারণ সহ |
| R3.14 | Period খোলার জন্য কারণ আর অনুমোদন | কারণ ছাড়া নয় |
| R3.15 | বন্ধ period-এ post | `period_lock_mode` অনুযায়ী `JRN_PERIOD_LOCKED` বা সতর্কতা |

**Error handling:** নতুন code `RPT_MAPPING_INCOMPLETE`, `RPT_RECON_FAILED`, `PRD_CLOSE_BLOCKED`, `PRD_REOPEN_DENIED`। Report-এ কোনো reconciliation ব্যর্থ হলে report আটকে না দিয়ে দেখানো হয় (accountant-এর দেখা দরকার), কিন্তু export-এ "মেলেনি" লেখা যোগ হয়।

### ৮.৬ User Roles ও Permissions

| কাজ | Accountant | Finance Manager | Head of Finance | Auditor | Admin |
| --- | --- | --- | --- | --- | --- |
| Report দেখা | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ |
| Report export | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ | কনফিগারযোগ্য |
| Cash Flow-এর mapping বদলানো | না | হ্যাঁ | হ্যাঁ | না | হ্যাঁ |
| Reconciliation চালানো | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ |
| Period বন্ধ করার অনুরোধ | না | হ্যাঁ | হ্যাঁ | না | কনফিগারযোগ্য |
| Period বন্ধ অনুমোদন | না | না | হ্যাঁ | না | কনফিগারযোগ্য |
| Period আবার খোলা | না | অনুরোধ | অনুমোদন | না | কনফিগারযোগ্য |
| Audit Report দেখা | না | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ |

**Maker–Checker:** Period বন্ধ আর আবার খোলায় যে অনুরোধ করে আর যে অনুমোদন দেয় আলাদা মানুষ। Cash Flow-এর mapping যে বদলায় সে ওই বদলের অনুমোদনকারী হতে পারে না। **SoD:** যে journal post করেছে সে ওই journal-এর period বন্ধের একমাত্র অনুমোদনকারী হতে পারে না (configurable)। Report দেখার permission company ধরে, আর export-এর permission আলাদা।

### ৮.৭ Data Model ও Dependencies

| Entity | মূল field | সম্পর্ক |
| --- | --- | --- |
| `accounting_periods` (Phase 1-এ শুরু) | যোগ: status, closed\_at, closed\_by, reopened\_at, reopen\_reason | company-র সাথে |
| `accounting_period_balances` (cache) | company\_id, period\_id, account\_id, debit\_total, credit\_total, as\_of\_version, status | period ও account-এর সাথে। শুধু derived |
| `accounting_reconciliation_runs` | company\_id, period\_id, check\_code, result, difference, run\_at, run\_by | company ও period-এর সাথে |
| `report_exports` | company\_id, report, filters, file, requested\_by, at | company-র সাথে |
| `accounts` | ব্যবহৃত: `cashflow_class`, `is_cash_equivalent` | বিদ্যমান |
| `journal_lines` (Phase 2) | উৎস | সরাসরি পড়া হয় |

**ভাবা দরকার (OD-14):** Ledger-এর উৎস কি posted `journal_lines` নিজেই (Option A, সরল, পরীক্ষা সহজ) নাকি post-এর সময় আলাদা `ledger_entries` projection (Option B, report দ্রুত কিন্তু দুই জায়গা মেলানোর ঝুঁকি)? এই blueprint Option A ধরে লেখা, কারণ reconciliation সরল হয়। সিদ্ধান্ত বদলালে এই section আর Phase 2-এর posting service বদলাবে।

**Dependency:**

- Phase 2 পুরো শেষ এবং অন্তত কিছু posted data আছে।
- OD-1 আর OD-2 ছাড়া Cash Flow, Balance Sheet-এর semantics আর P&L-এর recognition ঠিক হয় না।
- OD-5 ছাড়া period বন্ধের নিয়ম আর `period_lock_mode` কঠোর করা যায় না।
- OD-14, OD-20 report-এর কাঠামোয় প্রভাব ফেলে।
- Export-এর জন্য ERPFlow-এ বিদ্যমান queue আর file storage।

### ৮.৮ Exception ও Edge Cases

| পরিস্থিতি | সিস্টেমের আচরণ |
| --- | --- |
| Report চলার সময় নতুন journal post হলো | Report শুরুর মুহূর্তের snapshot (একই transaction-এর isolation) থেকে চলে, মাঝপথে বদলায় না। তাই এক report-এর ভেতরে সংখ্যা নিজের সাথে মিলে থাকে |
| Cache পুরোনো (`stale`) | অবহেলা করে মূল line থেকে নতুন হিসাব, তারপর cache পুনর্নির্মাণ |
| Cash Flow-এ কিছু account-এর ভাগ নেই | `RPT_MAPPING_INCOMPLETE`, তালিকা সহ |
| Reconciliation ব্যর্থ | Report দেখায় কিন্তু লাল চিহ্ন আর পার্থক্য সহ, period বন্ধ আটকায় |
| Period বন্ধ, কিন্তু একটা journal আগেই `approved` ছিল | বন্ধের যাচাইয়ে ধরা পড়ে, বন্ধ আটকায় |
| বন্ধ period-এ reversal দরকার | OD-15 অনুযায়ী। নিয়ম ঠিক না হলে `period_lock_mode` মেনে সতর্কতা বা আটকানো |
| Period আবার খোলার পর নতুন post | আগের closing-এর ইতিহাস থাকে, আবার বন্ধ করলে নতুন closing রেকর্ড |
| বছর পেরিয়ে তুলনা | আর্থিক বছরের সীমা মেনে |
| Export চলার সময় server ভেঙে গেল | Queue-র job আবার চেষ্টা করে, দ্বিতীয় ফাইল তৈরি না করে (idempotent) |
| বিশাল পরিসরে report | Pagination বা export-এ পাঠানো, screen-এ সীমা |
| Account-এর type বদলে গিয়েছিল (শুরুর দিকে) | Phase 1-এর R1.2 আটকায়। পোস্ট থাকলে বদলানো যায় না |
| দুই company-র একই code-এর account | Report সবসময় company-scoped, মিশে যায় না |
| একই সময়ে দুজন period বন্ধের অনুরোধ করে | প্রথমটা কার্যকর, দ্বিতীয়টা `PRD_CLOSE_BLOCKED` (ইতিমধ্যে বন্ধ) |

### ৮.৯ Audit Trail ও Reporting

**Audit log-এ যা যাবে:** প্রতিটা report চালানো ও export (কে, কোন company, কোন filter), period বন্ধ ও আবার খোলার অনুরোধ, অনুমোদন ও কারণ, reconciliation চালানো ও ফল, Cash Flow-এর mapping বদল, `period_lock_mode` ও `cashflow_method` বদল, বন্ধ period-এ post-এর চেষ্টা (সফল বা ব্যর্থ), cache পুনর্নির্মাণ।

**এই Phase-এর report ও tracking:**

- Trial Balance, General Ledger, P&L, Balance Sheet, Cash Flow, Account Summary, Opening Balance Report, Audit Report
- Reconciliation dashboard: প্রতিটা check-এর সর্বশেষ ফল, কবে চলেছে, কে চালিয়েছে
- Period-এর অবস্থা তালিকা: কোনটা open, soft closed, closed, কে কখন বন্ধ করেছে
- Export ইতিহাস
- বন্ধ period-এ post-এর চেষ্টার তালিকা

### ৮.১০ Acceptance Criteria ও Deliverables

| # | শর্ত | কীভাবে প্রমাণ হবে |
| --- | --- | --- |
| AC3.1 | Trial Balance আর GL posted data-র সাথে মেলে | RC1, RC2, RC6 স্বাধীন SQL-এর সাথে মিলিয়ে |
| AC3.2 | P&L, Balance Sheet আর Cash Flow অনুমোদিত mapping ও নীতি অনুযায়ী | Accountant নমুনা pack যাচাই করে সই করেছে |
| AC3.3 | Balance Sheet সমীকরণ মেলে | RC3 সবুজ |
| AC3.4 | Cash Flow-এর নগদ Balance Sheet-এর সাথে মেলে | RC4 সবুজ |
| AC3.5 | Report শুধু posted data দেখায় | Draft, approved, rejected journal থাকা অবস্থায় সংখ্যা বদলায় না |
| AC3.6 | Company isolation report-এও | দুই company-র test, API ও export দুই স্তরেই |
| AC3.7 | Period বন্ধ আর আবার খোলা নিয়ম মানে | শর্ত ভাঙা ও পূরণ দুই অবস্থায় test |
| AC3.8 | বন্ধ period-এ post `period_lock_mode` অনুযায়ী | সতর্কতা ও কঠোর দুই mode-এ |
| AC3.9 | Export screen-এর সাথে মেলে | কয়েকটা report-এর Excel ও PDF-এর সংখ্যা মিলিয়ে |
| AC3.10 | Cache কখনো ভুল সংখ্যা দেয় না | নতুন post-এর পর cache stale হয়, RC7 সবুজ |
| AC3.11 | Cash Flow-এ ভাগহীন account থাকলে পরিষ্কার error | `RPT_MAPPING_INCOMPLETE` test |
| AC3.12 | বড় data-তেও report গ্রহণযোগ্য সময়ে | নির্ধারিত আকারের data-য় performance test (সীমা Phase শুরুতে ঠিক) |
| AC3.13 | বিদ্যমান Project, Payroll, Asset-এর কাজ অক্ষত | সম্পূর্ণ regression suite |

**Deliverable:** GL, সব report, reconciliation framework আর dashboard, period control, derived cache, export, test suite, accountant-এর সই করা নমুনা report pack, runbook হালনাগাদ।

**Exit criteria:** সব AC সবুজ, accountant সই করেছে, OD-1, OD-2, OD-5, OD-14, OD-15, OD-20 লিখিতভাবে বন্ধ। এই Phase-এর সব সবুজ না হলে Phase 4 শুরু হবে না।

### ৮.১১ WBS (Phase 3)

| Workstream | Feature | Task | নির্ভরতা | Deliverable | Acceptance |
| --- | --- | --- | --- | --- | --- |
| 3.1 Ledger core | Query layer | Posted line থেকে account ও তারিখ ধরে যোগের service | Phase 2, OD-14 | `LedgerQueryService` | RC6 |
| 3.1 Ledger core | Derived cache | `accounting_period_balances`, stale ও rebuild | 3.1 | Cache service | AC3.10 |
| 3.1 Ledger core | Snapshot isolation | Report চলার সময় একই snapshot | 3.1 | নিয়ম ও test | Edge-case test |
| 3.2 GL ও TB | General Ledger | Report, drill-down | 3.1 | API ও UI | AC3.1 |
| 3.2 GL ও TB | Trial Balance | Hierarchy roll-up, মিল পরীক্ষা | 3.1 | API ও UI | AC3.1 |
| 3.3 Statements | Profit & Loss | সময় অনুযায়ী, তুলনা | 3.1, OD-1 | API ও UI | AC3.2 |
| 3.3 Statements | Balance Sheet | তারিখ অনুযায়ী, Equity-তে নিট লাভ (OD-20) | 3.1, OD-1, OD-20 | API ও UI | AC3.3 |
| 3.3 Statements | Cash Flow | Mapping ভিত্তিক, error তালিকা | 3.1, OD-1, OD-2 | API ও UI | AC3.4, AC3.11 |
| 3.3 Statements | Cash Flow mapping UI | Account-এ ভাগ ও নগদ চিহ্ন | OD-2 | Screen | R3.8 |
| 3.4 Other reports | অন্য report | Account Summary, Opening Balance Report, Audit Report, Register-এর পূর্ণ filter | 3.1 | API ও UI | AC3.6 |
| 3.5 Export | Excel ও PDF | Export service, queue, idempotent job | 3.2 থেকে 3.4 | Export | AC3.9 |
| 3.6 Period control | Period বন্ধ | যাচাই, অনুমোদন, অবস্থা | Phase 1 periods, OD-5 | Flow ও UI | AC3.7 |
| 3.6 Period control | Period আবার খোলা | অনুরোধ, কারণ, অনুমোদন | 3.6 | Flow ও UI | AC3.7 |
| 3.6 Period control | Lock কার্যকর | `PeriodGuard` কঠোর mode | 3.6 | Service | AC3.8 |
| 3.7 Reconciliation | Checks | RC1 থেকে RC7 | 3.2, 3.3 | Framework | AC3.1 |
| 3.7 Reconciliation | Dashboard ও schedule | ফল সংরক্ষণ, দেখা | 3.7 | Screen ও job | Dashboard-এ সর্বশেষ ফল |
| 3.8 Performance | নমুনা বড় data | Data তৈরি, index, query-র মাপ | 3.1 | Report | AC3.12 |
| 3.9 Audit | Report ও period-এর log | Export, close, reopen, mapping-এর log | 3.5, 3.6 | Events | Log পাওয়া যায় |
| 3.10 QA | Test suite | Reconciliation, isolation, period, export, performance | সব | Test report | AC3.1 থেকে AC3.13 |
| 3.11 Sign-off | Accountant নমুনা pack | নমুনা company-তে সব report চালিয়ে যাচাই | 3.10 | সই করা pack | AC3.2 |

## ৯. Phase 4: Finance Event Intake ও Asset Pilot

### ৯.১ Objective ও Scope

**উদ্দেশ্য:** অন্য module থেকে আসা Financial Event গ্রহণ করে Draft Journal বানানোর একটা সাধারণ pipeline তৈরি করা, আর সেটা প্রথমে শুধু Asset-এর write-off দিয়ে প্রমাণ করা। এই Phase-এ কিছুই নিজে নিজে post হয় না। Event থেকে শুধু Draft তৈরি হয়, বাকি সব (submit, approve, post) accountant-এর হাতে।

**শুরুর শর্ত (Entry criteria):**

1. Phase 3 পুরো শেষ।
2. **Client-এর লিখিত change request অনুমোদন।** মূল scope-এ Fixed Assets আলাদা workflow হিসেবে আর অন্য module থেকে automatic posting দুটোই বাদ। এই pilot দুটোর সীমানা ছোঁয়।
3. OD-10 (write-off-এর Debit ও Credit mapping আর amount-এর অর্থ) accountant বন্ধ করেছে।
4. OD-3 (currency) বন্ধ, আর Asset-এর currency আর company-র base currency-র সম্পর্ক পরিষ্কার।
5. OD-11 (event-এর `accepted` অবস্থার অর্থ) ঠিক।

**Scope-এর ভেতরে:**

- Source-নিরপেক্ষ Finance Event Inbox
- Consumer job, retry, আটকে থাকা ও ব্যর্থ অবস্থা
- Account mapping (নিজের screen ও permission)
- Asset `write_off` event থেকে Draft Journal
- Inbox-এর screen: তালিকা, আটকে থাকা, ব্যর্থ, আবার চালানো, কারণ লিখে বাদ দেওয়া
- Event থেকে ledger পর্যন্ত reconciliation
- Company-ভিত্তিক feature flag (Default বন্ধ)

**Scope-এর বাইরে:**

- Asset `disposal` (proceeds আর carrying value-র নীতি নেই, event-এর amount শুধু acquisition cost)
- Asset `acquisition` (production code-এ event লেখাই হয় না, AST-1.8.5 বাকি)
- Depreciation (Asset-এর outbox-এ নেই)
- Project invoice ও payment, Payroll, Commissioning (ভবিষ্যৎ roadmap-এ একই pattern)
- নিজে নিজে post, কোনো অবস্থাতেই নয়
- Asset-এর write-off-এর business rule বদলানো
- Asset বা Project-এর বিদ্যমান invoice ও payment-এর আচরণ বদলানো

### ৯.২ Functional Requirements

**ক. Finance Event Inbox**

| অবস্থা | মানে |
| --- | --- |
| `received` | Event গ্রহণ হয়েছে, এখনো প্রক্রিয়া হয়নি |
| `draft_created` | Draft Journal তৈরি হয়েছে |
| `held_mapping` | Mapping নেই বা অবৈধ, journal তৈরি হয়নি |
| `held_currency` | Event-এর currency আর base currency আলাদা, `event_currency_policy` অনুযায়ী আটকে |
| `conflict` | একই identity, ভিন্ন payload |
| `failed` | বারবার চেষ্টার পরও ব্যর্থ, কারণ লেখা |
| `ignored` | কারণ লিখে ইচ্ছা করে বাদ, অনুমতি সহ |

**খ. Consumer job:** নির্ধারিত সময় পর পর (বা queue-তে) চলে। শুধু সেই company-র জন্য চলে যেখানে `accounting.enabled`, `event_intake.enabled` আর `sources.asset_write_off` তিনটাই চালু। প্রতিটা job-এর শুরুতে company context বসে। একই event দুটো worker না ধরার জন্য `skip locked` ধরনের lock। ব্যর্থ হলে বাড়তে থাকা বিরতিতে আবার চেষ্টা, সর্বোচ্চ চেষ্টার পর `failed`।

**গ. Mapping:** `account_mappings`-এ company, source type, event type আর একটা key (যেমন `debit_account`, `credit_account`) থেকে একটা account। Mapping বসাতে আলাদা permission। Account বৈধ হতে হবে: ওই company-র, active, leaf। কোন ধরনের account (Expense, Asset) বসবে সেটা accountant ঠিক করে, system সেটা জোর করে না, কিন্তু অস্বাভাবিক হলে সতর্ক করে।

**ঘ. Draft Journal তৈরি:** `JournalService` দিয়েই, সরাসরি table-এ নয়। Journal-এর ধরন `event_generated`, তারিখ = event-এর `effective_date` (তৈরির তারিখ নয়), narration-এ source-এর নাম ও নম্বর, `source_type` ও `source_id` বসে।

**ঙ. Asset write-off-এর নির্দিষ্ট নিয়ম (OD-10 বন্ধ হলে):**

| Line | Account | Debit | Credit |
| --- | --- | --- | --- |
| 1 | Mapping-এর `debit_account` (যেমন Asset Write-off Loss) | amount |  |
| 2 | Mapping-এর `credit_account` (যেমন সংশ্লিষ্ট Asset-এর cost account) |  | amount |

Amount = write-off-এর `amount`। Accumulated depreciation এখন ধরা হচ্ছে না (OD-10)। Currency base currency-র সমান হতে হবে।

**চ. Asset-এ ফল জানানো:** Asset নিজের table-এর মালিক, তাই Accounting সরাসরি Asset-এর table বদলাবে না (সীমানা A9)। Asset-এ একটা ছোট status-update service থাকবে যা Accounting ডাকে। `accepted` মানে "Draft তৈরি হয়েছে" নাকি "Post হয়েছে", সেটা OD-11। এই blueprint ধরে নিয়েছে: Accounting-এর inbox-ই সত্যের উৎস, Asset-এর status শুধু তথ্য।

**ছ. Approval ও SoD:** Event থেকে বানানো journal সাধারণ approval rule-ই পায় (`event_generated` ধরন)। যে mapping বদলায় সে ওই mapping থেকে বানানো journal approve করতে পারে না।

**জ. Reconciliation:** নতুন check RC8: প্রতিটা `accepted` event-এর একটা journal আছে, প্রতিটা post হওয়া event-journal-এর amount আর event-এর amount সমান, কোনো event-এর দুটো journal নেই।

**ঝ. Feature flag:** তিন স্তর: `accounting.enabled` > `event_intake.enabled` > `sources.asset_write_off`। যেকোনো একটা বন্ধ হলে consumer ওই company-র জন্য কিছু করে না।

### ৯.৩ Operational Workflow

**Workflow A: স্বাভাবিক পথ (উদাহরণ: ৮০,০০০ টাকার laptop write-off)**

| ধাপ | কে | কী করে | যাচাই |
| --- | --- | --- | --- |
| 1 | Asset user | Write-off-এর request, Asset-এর নিজের approval | Asset-এর নিয়ম |
| 2 | Asset (system) | Executor asset-এর অবস্থা বদলায় আর একই transaction-এ outbox-এ `pending` event লেখে | Asset-এর বিদ্যমান unique key |
| 3 | Accounting consumer | Pending event তুলে inbox-এ `received` | Flag চালু, company context |
| 4 | Consumer | Mapping খোঁজে, currency যাচাই | `EVT_MAPPING_MISSING` বা `EVT_CURRENCY_MISMATCH` |
| 5 | Consumer | `JournalService`-এ Draft: Dr Asset Write-off Loss ৮০,০০০, Cr IT Equipment ৮০,০০০, তারিখ = write-off-এর তারিখ | Validation, source identity unique |
| 6 | System | Inbox `draft_created`, Asset-এর status-update service ডাকে | OD-11 অনুযায়ী |
| 7 | Accountant | Draft দেখে, দরকারে সংশোধন, submit | সাধারণ journal নিয়ম |
| 8 | Approver | Approve | SoD |
| 9 | Post-অনুমতিওয়ালা user | Post | Phase 2-এর posting service |
| 10 | System | GL আর report-এ আসে | Reconciliation RC8 |

**Workflow B: Mapping নেই**

ধাপ 4-এ mapping না থাকলে inbox `held_mapping`, কোনো journal তৈরি হয় না, ops screen-এ দেখায়। Finance Manager mapping বসায়। "আবার চালাও" চাপলে event আবার প্রক্রিয়া হয়, একই identity-তে কখনো দুটো journal হয় না।

**Workflow C: Currency মেলেনি**

Asset-এর event-এ currency আর company-র base currency আলাদা হলে `held_currency`। `event_currency_policy` যদি "আটকে রাখা" হয় তাহলে accountant সিদ্ধান্ত না দেওয়া পর্যন্ত থাকে। "বাতিল" হলে `ignored` হয় কারণ সহ। নিজে নিজে currency বদল বা exchange rate হিসাব এই Phase-এ নেই।

**Workflow D: ব্যর্থ event**

Payload অসম্পূর্ণ বা company মেলে না, তখন `failed`, কারণ লেখা। Ops screen থেকে দেখে সংশোধনের পর আবার চালানো যায়। একই identity-র event-এর payload বদলে এলে `conflict`, নিজে নিজে কিছু হয় না, মানুষ দেখে।

### ৯.৪ Data Flow ও Integration

| দিক | কী |
| --- | --- |
| উৎস | Asset-এর `asset_finance_events` (শুধু `write_off`, `pending`) |
| Accounting-এ পড়ার উপায় | Consumer job Asset-এর outbox পড়ে (read-only access বা Asset-এর দেওয়া query service) |
| Accounting-এ তৈরি হয় | `finance_event_inbox`, `account_mappings`, Draft `journals` ও lines, inbox-journal সংযোগ, audit log |
| Asset-এ ফেরত | Asset-এর status-update service-এ `accepted` (বা `void`), Accounting সরাসরি Asset-এর table লেখে না |
| Asset-এর বর্তমান সীমাবদ্ধতা | outbox-এ `effective_date`, `schema_version` নেই, tracking-এর জন্য journal-এর কোনো FK নেই (ইচ্ছাকৃত), `disposal`-এর amount-এ proceeds নেই |
| Payload-এর ঘাটতি পূরণ | `effective_date` আর `schema_version`: Asset-এ যোগ করা, নাকি Accounting-এর adapter-এ Asset-এর write-off source থেকে তারিখ পড়া (OD-21) |
| অন্য module | Project, Payroll, Commissioning-এর সাথে কোনো সংযোগ নেই |

### ৯.৫ Business Rules ও Validations

| # | নিয়ম | আচরণ |
| --- | --- | --- |
| R4.1 | Event-এর পরিচয় `(company_id, source_type, source_id, event_type)` | Unique, DB-তেও |
| R4.2 | একই event আবার এলে | নতুন row বা journal নয়, আগের অবস্থা ফেরত (`EVT_DUPLICATE`) |
| R4.3 | একই identity, ভিন্ন payload | `conflict`, মানুষের সিদ্ধান্ত |
| R4.4 | Company মেলে না (source আর asset আলাদা company) | `failed`, journal নয় |
| R4.5 | Mapping অবৈধ বা অনুপস্থিত | `held_mapping`, অসম্পূর্ণ journal কখনো নয় |
| R4.6 | Currency base currency-র সমান না হলে | `held_currency` |
| R4.7 | Amount শূন্য বা ঋণাত্মক | `failed` |
| R4.8 | Journal-এর তারিখ = `effective_date` | তৈরির তারিখ কখনো নয় |
| R4.9 | Event থেকে বানানো journal নিজে post হয় না | কোনো পথ নেই |
| R4.10 | Event-journal-এর সংযোগ একটাই | একটা event-এর একটা live journal। Draft বাতিল হলে event `held_mapping` এর মতো আবার প্রক্রিয়াযোগ্য, ইচ্ছা করে |
| R4.11 | Consumer job চলার আগে company context | না থাকলে job আরম্ভই হয় না |
| R4.12 | Flag-এর তিন স্তর | যেকোনো বন্ধ হলে ওই company-র জন্য কিছু হয় না |
| R4.13 | Asset-এ ফেরত জানানোর ব্যর্থতা | Journal আর inbox আগের মতো থাকে, status-update পরে আবার চেষ্টা |
| R4.14 | Mapping বদলালে পুরোনো Draft | বদলায় না। শুধু নতুন event নতুন mapping পায় |
| R4.15 | Event তৈরির পর Asset-এর source (write-off) বদলে বা বাতিল হলে | নিজে নিজে কিছু হয় না, OD-22 অনুযায়ী মানুষের সিদ্ধান্ত। Posted হলে Reversal |

**Error handling:** `EVT_DUPLICATE`, `EVT_CONFLICT`, `EVT_MAPPING_MISSING`, `EVT_CURRENCY_MISMATCH`, `EVT_PAYLOAD_INVALID`, `EVT_COMPANY_MISMATCH`। প্রতিটার কারণ inbox-এ লেখা থাকে, ops screen-এ দেখা যায়।

**Idempotency:** Consumer-এর প্রতিটা ধাপ আবার চালানো নিরাপদ। Inbox-এ তোলা, Draft তৈরি আর Asset-এ ফেরত জানানো, তিনটা আলাদাভাবে idempotent, যাতে মাঝখানে ভাঙলেও ঠিক জায়গা থেকে চালু হয়।

### ৯.৬ User Roles ও Permissions

| কাজ | Accountant | Finance Manager | Head of Finance | Auditor | Admin |
| --- | --- | --- | --- | --- | --- |
| Inbox দেখা | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ |
| আটকে থাকা event আবার চালানো | হ্যাঁ | হ্যাঁ | হ্যাঁ | না | কনফিগারযোগ্য |
| Event বাদ দেওয়া (`ignored`) | না | হ্যাঁ, কারণ সহ | হ্যাঁ, কারণ সহ | না | কনফিগারযোগ্য |
| Mapping বসানো ও বদলানো | না | হ্যাঁ | হ্যাঁ | না | হ্যাঁ |
| Event-তৈরি Draft সংশোধন, submit | হ্যাঁ | হ্যাঁ | হ্যাঁ | না | কনফিগারযোগ্য |
| Event-তৈরি journal approve | না | হ্যাঁ (SoD সহ) | হ্যাঁ | না | কনফিগারযোগ্য |
| Feature flag চালু বা বন্ধ | না | না | না | না | হ্যাঁ |
| Reconciliation RC8 চালানো | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ | হ্যাঁ |

**Maker–Checker ও SoD:** যে mapping বদলায় সে ওই mapping থেকে বানানো journal approve করতে পারে না। যে event বাদ দেয় (`ignored`) তার কারণ লেখা বাধ্যতামূলক, আর Head of Finance পরে পর্যালোচনা করে। Flag চালু বা বন্ধ করা Admin-এর কাজ, কিন্তু প্রথমবার চালু করতে আলাদা একজনের সম্মতি (Maker–Checker, configurable)।

### ৯.৭ Data Model ও Dependencies

| Entity | মূল field | সম্পর্ক |
| --- | --- | --- |
| `finance_event_inbox` | id, company\_id, source\_type, source\_id, event\_type, schema\_version, effective\_date, amount, currency, payload, status, error\_code, error\_message, attempts, next\_retry\_at, received\_at, processed\_at | `(company_id, source_type, source_id, event_type)` unique |
| `finance_event_journal_links` | inbox\_id, journal\_id, created\_at | inbox ও journal-এর সাথে এক-থেকে-এক (live) |
| `account_mappings` | id, company\_id, source\_type, event\_type, mapping\_key, account\_id, active, updated\_by | company ও account-এর সাথে, `(company_id, source_type, event_type, mapping_key)` unique |
| `finance_event_actions` | inbox\_id, action (retry, ignore, release), actor, reason, at | inbox-এর সাথে |
| `journals` (Phase 2) | ব্যবহৃত: `journal_type = event_generated`, `source_type`, `source_id` | unique source identity |
| `asset_finance_events` (Asset-এর, বিদ্যমান) | উৎস, Accounting বদলায় না | inbox এর `source_id` এটাই |

**Asset-এর কাঠামো বদলাবে না** (journal-এর FK বা GL-এর column যোগ হবে না, এটা ইচ্ছাকৃত যাতে outbox কখনো ledger বলে ভুল না হয়)। বড়জোর Asset-এ `effective_date` আর `schema_version` (OD-21), আর status-update service।

**Dependency:**

- Phase 3 শেষ, কারণ journal-এর পুরো জীবনচক্র আর report আগে দাঁড়াতে হবে।
- Change request-এর লিখিত অনুমোদন।
- OD-3, OD-10, OD-11, OD-21, OD-22, OD-18।
- Asset-এর `AssetFinanceEventWriter` আর write-off executor-এর বর্তমান আচরণ অপরিবর্তিত।
- Asset-এর AST-1.8.5 (acquisition event) এখনো Todo, তাই acquisition এই Phase-এ আসতেই পারে না।

### ৯.৮ Exception ও Edge Cases

| পরিস্থিতি | সিস্টেমের আচরণ |
| --- | --- |
| একই event দুইবার পৌঁছাল | দ্বিতীয়বার নতুন কিছু তৈরি হয় না, `EVT_DUPLICATE`, আগের ফল |
| Consumer Draft তৈরির মাঝখানে ভেঙে গেল | Transaction rollback, inbox `received`-এ থাকে, আবার চেষ্টায় একটাই Draft |
| Draft তৈরি হয়েছে কিন্তু Asset-এ ফেরত জানানো ব্যর্থ | Journal আর inbox ঠিক থাকে, শুধু ফেরত জানানো আবার চেষ্টা হয় |
| Mapping ছাড়া event | `held_mapping`, কোনো আংশিক journal নয় |
| Mapping-এ account পরে inactive হলো | নতুন event `held_mapping`, পুরোনো Draft বদলায় না |
| Currency আলাদা | `held_currency` |
| একই identity, ভিন্ন payload | `conflict`, মানুষের সিদ্ধান্ত |
| Event-journal Draft বাতিল হলো | Event আবার প্রক্রিয়াযোগ্য, ইচ্ছা করে নতুন Draft হতে পারে (R4.10) |
| Event-journal reject হলো | সাধারণ নিয়ম: maker সংশোধন করে আবার পাঠায়, event-এর অবস্থা বদলায় না |
| Posted event-journal-এ ভুল ধরা পড়ল | Phase 2-এর Reversal বা Adjustment, নতুন event নয় |
| Source write-off পরে বাতিল বা সংশোধিত | নিজে নিজে কিছু নয়, OD-22 |
| বন্ধ period-এর তারিখে event | Draft তৈরি হয় (তারিখ `effective_date`), post-এর সময় `period_lock_mode` অনুযায়ী |
| Flag মাঝপথে বন্ধ করা | নতুন event নেওয়া বন্ধ, আগের Draft-এর কাজ চলে |
| Company-র module বন্ধ | Consumer ওই company-কে বাদ দেয় |
| বিপুল সংখ্যক pending event একসাথে | Batch আর rate সীমা, queue-তে ক্রমে প্রক্রিয়া |
| Worker দুটো একই event ধরতে চায় | `skip locked`, একটাই পায় |
| Payload-এ `effective_date` নেই | OD-21 অনুযায়ী adapter পড়ে, না পেলে `EVT_PAYLOAD_INVALID` |

### ৯.৯ Audit Trail ও Reporting

**Audit log-এ যা যাবে:** প্রতিটা event গ্রহণ ও অবস্থা বদল, Draft তৈরি (কোন event থেকে), mapping তৈরি ও বদল (পুরোনো ও নতুন সহ), প্রতিটা retry, ignore (কারণ সহ ও পর্যালোচনা), flag চালু ও বন্ধ, Asset-এ ফেরত জানানো (সফল বা ব্যর্থ), conflict ও failed-এর কারণ।

**Traceability শৃঙ্খল (৪.৩ অনুযায়ী):** Posted line → Journal → Approval → Inbox → Financial Event → Asset-এর write-off। উল্টোদিকেও: Asset-এর একটা write-off থেকে তার journal এখন কোন অবস্থায় সেটা দেখা যায়।

**এই Phase-এর report ও tracking:**

- Inbox তালিকা: অবস্থা, কত দিন আটকে, কোন কারণে
- আটকে থাকা (mapping ও currency) event-এর আলাদা তালিকা, দৈনিক গণনা
- ব্যর্থ ও conflict event-এর তালিকা
- Mapping-এর পূর্ণ তালিকা ও বদলের ইতিহাস
- Event থেকে ledger পর্যন্ত reconciliation report (RC8): event আছে কিন্তু journal নেই, journal post হয়েছে কিন্তু amount মেলেনি, Draft অনেক দিন ধরে আটকে
- Consumer job-এর স্বাস্থ্য: শেষ কবে চলেছে, কতগুলো, কতগুলো ব্যর্থ

### ৯.১০ Acceptance Criteria ও Deliverables

| # | শর্ত | কীভাবে প্রমাণ হবে |
| --- | --- | --- |
| AC4.1 | একই event বারবার এলেও একটাই journal | Replay test: একই event ১০ বার |
| AC4.2 | Mapping না থাকলে অসম্পূর্ণ journal তৈরি হয় না | `held_mapping` test, journal সংখ্যা শূন্য |
| AC4.3 | Event থেকে ledger পর্যন্ত পুরো traceability | Event থেকে posted line আর উল্টো, দুদিকেই নামা |
| AC4.4 | কিছুই নিজে post হয় না | Consumer-এর সব পথে post-এর call নেই, test আর code review |
| AC4.5 | তিন স্তরের flag কাজ করে | যেকোনো একটা বন্ধ হলে consumer কিছুই করে না |
| AC4.6 | Company context ঠিক | দুই company-র event মিশে যায় না |
| AC4.7 | Currency আলাদা হলে `held_currency` | Test |
| AC4.8 | Asset-এর write-off-এর আচরণ অপরিবর্তিত | Asset-এর বিদ্যমান test suite পাস, আচরণ-তুলনা |
| AC4.9 | Project, Payroll, বিদ্যমান invoice ও payment অক্ষত | সম্পূর্ণ regression suite |
| AC4.10 | Consumer ভেঙে গেলেও ঠিক জায়গা থেকে চালু হয় | Failure injection test |
| AC4.11 | SoD: mapping-বদলকারী ওই journal approve করতে পারে না | Permission test |
| AC4.12 | RC8 সবুজ | Event, inbox, journal আর posted line-এর মিল |
| AC4.13 | Asset-এ ফেরত জানানোর ব্যর্থতা আর কিছু নষ্ট করে না | Failure test |

**Deliverable:** Finance Event Inbox, Consumer job, Mapping screen আর API, Asset `write_off`-এর Draft তৈরি, Inbox-এর ops screen, RC8, flag-ব্যবস্থা, Asset-এর ছোট status-update service (আর OD-21 অনুযায়ী ছোট payload পরিবর্তন), test suite, runbook হালনাগাদ, Asset-এর ভবিষ্যৎ event-এর (disposal, acquisition) জন্য কী লাগবে তার তালিকা।

**Exit criteria:** সব AC সবুজ, accountant একটা নমুনা company-তে সত্যিকারের কয়েকটা write-off-এর পুরো পথ (স্বাভাবিক, mapping নেই, duplicate) দেখে মেনে নিয়েছে, flag সব company-তে বন্ধ অবস্থায়ই release হবে।

### ৯.১১ WBS (Phase 4)

| Workstream | Feature | Task | নির্ভরতা | Deliverable | Acceptance |
| --- | --- | --- | --- | --- | --- |
| 4.0 Governance | Change request | Client-এর লিখিত অনুমোদন | Phase 3 | সই করা doc | Entry criteria |
| 4.0 Governance | OD বন্ধ | OD-3, 10, 11, 21, 22-এর সিদ্ধান্ত | Client | সিদ্ধান্ত নথি | লিখিত |
| 4.1 Inbox | Model ও table | `finance_event_inbox`, unique, status | Phase 3 | Migration | R4.1 test |
| 4.1 Inbox | Status machine | অবস্থা ও পরিবর্তন | 4.1 | Service | AC4.1 |
| 4.2 Consumer | Job | Schedule, batch, skip locked, retry | 4.1 | Job | AC4.10 |
| 4.2 Consumer | Company context | Job-এ context বসানো | 4.2 | Helper | AC4.6 |
| 4.2 Consumer | Asset reader | Asset-এর outbox পড়ার পথ (read-only) | 4.2 | Adapter | AC4.8 |
| 4.3 Mapping | Model ও API | `account_mappings`, validation | Phase 1 CoA | API | R4.5 |
| 4.3 Mapping | Mapping UI | Screen, history | 4.3 | UI | AC4.2 |
| 4.4 Draft তৈরি | Journal তৈরি | `JournalService` দিয়ে event-journal | Phase 2, 4.1 | Service | AC4.4 |
| 4.4 Draft তৈরি | Write-off নিয়ম | Dr ও Cr line, amount, তারিখ | 4.3, OD-10 | Service | AC4.3 |
| 4.4 Draft তৈরি | Event-journal link | `finance_event_journal_links` | 4.4 | Table ও service | AC4.3 |
| 4.5 Asset ফেরত | Status service | Asset-এ ছোট service (OD-11) | OD-11 | Service | AC4.13 |
| 4.5 Asset ফেরত | Payload পরিবর্তন | `effective_date`, `schema_version` (OD-21) | OD-21 | Asset-এর ছোট পরিবর্তন | AC4.8 |
| 4.6 Approval | Event-journal rule | `event_generated` ধরনের rule | Phase 2 | Rule | AC4.11 |
| 4.7 Ops | Inbox screen | তালিকা, retry, ignore, release | 4.1 | UI | AC4.2 |
| 4.7 Ops | Job স্বাস্থ্য | Dashboard | 4.2 | UI | Dashboard-এ শেষ চলা |
| 4.8 Reconciliation | RC8 | Event, inbox, journal, line-এর মিল | 4.4 | Check | AC4.12 |
| 4.9 Flags | তিন স্তর | `accounting.enabled` > `event_intake.enabled` > `sources.*` | Phase 1 | Flag | AC4.5 |
| 4.10 Audit | Log | Inbox, mapping, retry, ignore | 4.1 | Events | Log পাওয়া যায় |
| 4.11 QA | Test suite | Replay, failure injection, isolation, regression | সব | Test report | AC4.1 থেকে AC4.13 |
| 4.12 Release | Runbook ও UAT প্রস্তুতি | Runbook, accountant demo | 4.11 | Doc ও demo | Exit criteria |

## ১০. Phase 5: UAT ও Controlled Release

### ১০.১ Objective ও Scope

**উদ্দেশ্য:** Client-এর accountant ও finance team-কে দিয়ে সত্যিকারের পরিস্থিতিতে পুরো system যাচাই করানো, আসল opening balance দিয়ে cutover-এর মহড়া দেওয়া, আর company-র পর company ধীরে ধীরে চালু করা। এই Phase-এ নতুন feature আসে না। কোড merge হওয়া আর চালু করা এক জিনিস নয়, কারণ `development`-এ push হলে production deploy হয়। তাই flag বন্ধ রেখে merge, আলাদা করে সচেতনভাবে চালু।

**Scope-এর ভেতরে:**

- UAT পরিকল্পনা, পরিস্থিতি-ভিত্তিক test pack, সই করা প্রমাণ
- Cutover-এর মহড়া (আসল বা আসলের মতো opening balance)
- Performance, security আর permission-এর শেষ যাচাই
- Monitoring, alert আর রোজকার reconciliation
- Rollback আর জরুরি বন্ধ করার পরিকল্পনা
- Runbook, training আর support-এর playbook
- Company-ভিত্তিক ধাপে ধাপে চালু (pilot company থেকে বাকিতে)
- চালুর পরের পর্যবেক্ষণের সময়কাল

**Scope-এর বাইরে:**

- নতুন feature বা নতুন report
- Group report, অন্য module-এর integration (Project, Payroll ইত্যাদি)
- Asset-এর disposal, acquisition (Phase 4-এ বাদ ছিল)
- অতীতের লেনদেন migrate করা

### ১০.২ Functional Requirements

**ক. UAT pack:** সব Phase-এর acceptance criteria থেকে পরিস্থিতি বানানো। অন্তত: নতুন company চালু করা, সাধারণ journal, reject আর আবার পাঠানো, reversal, period বন্ধ ও আবার খোলা, সব report-এর মিল, Asset write-off-এর পুরো পথ, mapping নেই, duplicate event, অনুমতি ছাড়া চেষ্টা, company isolation।

**খ. Cutover মহড়া:** Production-এর মতো পরিবেশে আসল opening balance ঢোকানো, post, Trial Balance আর পুরোনো system-এর সাথে মেলানো, মিল না হলে কোথায় ফাঁক তা বের করা। ঠিক করতে কত সময় লাগল তা লেখা।

**গ. Release gate:** প্রতিটা company চালুর আগে সবকটা শর্ত সবুজ: Setup Checklist সবুজ, UAT সই, opening balance post ও মেলানো, আর্থিক ও technical দুই পক্ষের লিখিত অনুমোদন, rollback পরিকল্পনা প্রস্তুত।

**ঘ. Monitoring ও alert:**

| কী দেখা হবে | কখন alert |
| --- | --- |
| Reconciliation RC1 থেকে RC8 | যেকোনো একটা ব্যর্থ হলে সঙ্গে সঙ্গে |
| Consumer job | নির্ধারিত সময়ের বেশি না চললে |
| আটকে থাকা event (`held_*`) | নির্ধারিত সংখ্যা বা সময় পেরোলে |
| ব্যর্থ ও conflict event | যেকোনো একটা হলে |
| Approval-এ অনেক দিন আটকে থাকা journal | নির্ধারিত দিন পেরোলে |
| Post-এর ব্যর্থতা | যেকোনো একটা হলে |
| বন্ধ period-এ post-এর চেষ্টা | সংখ্যা বাড়লে |

**ঙ. Rollback ও জরুরি বন্ধ:**

- প্রথম স্তর: `accounting.enabled` বা নির্দিষ্ট `sources.*` flag বন্ধ। কোনো data মোছে না, শুধু নতুন কাজ থামে।
- দ্বিতীয় স্তর: code-এর আগের version-এ ফেরা (CI/CD-র সুযোগ অনুযায়ী)।
- Migration deploy ব্যর্থ হলে rollback হয় না, তাই প্রতিটা migration পিছনে সামঞ্জস্যপূর্ণ হতে হবে (আগে যোগ, পরে বদল, শেষে মোছা)।

**চ. Training ও support:** ভূমিকা অনুযায়ী ছোট নির্দেশিকা (Accountant, Finance Manager, Admin, Auditor), সাধারণ সমস্যার সমাধান, কাকে কীভাবে জানাতে হবে।

### ১০.৩ Operational Workflow: একটা company চালু

| ধাপ | কে | কী করে | যাচাই |
| --- | --- | --- | --- |
| 1 | Release manager | Release checklist খোলে | সব Phase-এর exit criteria সবুজ |
| 2 | Accountant | Staging-এ cutover মহড়া | Trial Balance পুরোনো system-এর সাথে মেলে |
| 3 | Finance owner | UAT-এর ফল দেখে সই | সব পরিস্থিতি পাস |
| 4 | Admin | Production-এ Setup Checklist সম্পূর্ণ, opening balance post | Checklist সবুজ, Trial Balance মেলে |
| 5 | Admin ও দ্বিতীয় জন | `accounting.enabled` চালু (Maker–Checker) | Release gate |
| 6 | Finance team | নির্দিষ্ট পর্যবেক্ষণ-সময়ে সীমিত ব্যবহার | রোজ RC1 থেকে RC8, alert |
| 7 | Finance owner | পর্যবেক্ষণের শেষে সই | কোনো গুরুতর সমস্যা নেই |
| 8 | Admin | (প্রযোজ্য হলে) `event_intake.enabled` ও `sources.asset_write_off` চালু | আলাদা অনুমোদন |
| 9 | Release manager | পরের company-র জন্য আবার ১ থেকে | — |

Asset event intake সবসময় accounting চালুর আলাদা ধাপ। একসাথে চালু করা হয় না।

### ১০.৪ Data Flow ও Integration

| দিক | কী |
| --- | --- |
| Cutover data-র উৎস | Client-এর বিদ্যমান হিসাব থেকে হাতে ঢোকানো শুরুর balance, কোনো আমদানি tool নয় |
| UAT-এর পরিবেশ | Production-এর মতো কাঠামো, আসল বা মুখোশ-পরানো data |
| Monitoring-এর data | Reconciliation ফল, job-এর অবস্থা, inbox-এর অবস্থা, post-এর ব্যর্থতা |
| অন্য module | কোনো নতুন সংযোগ নয়, শুধু Asset-এর পাইলট চালু বা বন্ধ |
| Release-এর রেকর্ড | কে, কখন, কোন company, কোন flag চালু করল |

### ১০.৫ Business Rules ও Validations

| # | নিয়ম |
| --- | --- |
| R5.1 | UAT সই ছাড়া কোনো company-তে `accounting.enabled` চালু করা যাবে না |
| R5.2 | Setup Checklist সবুজ ও opening balance post না হলে চালু করা যাবে না |
| R5.3 | `accounting.enabled` চালুর জন্য দুজন (Maker–Checker) |
| R5.4 | `event_intake` ও `sources.*` চালুর জন্য আলাদা লিখিত অনুমোদন, accounting চালুর পরেই, একসাথে নয় |
| R5.5 | সব নতুন flag-এর Default বন্ধ। release-এর সময় কোনো flag চালু থাকা অবস্থায় পাঠানো হবে না |
| R5.6 | `development`-এ merge মানে production-এ deploy। তাই merge আগে flag-বন্ধ অবস্থা প্রমাণ করতে হবে |
| R5.7 | Migration পিছনে সামঞ্জস্যপূর্ণ, ধ্বংসাত্মক পরিবর্তন নয় |
| R5.8 | জরুরি বন্ধ (flag) শুধু Admin করে, আর কারণ লিখতে হয় |
| R5.9 | পর্যবেক্ষণের সময়ে RC1 থেকে RC8-এর কোনোটা ব্যর্থ হলে পরের company-র চালু থামে |
| R5.10 | প্রতিটা চালু, বন্ধ ও জরুরি পদক্ষেপ audit-এ |

### ১০.৬ User Roles ও Permissions

| ভূমিকা | দায়িত্ব |
| --- | --- |
| Release manager | Checklist পরিচালনা, কোন company কখন চালু তার সমন্বয় |
| Finance owner (OD-17) | UAT ও পর্যবেক্ষণ শেষে সই, চালুর লিখিত অনুমোদন |
| Client-এর accountant | নমুনা report pack সই, cutover মহড়া যাচাই |
| Admin | Setup Checklist, flag চালু ও বন্ধ |
| Dev team | সমস্যার সমাধান, rollback |
| QA | UAT-এর পরিস্থিতি চালানো, defect-এর রেকর্ড |

**Maker–Checker:** `accounting.enabled` চালু ও জরুরি বন্ধ দুটোতেই দ্বিতীয় একজনের সম্মতি (জরুরি বন্ধে পরে পর্যালোচনা চললে দ্রুততার জন্য আগে বন্ধ, পরে অনুমোদন)। **SoD:** যে developer কোড বদলায় সে নিজে production-এ flag চালু করে না।

### ১০.৭ Data Model ও Dependencies

| Entity | মূল field |
| --- | --- |
| `accounting_enablement_log` | company\_id, flag, action (on, off), actor, approver, reason, at |
| `uat_runs` (ঐচ্ছিক, tool-এ না রেখে) | পরিস্থিতি, ফল, প্রমাণ, কে, কবে |
| `reconciliation_runs` (Phase 3) | ব্যবহৃত |
| `finance_event_inbox` (Phase 4) | ব্যবহৃত |

**Dependency:** Phase 1 থেকে 4 পুরো শেষ ও সবুজ। OD-17 (কে সই করবে) বন্ধ। Client-এর accountant ও finance owner-এর সময় নিশ্চিত।

### ১০.৮ Exception ও Edge Cases

| পরিস্থিতি | সিস্টেম বা প্রক্রিয়ার আচরণ |
| --- | --- |
| Cutover মহড়ায় Trial Balance মেলেনি | চালু থামে, কারণ বের করে opening balance ঠিক করে আবার মহড়া |
| চালুর পর RC ব্যর্থ | আর কোনো company চালু হয় না, ওই company-র ব্যবহার সীমিত, কারণ খোঁজা |
| Consumer job থেমে গেল | Alert, flag বন্ধ করা যায়, জমা event হারায় না, আবার চালালে ক্রমে প্রক্রিয়া |
| Production-এ ভুল mapping পাওয়া গেল | নতুন event আটকে রাখা (mapping ঠিক না হওয়া পর্যন্ত), পুরোনো Draft হয় বাতিল না হয় সংশোধন, posted হলে Reversal |
| Deploy-এর সময় migration ব্যর্থ | Code আগের version-এ, migration পিছনে সামঞ্জস্যপূর্ণ বলে আগের কাজ চলে |
| Pilot company-র user খুব ভুল করছে | Training-এর ঘাটতি, flag বন্ধ না করে সহায়তা, গুরুতর হলে বন্ধ |
| জরুরি বন্ধ করার পর data | কিছু মোছা হয় না, Draft, submitted, posted সব থাকে, শুধু নতুন কাজ বন্ধ |
| একই সময়ে দুটো company চালু করার চেষ্টা | একটা একটা করে, release manager আটকায় |
| UAT-এ পাওয়া গুরুতর defect | সংশোধন আর সংশ্লিষ্ট পরিস্থিতি আবার চালানো, আংশিক সই নয় |

### ১০.৯ Audit Trail ও Reporting

**যা audit-এ যাবে:** প্রতিটা flag চালু ও বন্ধ (কে, কে অনুমোদন দিল, কারণ), release checklist-এর সিদ্ধান্ত, UAT-এর সই, জরুরি বন্ধ ও তার পর্যালোচনা।

**Reporting ও tracking:**

- UAT-এর ফল: কোন পরিস্থিতি পাস বা ব্যর্থ, defect-এর তালিকা, সংশোধনের অবস্থা
- চালুর রিপোর্ট: কোন company কবে চালু, কে অনুমোদন দিল
- পর্যবেক্ষণের সময়ে রোজকার reconciliation সারাংশ
- সমস্যা ও support-এর তালিকা
- চালুর পরের প্রথম মাসের সারাংশ: কতগুলো journal, কতগুলো event, কতগুলো আটকে, কতগুলো ব্যর্থ

### ১০.১০ Acceptance Criteria ও Deliverables

| # | শর্ত | কীভাবে প্রমাণ হবে |
| --- | --- | --- |
| AC5.1 | সব Phase-এর acceptance criteria UAT-এ পাস | UAT-এর ফল, সই |
| AC5.2 | Cutover মহড়ায় Trial Balance পুরোনো হিসাবের সাথে মেলে | নথিভুক্ত তুলনা |
| AC5.3 | Accountant-এর সই করা নমুনা report pack | সই |
| AC5.4 | Alert কাজ করে | ইচ্ছা করে ব্যর্থতা ঘটিয়ে alert পাওয়া |
| AC5.5 | জরুরি বন্ধ কাজ করে ও data নষ্ট করে না | Flag বন্ধ করে দেখা |
| AC5.6 | Rollback পরিকল্পনা অনুশীলিত | মহড়ার রেকর্ড |
| AC5.7 | Pilot company পর্যবেক্ষণের সময় RC1 থেকে RC8 সবুজ | রোজকার রিপোর্ট |
| AC5.8 | কোনো গুরুতর defect খোলা নেই | Defect-এর তালিকা |
| AC5.9 | Training সম্পন্ন | উপস্থিতি, নির্দেশিকা |
| AC5.10 | বিদ্যমান ERPFlow-এর অন্য কাজ অক্ষত | Regression suite, pilot-এর সময়ে পর্যবেক্ষণ |

**Deliverable:** UAT pack ও ফল, সই করা নমুনা report pack, cutover মহড়ার রিপোর্ট, runbook, monitoring ও alert, rollback পরিকল্পনা, training উপকরণ, প্রথম company-র চালুর রিপোর্ট।

**Exit criteria:** Pilot company-তে নির্ধারিত পর্যবেক্ষণ-সময় শেষ, সব AC সবুজ, Finance owner-এর লিখিত চালুর অনুমোদন, বাকি company-র জন্য ধাপে ধাপে চালুর সময়সূচি ঠিক।

### ১০.১১ WBS (Phase 5)

| Workstream | Feature | Task | নির্ভরতা | Deliverable | Acceptance |
| --- | --- | --- | --- | --- | --- |
| 5.1 UAT | পরিকল্পনা | Test পরিস্থিতির pack | Phase 1–4 | UAT pack | সব AC থেকে পরিস্থিতি আছে |
| 5.1 UAT | চালানো | Accountant ও QA-র সাথে UAT | 5.1 | ফল ও defect তালিকা | AC5.1 |
| 5.1 UAT | সই | Finance owner-এর সই | 5.1 | সই করা doc | AC5.3 |
| 5.2 Cutover | মহড়া | Staging-এ opening balance, মেলানো | Phase 3 | রিপোর্ট | AC5.2 |
| 5.3 Ops | Monitoring | RC ও job ও inbox-এর alert | Phase 3, 4 | Dashboard ও alert | AC5.4 |
| 5.3 Ops | Rollback | পরিকল্পনা ও মহড়া | 5.3 | Doc ও রেকর্ড | AC5.5, AC5.6 |
| 5.4 Security | যাচাই | Permission, company isolation, শেষ review | Phase 1–4 | রিপোর্ট | কোনো গুরুতর ফাঁক নেই |
| 5.4 Performance | যাচাই | বাস্তব আকারের data-য় | Phase 3 | রিপোর্ট | AC3.12-এর সীমা মানা |
| 5.5 Release | Gate ও log | Release checklist, `accounting_enablement_log` | 5.1, 5.2 | Checklist ও log | R5.1 থেকে R5.5 |
| 5.5 Release | Pilot company | চালু ও পর্যবেক্ষণ | 5.5 | চালুর রিপোর্ট | AC5.7 |
| 5.5 Release | Asset intake চালু | আলাদা ধাপে, আলাদা অনুমোদনে | 5.5, Phase 4 | চালু | R5.4 |
| 5.6 Enablement | Training | নির্দেশিকা ও অধিবেশন | 5.1 | উপকরণ | AC5.9 |
| 5.6 Enablement | Support | Playbook, যোগাযোগের নিয়ম | 5.6 | Doc | Review শেষ |
| 5.7 Rollout | পরের company | ধাপে ধাপে চালু | 5.5 | সময়সূচি | Finance owner-এর অনুমোদন |

## ১১. Open Decision Register

নিচের সিদ্ধান্তগুলো এখনো চূড়ান্ত নয়। কোনোটাই নিজে ধরে নিয়ে কোড লেখা যাবে না। যেগুলো Admin Settings-এ রাখা যায় (OD-1 থেকে OD-5, OD-8) সেগুলোর জন্য development আটকে থাকবে না, কিন্তু চালু করার আগে মান বসাতে হবে।

| OD | বিষয় | বিকল্প | প্রভাব | কী আটকায় | কে ও কখন |
| --- | --- | --- | --- | --- | --- |
| OD-1 | Accounting basis | (ক) Accrual (খ) Cash (গ) দুটোই configurable | Accrual-এ আয়-খরচ লেনদেনের তারিখে ধরা হয়। Cash-এ টাকা লেনদেনের তারিখে। দুটোর হিসাব আলাদা logic, তাই (গ)-তে কাজ আর test দুই গুণ | P&L, Balance Sheet, Cash Flow | Client-এর accountant, Phase 3 শুরুর আগে |
| OD-2 | Cash Flow পদ্ধতি ও নগদ-সমতুল্য | (ক) Direct (খ) Indirect (গ) দুটোই। নগদ-সমতুল্য কোন account | Indirect-এ receivable, payable, depreciation-এর mapping বেশি লাগে। Direct-এ নগদ লেনদেনের নিখুঁত ভাগ | Cash Flow, RC4 | Accountant, Phase 3 শুরুর আগে |
| OD-3 | Currency | (ক) পুরো group-এ একই (খ) company ধরে আলাদা মূল currency। precision কত | (খ)-তে report আর event-এর currency মেলানোয় বেশি নিয়ম। বিদেশি মুদ্রায় লেনদেন দুই ক্ষেত্রেই বাদ | Company setup, সব report, event intake | Management, Phase 1 শুরুর আগে |
| OD-4 | আর্থিক বছর | প্রতি company-র শুরুর মাস ও দিন | Voucher নম্বর, period, বছর-শেষ প্রভাবিত। একবার post হলে বদলানো যায় না | Period, voucher, report | Accountant, Phase 1 শুরুর আগে |
| OD-5 | Period বন্ধের নীতি | (ক) বন্ধ নেই (খ) সতর্কতা (গ) কঠোর আটকানো। বন্ধের শর্ত, আবার খোলার নিয়ম | (গ) হলে ভুল ঠিক করা কঠিন, কিন্তু হিসাব নিরাপদ | Period বন্ধ, `period_lock_mode` কঠোর | Finance owner, Phase 2 শুরুর আগে |
| OD-6 | Approval-এর ভিত্তি ও নীতি | (ক) Platform `ApprovalEngine`-এ amount threshold বাড়ানো (খ) Accounting-এর নিজের rule layer, request-এর জীবনচক্রে `ApprovalEngine` (গ) পুরো আলাদা approval। সাথে নীতি: কে approver, ধাপ, একে একে না একসাথে, delegation, নিজের journal নিজে approve, জরুরি override | (ক) অন্য module প্রভাবিত হতে পারে, কিন্তু reuse বেশি। (খ) মাঝামাঝি। (গ) সবচেয়ে বেশি কাজ, কিন্তু অন্য module-এ ঝুঁকি নেই | Phase 2-এর approval, event-journal-এর rule | Tech lead ও client, Phase 2 শুরুর আগে |
| OD-7 | CoA-র কাঠামো | Code-এর দৈর্ঘ্য ও পরিসর, অবশ্যই থাকবে এমন account, inactive করার সীমা, system account | সব report-এর mapping আর event mapping-এর ভিত্তি | CoA, opening balance, mapping | Accountant, Phase 1-এর মাঝামাঝি |
| OD-8 | Voucher নম্বর | ছাঁচ, post-এর সময় বসবে নাকি Draft-এ। বছরে নতুন করে শুরু কি না | Post-এর সময় বসালে নম্বরে ফাঁক থাকে না, Draft-এ বসালে বাতিল হলে ফাঁক পড়ে | Voucher service | Finance, Phase 2 শুরুর আগে |
| OD-9 | Opening balance-এর cutover | কোন তারিখ, কোন কাগজ থেকে মেলানো | Cutover মহড়া আর lock-এর ভিত্তি | Cutover, UAT | Finance, Phase 5 শুরুর আগে |
| OD-10 | Asset write-off-এর mapping ও amount-এর অর্থ | কোন account-এ Debit ও Credit। write-off-এর `amount` কি journal-এর একমাত্র amount (accumulated depreciation ছাড়া)? | ভুল হলে সম্পদের হিসাব বিকৃত। অনুমোদিত না হলে pilot-ই হবে না | Asset pilot | Accountant, Phase 4 শুরুর আগে |
| OD-11 | Asset event-এর `accepted`-এর অর্থ | (ক) Draft তৈরি হলে (খ) Post হলে। Asset-এর status বদলাবে নাকি শুধু Accounting-এর inbox সত্যের উৎস | (খ)-তে Draft আটকে থাকলে Asset-এর অবস্থা অনেকক্ষণ `pending`। (ক)-তে Draft বাতিল হলে বিভ্রান্তি | Asset status-update | Tech lead ও accountant, Phase 4 শুরুর আগে |
| OD-12 | Group reporting | (ক) Company-র আলাদা সারাংশ (খ) পুরো একত্রীকরণ, intercompany elimination | (খ) বড় আলাদা প্রকল্প | Group report | Client, প্রথম version-এর পরে |
| OD-13 | Posted journal-এর DB-level অপরিবর্তনীয়তা | (ক) DB trigger (খ) DB-র অনুমতি (গ) অন্য কৌশল। ERPFlow-এর বিদ্যমান database-এর সামর্থ্য অনুযায়ী | (ক) সরাসরি আটকায়, কিন্তু migration আর test-এ যত্ন লাগে | Phase 1-এর design, Phase 2-এর posting | Tech lead, Phase 1 শুরুর আগে |
| OD-14 | Ledger-এর উৎস | (ক) Posted `journal_lines` নিজেই (খ) post-এর সময় আলাদা `ledger_entries` | (ক) সরল, মেলানো সহজ। (খ) report দ্রুত, কিন্তু দুই জায়গা মেলানোর ঝুঁকি | Phase 2-এর posting, Phase 3-এর report | Tech lead, Phase 2 শুরুর আগে |
| OD-15 | বন্ধ period-এ reversal-এর তারিখ | (ক) আজকের তারিখে (খ) মূল তারিখে, period খুলে (গ) Admin বেছে নেয় | (খ)-তে আগের বন্ধ report বদলে যায় | Correction UX | Accountant, Phase 3 শুরুর আগে |
| OD-16 | Cash basis প্রথম version-এ বানানো হবে কি | (ক) হ্যাঁ (খ) পরের ধাপ। Accrual আগে | (ক)-তে Phase 3 লম্বা, (খ)-তে client-কে স্পষ্ট জানাতে হবে | Phase 3-এর সময় | Client ও PM, Phase 3 শুরুর আগে |
| OD-17 | Finance UAT-এ সই কে করবে | নাম বা ভূমিকা | সই ছাড়া চালু নয় | Phase 5 | Management, Phase 4 শেষে |
| OD-18 | Asset pilot-এর change request | অনুমোদিত / প্রত্যাখ্যাত | মূল scope-এ Fixed Assets ও automatic posting বাদ। অনুমোদন না হলে Phase 4 বাতিল বা পিছিয়ে যাবে | Phase 4 | Client, Phase 3 শেষের আগে |
| OD-19 | CoA bulk import | (ক) নেই, হাতে (খ) আছে | Scope doc-এ নেই। (খ) change request লাগে | Phase 1-এর পরিধি | Client, Phase 1 শুরুর আগে |
| OD-20 | বছর শেষে জমানো লাভের হিসাব | (ক) Balance Sheet-এ গতিশীল হিসাব (খ) বছর-শেষে আলাদা closing journal | (খ) হিসাবশাস্ত্রের চর্চার কাছাকাছি, কিন্তু আরেকটা workflow। (ক) সরল | Balance Sheet, period বন্ধ | Accountant, Phase 3 শুরুর আগে |
| OD-21 | Asset event-এ `effective_date` ও `schema_version` | (ক) Asset-এর outbox-এ যোগ (খ) Accounting-এর adapter write-off source থেকে তারিখ পড়ে | (ক) Asset-এ ছোট পরিবর্তন, কিন্তু পরিষ্কার সীমানা। (খ) Asset অপরিবর্তিত, কিন্তু Accounting Asset-এর ভেতরের জ্ঞান রাখে | Phase 4 | Tech lead, Phase 4 শুরুর আগে |
| OD-22 | সম্পন্ন write-off পরে বাতিল বা সংশোধিত হলে | (ক) শুধু হাতে Reversal (খ) নতুন event (গ) `void` event | (খ), (গ)-তে Asset-এ আরও কাজ | Phase 4-এর edge case | Accountant ও tech lead, Phase 4 শুরুর আগে |

### ১১.১ সিদ্ধান্তের ক্রম (কোনটা কখন লাগবে)

| কখনের মধ্যে | OD |
| --- | --- |
| Phase 1 শুরুর আগে | OD-3, OD-4, OD-13, OD-19 |
| Phase 1-এর মাঝামাঝি | OD-7 |
| Phase 2 শুরুর আগে | OD-5, OD-6, OD-8, OD-14 |
| Phase 3 শুরুর আগে | OD-1, OD-2, OD-15, OD-16, OD-20 |
| Phase 3 শেষের আগে | OD-18 |
| Phase 4 শুরুর আগে | OD-10, OD-11, OD-21, OD-22 |
| Phase 4 শেষে | OD-17 |
| Phase 5 শুরুর আগে | OD-9 |
| প্রথম version-এর পরে | OD-12 |

## ১২. WBS, QA Test Plan ও Sprint Planning-এর ছাঁচ

### ১২.১ WBS-এর স্তর কীভাবে বানাবেন

প্রতিটা Phase-এর শেষের WBS table সরাসরি এই স্তরে ভাঙা:

| স্তর | এই doc-এ কোথায় | উদাহরণ |
| --- | --- | --- |
| Level 1: Phase | Section ৫ থেকে ১০ | Phase 2: Journal Engine |
| Level 2: Workstream | WBS table-এর Workstream column (`2.2 Approval`) | `2.2 Approval` |
| Level 3: Feature | Feature column | Approval resolver |
| Level 4: Task | Task column | "Journal-এর জন্য প্রযোজ্য rule খোঁজা" |
| নির্ভরতা | Dependencies column | OD-6, `2.1` |
| Deliverable | Deliverable column | `PostingService` |
| Acceptance | Acceptance column | AC2.4 |

**নম্বর দেওয়ার নিয়ম:** `Phase.Workstream.Feature.Task`, যেমন `2.2.1.3`। Feature আর Task-এর নম্বর আপনি নিজের WBS tool-এ বসাবেন, এই doc-এ শুধু Workstream পর্যন্ত নম্বর আছে।

**নিজে যোগ করার কলাম:** Owner, আনুমানিক কাজ, Sprint, অবস্থা। এগুলো ইচ্ছা করে ফাঁকা, কারণ scope doc অনুযায়ী সময় আর কাজের পরিমাণ Discovery শেষে ঠিক হবে।

**ভাঙার নিয়ম:** একটা task এক জন developer এক sprint-এ শেষ করতে পারে এমন হবে। বেশি বড় হলে আরেক ধাপ ভাঙুন। প্রতিটা task-এর সাথে অন্তত একটা Acceptance বা test থাকবে।

### ১২.২ QA Test Plan-এর কাঠামো

**Test-এর স্তর:**

| স্তর | কী যাচাই | কখন |
| --- | --- | --- |
| Unit | Validator, state machine, posting-এর হিসাব, চিহ্ন নিয়ম | প্রতিটা Phase |
| API / Feature | প্রতিটা endpoint, permission, error code | প্রতিটা Phase |
| Integration | Approval, journal, report-এর একসাথে কাজ, Asset-এর outbox থেকে inbox | Phase 2, 3, 4 |
| UI | Screen-এর প্রবাহ, error বার্তা | প্রতিটা Phase |
| Concurrency | একসাথে approve, post, setting বদল | Phase 1, 2, 4 |
| Failure injection | মাঝপথে ভেঙে পড়া, network-এর ব্যর্থতা | Phase 2, 4 |
| Performance | বড় data-য় report আর consumer | Phase 3, 4, 5 |
| Security ও permission | প্রতিটা role, company isolation, API সরাসরি | প্রতিটা Phase |
| Regression | বিদ্যমান ERPFlow (Project, Payroll, Asset, Commissioning) অক্ষত | প্রতিটা Phase |
| UAT | Client-এর পরিস্থিতি | Phase 5 |

**সবসময় চলা মূল test (golden suite):** এগুলো যেকোনো Phase-এ কখনো ভাঙলে merge আটকাবে।

1. Debit আর Credit না মিললে submit বা post হয় না
2. Posted journal application ও DB দুই স্তরেই বদলানো যায় না
3. এক company-র data অন্য company কখনো দেখে না (API, report, export)
4. একই request বা event বারবার এলেও একই ফল (idempotency)
5. Post atomic: মাঝপথে ব্যর্থ হলে কিছুই বদলায় না
6. Report শুধু posted data দেখায়
7. Reconciliation check RC1 থেকে RC8 সবুজ
8. কোনো পথে নিজে নিজে post হয় না

**Test data:** কমপক্ষে দুই company, প্রতিটায় আলাদা CoA, প্রতিটা role-এর user, একটা company-তে একই code-এর account, অনুমোদন-ধাপ আলাদা দুইটা rule, বেশ কিছু posted journal, কয়েকটা reversal, কিছু period বন্ধ।

**Traceability:** প্রতিটা Acceptance Criteria (AC) থেকে এক বা একাধিক test case। WBS-এর Acceptance column-এর AC নম্বর দিয়েই test case-এর নম্বর মেলে। Test case-এর আইডি: `TC-<AC নম্বর>-<ক্রম>`, যেমন `TC-2.5-01`।

### ১২.৩ Sprint Planning-এর ইঙ্গিত

সময় আর কাজের পরিমাণ এখনো ঠিক নয়, তাই নিচে শুধু ক্রম আর ভাগ।

**একটা Phase-এর ভেতরে সাধারণ ক্রম:**

1. সীমানা আর ভাগ করা জিনিস: error code, company context, audit-এর কাঠামো, permission slug
2. Schema ও migration (পিছনে সামঞ্জস্যপূর্ণ)
3. Service ও business rule
4. API
5. UI
6. Test আর regression
7. Runbook আর demo

**সমান্তরালে চলতে পারে:** UI আর API (মডেল ঠিক হলে), Report-এর আলাদা আলাদা অংশ (Phase 3-এ GL, P&L, Balance Sheet), Inbox-এর UI আর consumer (Phase 4)।

**ক্রমে করতেই হবে:** Journal-এর state machine আগে, তারপর approval, তারপর posting। Posting আগে, তারপর report। Mapping আগে, তারপর Draft তৈরি।

**Definition of Ready:** AC লেখা আছে, নির্ভরতা বন্ধ (OD সহ), test data পরিষ্কার।

**Definition of Done:** code review শেষ, AC-র test সবুজ, golden suite সবুজ, regression সবুজ, audit log আছে, error code-এর বার্তা আছে, flag-বন্ধ অবস্থায় merge নিরাপদ।

**সবচেয়ে বড় ঝুঁকি (শুরু থেকেই নজরে রাখুন):**

| ঝুঁকি | মাত্রা | প্রতিরোধ |
| --- | --- | --- |
| `development`-এ push মানে production deploy | সংকটজনক | Feature branch, flag Default বন্ধ, R5.6 |
| Posted journal DB স্তরে বদলানো যায় | উচ্চ | OD-13 আগে ঠিক, Phase 1-এ design |
| ApprovalEngine-এ amount threshold নেই | মধ্যম | OD-6 আগে ঠিক, Phase 2-এর আগে |
| Cash Flow-এর পদ্ধতি ঠিক নেই | উচ্চ | OD-1, OD-2 Phase 3-এর আগে |
| Asset event ও currency-র অমিল | উচ্চ | OD-3, OD-10, `held_currency` |
| Queue-তে company context হারানো | মধ্যম | R4.11, test |
| Asset pilot scope doc-এর বাইরে | উচ্চ | OD-18 লিখিত অনুমোদন |
| বিদ্যমান Project বা Payroll ভাঙা | উচ্চ | প্রতিটা Phase-এ regression, Phase 1–3-এ ওই code না ছোঁয়া |

## ১৩. Enterprise Scope Map

সম্পূর্ণ scope map, §14 Gap Analysis ও §15 Architecture addendum: ১৩–১৫. Enterprise Scope, Gap Analysis, Architecture

## ১৪. Documentation Gap Analysis

পাঁচ শ্রেণির gap analysis: ১৩–১৫. Enterprise Scope, Gap Analysis, Architecture

## ১৫. Enterprise Architecture Addendum

Boundary, ownership, security, performance, DR, integration failure: ১৩–১৫. Enterprise Scope, Gap Analysis, Architecture

## ১৬. Phase 6: Journal Management ও Analytical Dimensions

Phase 6 journal তৈরির কাঠামো বাড়ায় (type, নম্বর, template, তারিখ, import, সংযুক্তি) আর প্রতিটা journal line-এ বিশ্লেষণধর্মী dimension বসায়। Ledger-এ লেখার পথ একটুও বদলায় না। সব journal `JournalService` দিয়ে Draft হয়। `PostingService` (P2-12) একমাত্র status লেখক।

### ১৬.১ Objective ও Scope

**Objective:** Phase 7 থেকে 15 যেসব নতুন journal ধরন আনবে, তাদের জন্য আগে থেকে একটা registry, একটা নম্বর-ব্যবস্থা আর একটা dimension কাঠামো তৈরি রাখা। একই সঙ্গে আজকের কাজ সহজ করা: ঘন ঘন হওয়া journal template থেকে, বড় তালিকা file থেকে, cost center বা project ধরে বিশ্লেষণসহ।

**ছয়টা ফল:**

1. `acc_journal_types` registry: system, phase-defined ও custom type, প্রতিটার আচরণের flag।
2. `acc_number_series`: P2-11-এর একক voucher counter থেকে type, company, বছর ধরে বহু series। Gapless নিশ্চয়তা Post transaction-এর ভেতরেই থাকে।
3. Journal template, document date, backdate নিয়ম, bulk import, সংযুক্তির policy, comment, tag, copy, clone, saved filter।
4. Dimension কাঠামো: cost center built-in, department, project, location, custom। গাছের মতো value, তারিখভিত্তিক কার্যকারিতা, বাধ্যতামূলক বা নিষিদ্ধ নিয়ম, default মান, ঐচ্ছিক dimension-ভিত্তিক balancing।
5. ERPFlow-এর cost center ও project master থেকে পড়ার mirror, আর dimension মান সরানোর reclassification journal।
6. Phase 3 report-এ dimension filter ও group-by, আর তার জন্য index ও cache নকশা।

| ক্ষেত্র | ভেতরে | বাইরে | বাইরের কেন |
| --- | --- | --- | --- |
| Journal type | Registry, flag, seed, `ck_acc_journals_type` সরিয়ে composite FK | accrual, fx\_revaluation, depreciation, closing ইত্যাদির নিজস্ব engine | Engine-এর মালিক Phase 7, 11, 12, 15। এখানে শুধু type-এর ঘর আর নিয়ম |
| Number series | Series, format token, reset rule, assignment, gapless, counter migration | Gapless আইনত বাধ্যতামূলক কি না চূড়ান্ত করা | OD-117 master-এর। এখানে বাধ্যতামূলক ধরে লেখা |
| Template | Parameterised template, share, edit approval, draft তৈরি | সময়সূচি ধরে নিজে নিজে চালানো | Recurring engine Phase 12। Template তার ভিত্তি |
| তারিখ | `document_date`, type-ভিত্তিক backdate ও future limit | Period lock-এর কঠোর রূপ | Phase 3 (`PeriodGuard`) ও Phase 12 |
| Import | CSV ও xlsx থেকে staging, যাচাই, আংশিক গ্রহণ, Draft তৈরি | পুরোনো system থেকে opening data migration | Phase 16 `import_*` বড় migration framework |
| সংযুক্তি ও নোট | Type, size, malware scan, retention policy, comment, tag, copy, clone, filter, search | Document management বা OCR | ERPFlow-এর file সুবিধা ব্যবহার হয় |
| Dimension | Framework, rule, default, balancing, mirror, reclassification, report filter ও group-by, security hook | Budget-by-dimension, dimension-ভিত্তিক data security কার্যকর করা | Phase 13 ও Phase 13-এর security কাজ। এখানে hook পর্যন্ত |
| Party, tax, FX | কিছু নয় | সব | Phase 7, 8, 9। Party dimension নয়, `party_id` আলাদা column |

**Baseline অনুমান (Master Plan G1 থেকে G9 ছাড়া):**

- একটা ledger, একটা company = একটা legal entity। Dimension company-ভিত্তিক, কোনো dimension company পেরোয় না।
- Cost center ও project master ERPFlow-এর (G8)। Accounting শুধু পড়ার mirror রাখে।
- Gapless voucher নম্বর বাধ্যতামূলক ধরা (OD-117)। Phase 6 তা series-ভিত্তিক flag করে, তাই OD-117 উল্টালেও কাঠামো বদলায় না।
- Phase 6-এর দুটো capability flag: `journal_mgmt.enabled` ও `dimensions.enabled`। দুটোই default বন্ধ। বন্ধ থাকলে আচরণ Phase 5 শেষের মতোই।

### ১৬.২ Functional Requirements

| ক্রম | Requirement |
| --- | --- |
| ক | **Journal type registry:** প্রতি company-তে system type (`manual`, `opening`, `reversal`, `adjustment`, `event_generated`) আর phase-defined type (`accrual`, `recurring`, `fx_revaluation`, `depreciation`, `closing`, `intercompany`, `elimination`, `consolidation`, `bank`, `tax_settlement`, `import`) seed হয়। Admin custom type বানাতে পারে (flag চালু থাকলে) |
| খ | **Type-ভিত্তিক আচরণ:** manual entry চলবে কি না, approval নিয়মের ধরন, default series, auto-reversal অনুমতি, সংযুক্তির নিয়ম, বাধ্যতামূলক dimension, অনুমত accountর পরিসর, backdate ও future সীমা, document date বাধ্যতামূলক কি না |
| গ | **Type পরিবর্তনের নিরাপত্তা:** পুরোনো journal-এর type-এর code কখনো বদলায় না। Type মুছা যায় না, শুধু inactive হয় |
| ঘ | **Number series:** type, company, আর্থিক বছর (বা মাস, বা কখনো নয়) ধরে বহু series। Format token, padding, reset rule, gapless সেটিং, series assignment নিয়ম (type, source, priority, কার্যকর তারিখ) |
| ঙ | **Gapless নিশ্চয়তা:** gapless series-এ নম্বর শুধু Post transaction-এর ভেতরে খরচ হয়। Post ব্যর্থ হলে নম্বর খরচ হয় না |
| চ | **পুরোনো counter স্থানান্তর:** P2-11-এর `acc_voucher_counters` থেকে নতুন counter-এ একটা controlled cutover। `VoucherService` facade হয়ে থাকে, আগের নম্বরের রূপ ও ক্রম নষ্ট হয় না |
| ছ | **Journal template:** পুনর্ব্যবহারযোগ্য, parameter সহ, share scope (private, role, company)। Template বদলাতে দ্বিতীয় অনুমোদন। Template থেকে শুধু Draft journal |
| জ | **Document date ও posting date:** `journal_date` = posting date (period ও report এখান থেকে)। `document_date` = মূল নথির তারিখ। পাশের নিয়ম type ধরে |
| ঝ | **Backdate ও future নিয়ম:** type ধরে দিনের সীমা। Period lock-এর চেয়ে আলগা নয়, আরও কঠোর হতে পারে, শিথিল কখনো নয় |
| ঞ | **Bulk import:** CSV বা xlsx upload → staging → সারি ধরে যাচাই → group (এক group = এক journal) → আংশিক গ্রহণ → Draft। অনুমোদন ও Post সাধারণ পথে |
| ট | **সংযুক্তি policy:** ফাইলের ধরন, আকার, malware scan, retention। Scan সবুজ না হলে নামানো বা Submit নয় |
| ঠ | **Comment, note, tag:** সব status-এ (posted সহ) যোগ করা যায়, ledger ও `posted_hash`-এর বাইরে |
| ড | **Copy ও clone:** copy = line ও dimension সহ নতুন Draft। Clone = copy + tag + সংযুক্তির সূত্র। উভয়বার নতুন তারিখ, নতুন নম্বর (Post-এ) |
| ঢ | **Saved filter ও search:** journal register-এ narration, reference, voucher, tag, dimension ধরে খোঁজা। Filter private বা company-নিবদ্ধ |
| ণ | **Dimension কাঠামো:** cost center built-in, department, project, location, custom। প্রতি line-এ প্রতি dimension-এ সর্বোচ্চ একটা value |
| ত | **Value:** গাছের মতো (parent-child), তারিখভিত্তিক কার্যকারিতা, active flag। Rename চলে। ব্যবহৃত value কখনো মুছা যায় না |
| থ | **Dimension নিয়ম:** account, account group, account type ও journal type ধরে mandatory, optional বা forbidden। Account-প্রতি default value |
| দ | **Line-level যাচাই:** Draft লেখা, Submit ও Post-এ `DimensionService::validateLines` |
| ধ | **Dimension-level balancing (fund accounting):** ঐচ্ছিক, dimension ধরে। প্রতি value-র Debit = Credit |
| ন | **Mirror:** ERPFlow-এর cost center ও project থেকে value নিয়মিত সিঙ্ক। Mirror value Accounting-এ সম্পাদনা যায় না |
| প | **Reclassification:** এক dimension value থেকে আরেক value-তে balance সরানোর Draft journal, পুরোনো posted line না ছুঁয়ে |
| ফ | **Report:** TB, GL, P&L, BS-এ dimension filter (শাখাসহ) ও group-by। Unassigned সারি দেখায়, যাতে যোগফল মেলে |
| ব | **Data security hook:** `DimensionAccessGuard` interface, default সবাই দেখে। Phase 13 এর ওপর বিধি বসাবে |
| ভ | **Performance:** dimension সংরক্ষণ `acc_journal_line_dimensions`, তার index, আর derived cache `acc_dimension_balances` |
| ম | **Capability flag:** `journal_mgmt.enabled` ও `dimensions.enabled`, default বন্ধ, শর্ত মিললে দুজনে চালু |

### ১৬.৩ Technical Requirements

| বিষয় | নির্দিষ্ট ব্যবস্থা |
| --- | --- |
| Service | `JournalTypeRegistry` (`get(company, code)`), `NumberSeriesService` (`next(company, seriesCode, date)`, `assign(journal)`, `preview`), `JournalTemplateService`, `JournalImportService`, `AttachmentPolicyService`, `DimensionService` (`validateLines(company, lines)`, `applyDefaults`, `lockAndRecheck`), `DimensionRuleResolver`, `DimensionMirrorService`, `ReclassificationService`, `DimensionAccessGuard` (hook)। `VoucherService` (P2-11) facade |
| সব journal তৈরি | শুধু `JournalService::createDraft` ও তার validator (P2-03)। Template, import, copy, clone, reclassification সবাই এই দরজা দিয়ে। কেউ `acc_journals.status` লিখবে না |
| Job (সব `AccountingJob`, P1-12) | `JournalImportParseJob`, `JournalImportCommitJob`, `AttachmentScanJob`, `DimensionMirrorSyncJob`, `DimensionBackfillJob`, `DimensionBalanceRebuildJob`। Voucher cutover একটা artisan command (job নয়, সামনে দাঁড়িয়ে চালানো) |
| Queue | `accounting-import` (company প্রতি সর্বোচ্চ একটা কাজ), `accounting-scan`, `accounting-mirror`। Post-এর পথে কোনো job নেই |
| Performance (লক্ষ্য, test-এ মাপা, OD-119-এর সংখ্যা এলে সমন্বয়) | Post-এর অতিরিক্ত খরচ Phase 5 baseline-এর তুলনায় p95 +20ms-এর মধ্যে। 500 line × 4 dimension-এর `validateLines` 150ms-এর মধ্যে (rule আগে থেকে লোড)। Import: 5,000 সারির যাচাই 60 সেকেন্ডের মধ্যে। TB by dimension (এক বছর, 5M line) p95 3 সেকেন্ড। GL with dimension filter-এর প্রথম পৃষ্ঠা 1 সেকেন্ড |
| Dimension storage | প্রতি line ও dimension-এ একটা সারি `acc_journal_line_dimensions`। ধরা হয় line-প্রতি সর্বোচ্চ 4টা। 10M line/বছর হলে সর্বোচ্চ 40M সারি/বছর, সারি প্রতি প্রায় 30 byte, index সহ বছরে তিন GBের কাছাকাছি। Partitioning Phase 16। Posted line-এর সারিগুলো trigger-সুরক্ষিত |
| Cache | `acc_dimension_balances` derived cache (account × period × dimension × value), `balance_version`-নির্ভর invalidation, P3-02-এর মতো। সত্যের উৎস সবসময় posted line |
| Concurrency | Post-এ lock ক্রম: journal → approval request → account (FOR SHARE, id ক্রমে) → period (FOR SHARE) → number series counter → dimension value (FOR SHARE, id ক্রমে)। নতুন lock শেষে বা নিজের table-এ। Tree সরানোর সময় dimension সারি `FOR UPDATE`, তারপর value সারি id ক্রমে। Template, series, type, dimension row-এ `version` optimistic check |
| Failure handling | Import সারি-স্তরে ত্রুটি staging-এ থাকে, group-স্তরে একটা transaction। Scan service বন্ধ থাকলে status `pending`, backoff retry, নিরাপদ দিকে বন্ধ (fail-closed)। Mirror source পাওয়া না গেলে শেষ সফল মান থাকে আর `dimension_mirror_stale` alert। Backfill cursor ধরে পুনঃচালুযোগ্য। Post ব্যর্থ হলে counter rollback |
| Security | Upload ফাইল webroot-এর বাইরে রাখা, স্বতন্ত্র নাম, MIME sniff, macro সহ xlsm অগ্রহণ, ZIP bomb সীমা। Export-এ `=`, `+`, `-`, `@` দিয়ে শুরু সেলে formula injection ঠেকানো। Template expression sandbox (শুধু সংখ্যা, parameter, `+ - * /`, `ROUND`, `MIN`, `MAX`, `ABS`)। Comment plain text। Filter JSON whitelistের বাইরে কিছু নয়। Upload API সিংগেল user প্রতি মিনিটে 5 বার। সব table-এ composite FK, এক company-র তথ্য অন্য company-তে যায় না |
| Observability | Metric: Post-এ counter অপেক্ষার সময়, import ক্ষতিগ্রস্ত group সংখ্যা, scan বাকি, mirror বয়স। Alert `acc_alert_state` (P5) ব্যবহার |

### ১৬.৪ Data Model ও API

Phase 6 ৩১টা নতুন table (তালিকার শেষে P6-09-এর backfill cursor table acc\_dimension\_backfill\_state) যোগ করে (সব `acc_` prefix, সবাই `uq (company_id, id)` ও composite FK মেনে)। পুরো column spec Pack-এ P6-01 থেকে P6-13 card-এ।

| গোষ্ঠী | Table | মূল column (সংক্ষিপ্ত) | Card |
| --- | --- | --- | --- |
| Type | `acc_journal_types` | `company_id`, `code`, `kind` (system, phase, custom), `owner_phase`, `is_active`, `allow_manual_entry`, `approval_rule_type`, `default_series_code`, `auto_reverse_allowed`, `attachment_rule`, `requires_document_date`, `backdate_days_limit`, `future_days_limit` | P6-01 |
| Type | `acc_journal_type_account_ranges` | `company_id`, `journal_type_code`, `effect` (allow, deny), `from_code`, `to_code` অথবা `account_group_id` | P6-01 |
| Series | `acc_number_series` | `code`, `journal_type_code`, `format`, `padding`, `gapless`, `allocate_on`, `reset_rule`, `valid_from`, `valid_to`, `is_active` | P6-03 |
| Series | `acc_number_series_assignments` | `series_id`, `journal_type_code`, `source_type`, `priority`, `effective_from` | P6-03 |
| Series | `acc_number_series_counters` | `series_id`, `counter_key`, `last_no` | P6-03 |
| Series | `acc_voucher_allocations` (append-only) | `series_id`, `counter_key`, `seq_no`, `voucher_no`, `journal_id`, `status` | P6-03 |
| Template | `acc_journal_templates`, `_versions`, `_lines`, `_line_dimensions`, `_shares`, `_uses` | code, share scope, version, line, expression, dimension default, use log | P6-06 |
| Import | `acc_journal_import_batches`, `_groups`, `_rows` | file hash, status, accept mode, group, row, error list | P6-07 |
| সংযুক্তি ও নোট | `acc_attachment_policies`, `acc_attachment_scans` | type, size, scan, retention | P6-08 |
| নোট | `acc_journal_comments`, `acc_journal_tags`, `acc_journal_tag_links`, `acc_saved_filters`, `acc_journal_copy_links` | comment, tag, filter, copy lineage | P6-08 |
| Dimension | `acc_dimensions`, `acc_dimension_values`, `acc_dimension_value_history` | kind, balancing, hierarchy, effective date, mirror key, rename history | P6-09 |
| Dimension | `acc_dimension_rule_sets`, `acc_dimension_rules` | scope, requirement, default value | P6-10 |
| Dimension | `acc_journal_line_dimensions` | `company_id`, `journal_line_id`, `dimension_id`, `value_id` (Master Plan §৬) | P6-09 |
| Dimension | `acc_dimension_reclass_requests` | from value, to value, as-of, snapshot hash, journal | P6-12 |
| Dimension | `acc_dimension_balances` (derived cache) | `period_id`, `account_id`, `dimension_id`, `value_id`, `debit`, `credit` | P6-13 |

**বিদ্যমান table-এ expand-only বদল:**

- `acc_journals`: `document_date DATE NULL`, `series_id NULL`; composite FK `(company_id, journal_type)` → `acc_journal_types (company_id, code)`; `ck_acc_journals_type` সরানো। P1-10 trigger-এ `document_date` ও `series_id` "অপরিবর্তনীয় column"-এ যোগ হয়।
- `acc_accounts`: `default_dimension_rule_id NULL` (Master Plan §৬-এর নাম; মান `acc_dimension_rule_sets.id`) ও composite FK।
- `acc_journal_lines`: কোনো নতুন column নয়। `cost_center_id` থাকে, built-in dimension `cost_center`-এর প্রতিফলন হিসেবে।
- `acc_voucher_counters` (P2-11): স্থাবর থাকে, cutover-এর পর শুধু পড়ার (rollback-এর জন্য)।

**API (prefix `/accounting`, সব write-এ `Idempotency-Key`):**

| গোষ্ঠী | Endpoint |
| --- | --- |
| Type | `GET/POST /journal-types`, `GET/PATCH /journal-types/{code}`, `PUT /journal-types/{code}/account-ranges`, `POST /journal-types/{code}/activate`, `/deactivate` |
| Series | `GET/POST /number-series`, `GET/PATCH /number-series/{id}`, `PUT /number-series/{id}/assignments`, `GET /number-series/preview`, `GET /number-series/{id}/allocations`, `POST /number-series/cutover` (`dry_run`) |
| Template | `GET/POST /journal-templates`, `GET /journal-templates/{id}`, `POST /journal-templates/{id}/versions` (সম্পাদনার অনুরোধ), `POST /journal-templates/{id}/retire`, `PUT /journal-templates/{id}/shares`, `POST /journal-templates/{id}/create-draft` |
| Import | `POST /journal-imports` (multipart), `GET /journal-imports/{id}`, `GET /journal-imports/{id}/rows`, `POST /journal-imports/{id}/accept`, `POST /journal-imports/{id}/cancel`, `GET /journal-imports/sample-file` |
| Journal অতিরিক্ত | `POST /journals/{id}/copy`, `POST /journals/{id}/clone`, `GET/POST /journals/{id}/comments`, `PUT /journals/{id}/tags`, `GET /journals/{id}/attachments/{aid}/download`, `GET /journals/search`, `GET/POST/PATCH/DELETE /saved-filters` |
| Policy | `GET/PUT /attachment-policies`, `GET/POST /journal-tags` |
| Dimension | `GET/POST /dimensions`, `PATCH /dimensions/{id}`, `GET/POST /dimensions/{id}/values`, `PATCH /dimension-values/{id}`, `POST /dimension-values/{id}/move`, `/deactivate`, `DELETE /dimension-values/{id}` |
| Rule | `GET/POST /dimension-rule-sets`, `PUT /dimension-rule-sets/{id}/rules`, `PUT /accounts/{id}/dimension-rule-set` |
| Mirror, reclass | `POST /dimensions/mirror-sync`, `POST /dimensions/backfill`, `GET /dimensions/health`, `POST /dimension-reclassifications` (preview), `POST /dimension-reclassifications/{id}/create-draft` |
| Report | বিদ্যমান P3 report endpoint-এ নতুন query parameter: `dim[<code>]=v1,v2`, `dim_descendants=1`, `dim_unassigned=1`, `group_by=dim:<code>` |

### ১৬.৫ Business Rules ও Configuration

**Business rule (R6.n):**

| নিয়ম | বিবরণ |
| --- | --- |
| R6.1 | Type `code` company-তে unique, ছোট হরফের `snake_case`, 2 থেকে 30 অক্ষর। Code পরে বদলায় না, কারণ posted journal composite FK দিয়ে code-ই ধরে |
| R6.2 | System type (`manual`, `opening`, `reversal`, `adjustment`, `event_generated`) মুছা বা code বদলানো যায় না। বদলানো যায় শুধু নাম, default series, সংযুক্তির নিয়ম, account পরিসর, backdate ও future সীমা। `allow_manual_entry` বদলানো যায় না (seed মান P2-03, P2-13, P2-14-এর বর্তমান আচরণ হুবহু) |
| R6.3 | Phase-defined type seed হয় `is_active = 0`, `allow_manual_entry = 0`। সক্রিয় করে ওই type-এর `owner_phase`-এর নিজের enable পথ, UI-তে কেউ সরাসরি নয়। Inactive type-এ নতুন Draft হয় না (`JTY_TYPE_INACTIVE`) |
| R6.4 | Custom type শুধু `journal_types.allow_custom` চালু থাকলে। Code `x_` দিয়ে শুরু, যাতে ভবিষ্যতের phase-defined code-র সাথে ধাক্কা না লাগে |
| R6.5 | Type-এর flag বদলালে নতুন Draft ও পুরোনো Draft-এর পরের Submit ও Post যাচাইয় কার্যকর। Posted journal কখনো নতুন করে যাচাই হয় না |
| R6.6 | Account পরিসর: `allow` সারি থাকলে প্রতি line-এর account কমপক্ষে একটা `allow`-এ পড়তে হবে আর কোনো `deny`-এ নয়। পরিসর `from_code` থেকে `to_code` (দুটোর দৈর্ঘ্য সমান) অথবা CoA group-এর সব সন্তান |
| R6.7 | সংযুক্তির নিয়ম `none`, `optional`, `required`। `required`-এ Submit-এ কমপক্ষে `attachment_min_count` টা scan-সবুজ সংযুক্তি, Post-এ আবার যাচাই |
| R6.8 | `auto_reverse_allowed = 0` হলে `auto_reverse_on` ভরা journal Draft-এ আটকায় (`JTY_AUTO_REVERSE_NOT_ALLOWED`)। Column Phase 12-এর, column থাকলে তবেই যাচাই |
| R6.9 | `approval_rule_type`: `standard` = P2-08-এর বর্তমান মিলানো। `strict` = কোনো rule না মিললে Submit বন্ধ (`JTY_APPROVAL_RULE_MISSING`)। কোনো ধরন Approval এড়িয়ে Post করায় না |
| R6.10 | Series `format`-এ `{SEQ}` বাধ্যতামূলক। Token: `{PFX}`, `{CO}`, `{TYPE}`, `{FY}`, `{FY2}`, `{YYYY}`, `{MM}`, `{PERIOD}`, `{SEQ}`। `{SEQ}` `padding` অংকে শূন্য দিয়ে ভরা |
| R6.11 | Counter key: `yearly` = সাল (সেই journal-এর period-এর `fiscal_year`), `monthly` = `period_key`, `never` = `ALL`। Reset নিয়ম বদলালে নতুন series |
| R6.12 | **Gapless:** নম্বর শুধু Post transaction-এর ভেতরে `next` দিয়ে, counter সারির lock commit পর্যন্ত। Transaction-এর বাইরে ডাকলে `NSR_NOT_IN_POST_TX` |
| R6.13 | গ্যাপহীন নয় এমন series `allocate_on = 'submit'` হতে পারে। নম্বর হারালে allocation `voided` হিসেবে থাকে, মুছে যায় না |
| R6.14 | Series-এ একবার allocation হলে `format`, `padding`, `reset_rule`, `gapless`, `allocate_on` লক (`NSR_SERIES_LOCKED`)। বদলাতে নতুন series আর নতুন `effective_from` |
| R6.15 | Series বাছাই: `journal_type_code` + ঐচ্ছিক `source_type` মিলিয়ে, সবচেয়ে ছোট `priority` সংখ্যা জিতে, তারপর সবচেয়ে নতুন `effective_from` (≤ journal\_date)। কিছু না মিললে type-এর `default_series_code`, তাও না থাকলে `NSR_SERIES_NO_ASSIGNMENT` |
| R6.16 | `voucher_no` companyজুড়ে unique (`acc_voucher_allocations`-এ DB unique)। দুটো active series-এর prefix এক হলে save আটকায় |
| R6.17 | `VoucherService::next` (P2-11) facade: flag বন্ধ থাকলে পুরোনো path, চালু হলে `NumberSeriesService::next` (default series `JV`)। পুরোনো নম্বরের রূপ হুবহু |
| R6.18 | `journal_date` = posting date, period ও report এর তারিখ। `document_date` মূল নথির। মান বাদ দিলে `journal_date`-ই বসে। Document date posting date-র পরে হলে `journal.document_date_policy` (allow, warn, block) |
| R6.19 | Backdate: `journal_date` < আজ − `backdate_days_limit` হলে `JTY_BACKDATE_LIMIT`। Future: `journal_date` > আজ + `future_days_limit` হলে একই রকম। Typeের সীমা Period lock (`PeriodGuard`) কখনো শিথিল করে না |
| R6.20 | Template সম্পাদনা = নতুন version, দ্বিতীয় অনুমোদন (`jtemplate:<id>` change request)। অনুমোদন না হওয়া পর্যন্ত আগের version চলে। ব্যবহৃত version কখনো বদলায় না |
| R6.21 | Template থেকে বানানো journal সবসময় Draft। Amount expression-এ শুধু parameter ও সংখ্যা। Account, dimension ব্যবহারের সময় বর্তমান নিয়মে যাচাই হয়, template-এর আগের অবস্থা ধরে নয় |
| R6.22 | Share scope `private` (শুধু মালিক), `role` (`_shares`-এর role), `company`। `company` scope-এ নেওয়ার পরিবর্তনে `approve` permission |
| R6.23 | Import: শুধু staging, `import` type, Draft। সারির যাচাই Draft-এর খাঁটি validator-ই (dry-run)। এক group = এক journal। এক ফাইল দুইবার (একই hash, 90 দিন) `JTY_IMPORT_DUPLICATE_FILE`, কারণ সহ `force` ছাড়া |
| R6.24 | সংযুক্তি policy: type-ভিত্তিক প্রথম, তারপর company default। Scan সবুজ না হলে download ও Submit বন্ধ। Posted journal-এর সংযুক্তি মুছা যায় না, retention-এর আগে তো নয়ই |
| R6.25 | Comment, note, tag, filter ledger নয়, `posted_hash`-নয়। Posted journal-তে যোগ হয়। Comment `journal.comment_edit_minutes` পর্যন্ত লেখক বদলাতে পারে, তারপর নয়। Filter কখনো access বাড়ায় না |
| R6.26 | Copy: line + dimension + narration, নতুন তারিখ, Draft। Clone: copy + tag + সংযুক্তির সূত্র। `voucher_no`, `status`, `posted_*`, source link কখনো কপি হয় না |
| R6.27 | Dimension সংখ্যা `dimension.max_count`। `cost_center` built-in, মুছা বা বন্ধ করা যায় না। নাম ও code rename হয় (ইতিহাস থাকে), কারণ line id ধরে |
| R6.28 | Value গাছ: গভীরতা ≤ `dimension.max_depth`, cycle নয়, `path` materialized। সরালে আশের সব সন্তানের `path` এক transaction-এ। Roll-up বর্তমান গাছ ধরে (OD-28b) |
| R6.29 | Value ব্যবহারযোগ্য যদি `is_active` আর `journal_date` তার `effective_from`..`effective_to`-এর ভেতর। Inactive করলে পুরোনো posted line বদলায় না |
| R6.30 | ব্যবহৃত value কখনো মুছা যায় না (`DIM_VALUE_IN_USE`)। Mirror value কখনো মুছা যায় না, শুধু inactive। অব্যবহৃত manual value hard delete হতে পারে |
| R6.31 | নিয়ম বাছাই (প্রতি dimension): account-এর নিজস্ব set, তারপর সবচেয়ে কাছের account group-এর set, তারপর account type-এর set। প্রথমে যেখানে dimensionটার কথা আছে সেই জিতে। Journal typeের set শুধু সেই ফাঁকা জায়গায় `mandatory` যোগ করে, `forbidden`-কে কখনো হারায় না |
| R6.32 | Default মান Draft লেখার সময় (`applyDefaults`) বসে, Post-এ নয়। Default বসানো মান কার্যকর না হলে (inactive) ফাঁকা থাকে আর mandatory হলে `DIM_REQUIRED` |
| R6.33 | Posted line-এর dimension সারি অপরিবর্তনীয় (P1-10 trigger-এ যোগ)। সংশোধন শুধু reclassification journal বা reversal দিয়ে |
| R6.34 | Balancing dimension: journal-এ প্রতি value-র যোগফল Debit = Credit (valueহীন line এক আলাদা বাঁধা)। Default বন্ধ, চালু/বন্ধে দ্বিতীয় অনুমোদন, শুধু নতুন journal-এর জন্য |
| R6.35 | Reclassification: posted ledger থেকে as-of তারিখে হিসাব, `adjustment` type Draft, একই account-এ দুই value-তে সমান ও উল্টো সারি। Submit-এ snapshot hash মিলানো (বদলালে `DIM_RECLASS_STALE`) |
| R6.36 | Mirror value-এ code, name, parent, active শুধু sync থেকে আসে (`DIM_MIRROR_READONLY`)। Sync `(source_type, source_id)` ধরে idempotent। Source-এ মুছলে Accounting-এ inactive |
| R6.37 | Report: এক dimension-এ বহু value = OR, বহু dimension = AND। Group-by সর্বোচ্চ 2 dimension। Unassigned সবসময় আলাদা সারি। Group ও unassigned-এর যোগফল = unfiltered মোট |
| R6.38 | Flag বন্ধ থাকলে আচরণ Phase 5-এর হুবহু: পুরোনো VoucherService, শুধু `cost_center_id`, নতুন যাচাই নেই |

**Configuration (ক): Journal Management।** Flag `Setting` model-এ (group `accounting`), বাকিগুলো `acc_settings`-এ `SettingRegistry` দিয়ে। "Maker-Checker" মানে P1-04-এর change request, যেখানে যিনি বদলান আর যিনি অনুমোদন করেন আলাদা মানুষ।

| Key | উদ্দেশ্য ও Business Impact | Option ও Validation | Default Policy | Scope | বাধ্যতামূলক কি | কে বানায়, বদলায়, অনুমোদন করে | বদলের প্রভাব | Lock ও Audit |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `flag:journal_mgmt.enabled` | Phase 6-এর journal অংশ (type, series, template, import) চালু করে। ভুলে চালু হলে নম্বর cutover অসম্পূর্ণ থাকতে পারে | `true` বা `false`। চালুর শর্ত §১৬.১৩-এর তালিকা | `false` | Company | হ্যাঁ। সব শর্ত সবুজ না হলে চালু হয় না (`SET_INCOMPLETE`) | বানায় ও বদলায়: settings manage permission (P1-04), ভূমিকা Accounting Admin। অনুমোদন: Maker-Checker, দ্বিতীয় admin লাগে | চালুর পর নতুন Post নতুন series পায়। আগের posted `voucher_no` কখনো বদলায় না (allocation table-এ আলাদা রেকর্ড)। বন্ধ করলে rollback command দিয়ে পুরোনো counter ফেরে | Post-এর পর lock নয়, তবে বন্ধ করতে rollback precondition লাগে। Event `accounting.journal_mgmt.flag_changed`, `acc_enablement_log`-এ আগে ও পরে ও কারণ |
| `flag:dimensions.enabled` | Dimension কাঠামো, rule, report filter চালু করে। ভুলে চালু হলে mandatory নিয়মে পুরোনো workflow আটকে যেতে পারে | `true` বা `false`। শর্ত: backfill সম্পূর্ণ, mirror প্রথম sync সফল, rule conflict শূন্য | `false` | Company | হ্যাঁ, শর্ত সবুজ হলে | বানায় ও বদলায়: Accounting Admin। অনুমোদন: Maker-Checker | চালুর পর নতুন Draft-এ নিয়ম খাটে। পুরোনো posted line অপরিবর্তিত। বন্ধ করলে নিয়ম যাচাই থামে, ইতিমধ্যে লেখা dimension তথ্য থাকে | Posted dimension সারি সবসময় lock (trigger)। Event `accounting.dimension.flag_changed`। আগে-পরে ও কারণ দেখায় |
| `journal_types.allow_custom` | কোম্পানি নিজের journal type বানাতে পারবে কি না। খোলা থাকলে type সংখ্যা বাড়ে, রিপোর্ট জটিল হয় | `true` বা `false` | `false` | Company | ঐচ্ছিক। শুধু custom type দরকার হলে | বানায় ও বদলায়: Accounting Admin (`accounting.journal_type.manage`)। অনুমোদন: Maker-Checker | চালু করলে নতুন `x_` type বানানো যায়। বন্ধ করলে আগের custom type থাকে, নতুন হয় না | Lock নয়। Event `accounting.journal_type.setting_changed` |
| `type.approval_rule_type` (প্রতি type) | Type-এর approval নিয়ম কত কঠোর। `strict` ছাড়া rule না মিললে অনুমোদন-স্তর শূন্য হতে পারে | `standard` বা `strict`। System type `reversal`, `adjustment`, `opening`-এ `strict` বাধ্যতামূলক | `standard` (manual, event\_generated), `strict` (বাকি system type ও custom) | Company, type | হ্যাঁ, সব type-এ | বানায়: `accounting.journal_type.manage`। অনুমোদন: `accounting.journal_type.approve` (দ্বিতীয় ব্যক্তি) | নতুন Submit-এ কার্যকর। Submitted ও approved journal-এর request আগের `rule_snapshot` ধরেই চলে | Post-এর পর journal-এ lock, type flag নয়। Event `accounting.journal_type.updated`, diff সহ |
| `type.attachment_rule` (প্রতি type) ও `attachment_min_count` | কোন type-এ সংযুক্তি ছাড়া Submit চলবে না। ভুল হলে প্রমাণহীন journal যায় | `none`, `optional`, `required`। `required`-এ min\_count 1 থেকে 10 | `optional` (manual), `none` (event\_generated), `required` (adjustment, import) | Company, type | হ্যাঁ, সব type-এ | বানায়: `accounting.journal_type.manage`। অনুমোদন: দ্বিতীয় ব্যক্তি শুধু `required` থেকে নরম করলে | নতুন Submit-এ কার্যকর। Posted journal নতুন করে যাচাই হয় না | Event `accounting.journal_type.updated`। বদলের আগের মান UI-তে দেখায় |
| `type.backdate_days_limit`, `type.future_days_limit` (প্রতি type) | আজকের তুলনায় কত দিন আগের বা পরের `journal_date` চলবে। ভুল হলে পুরোনো মাসে অকারণ journal ঢোকে | পূর্ণসংখ্যা 0 থেকে 3650 বা `NULL` (শুধু period নিয়ম)। দুটো পরস্পর সম্পর্কহীন | System type: `NULL`। Custom type: বাধ্যতামূলক, Admin দেবে | Company, type | System type-এ ঐচ্ছিক, custom-এ হ্যাঁ | বানায়: `accounting.journal_type.manage`। অনুমোদন: `accounting.journal_type.approve` | শুধু নতুন Draft ও আগের Draft-এর Submit-এ। Posted journal বদলায় না। সীমা কমালে আগের Draft ব্যর্থ হতে পারে, তাই Submit-এ স্পষ্ট ত্রুটি | Event `accounting.journal_type.updated`। ত্রুটি `JTY_BACKDATE_LIMIT` |
| `type.account_ranges` (প্রতি type) | কোন account-এ এই type লিখতে পারবে। ভুল হলে ভুল account-এ বিশেষ journal ঢোকে | `allow` বা `deny`, `from_code` থেকে `to_code` (সমান দৈর্ঘ্য) বা CoA group। পরিসর overlap হলে `deny` জেতে | খালি (কোনো সীমা নেই) | Company, type | ঐচ্ছিক | বানায়: `accounting.journal_type.manage`। অনুমোদন: `accounting.journal_type.approve` | নতুন Draft ও Submit-এ। Posted অপরিবর্তিত | Event `accounting.journal_type.ranges_changed`। ত্রুটি `JTY_ACCOUNT_RANGE_VIOLATION` |
| `numbering.default_gapless` | নতুন series-এর `gapless` default। বন্ধ থাকলে অসম্পূর্ণ ক্রমের ঝুঁকি, আইনি ঝুঁকি (OD-117) | `true` বা `false` | `true` (OD-117 ধরে) | Company | হ্যাঁ | বানায়: `accounting.number_series.manage`। অনুমোদন: Maker-Checker | শুধু নতুন series। বিদ্যমান series অপরিবর্তিত | Lock নয়। Event `accounting.number_series.setting_changed` |
| `series.<code>.format` ও `.gapless`, `.reset_rule` (প্রতি series) | Voucher নম্বরের গঠন ও আচরণ। ভুল হলে নম্বর অদ্ভুত বা নকল হয় | `format`-এ `{SEQ}` বাধ্যতামূলক, token তালিকা R6.10। `reset_rule`: `yearly`, `monthly`, `never` | `JV-{CO}-{FY}-{SEQ}` প্যাডিং 6, `yearly`, `gapless = true` (P2-11-এর রূপ) | Company, series | হ্যাঁ | বানায়: `accounting.number_series.manage`। অনুমোদন: `accounting.number_series.approve` | প্রথম allocation-এর পর লক। বদলাতে নতুন series ও `effective_from` | প্রথম allocation-এর পর lock (`NSR_SERIES_LOCKED`)। Event `accounting.number_series.updated` |
| `numbering.default_reset_rule` | নতুন series-এর default reset। ভুল হলে বছর বদলে নম্বর চলতে থাকে বা ভেঙে যায় | `yearly`, `monthly`, `never` | `yearly` | Company | হ্যাঁ | বানায়: `accounting.number_series.manage`। অনুমোদন: Maker-Checker | শুধু নতুন series | Event `accounting.number_series.setting_changed` |
| `journal.document_date_policy` | Document date posting date-র পরে হলে কী হবে। `allow` হলে ভবিষ্যতের নথিতে আগের তারিখে হিসাব ঢোকে | `allow`, `warn`, `block` | `warn` | Company | হ্যাঁ | বানায়: `accounting.journal_type.manage`। অনুমোদন: Maker-Checker যদি `block` থেকে `allow`-এ নামে | নতুন Draft ও Submit-এ। Posted অপরিবর্তিত | Event `accounting.journal.document_date_policy_changed` |
| `template.edit_requires_approval` | Template সম্পাদনায় দ্বিতীয় ব্যক্তি লাগবে কি না। বন্ধ থাকলে share করা template নিঃশব্দে বদলে যেতে পারে | `true` বা `false`। `company` scope-এর template-এ সবসময় `true` (বন্ধ করা যায় না) | `true` | Company | হ্যাঁ | বানায়: `accounting.journal_template.manage`। অনুমোদন: `accounting.journal_template.approve` | নতুন সম্পাদনা-অনুরোধে। চলমান অনুরোধ আগের নিয়ম ধরে | Event `accounting.journal_template.setting_changed` |

**Configuration (খ): Import, সংযুক্তি ও Dimension**

| Key | উদ্দেশ্য ও Business Impact | Option ও Validation | Default Policy | Scope | বাধ্যতামূলক কি | কে বানায়, বদলায়, অনুমোদন করে | বদলের প্রভাব | Lock ও Audit |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `journal_import.max_rows` | এক ফাইলে সর্বোচ্চ সারি। বড় হলে queue আটকায় ও memory বাড়ে | পূর্ণসংখ্যা 100 থেকে 50,000 | 5,000 | Company | হ্যাঁ | বানায়: `accounting.journal_import.manage`। অনুমোদন: নিচের কম সীমা বাড়ালে Maker-Checker | নতুন upload-এ। আগের batch অপরিবর্তিত | Lock নয়। Event `accounting.journal_import.setting_changed`। ত্রুটি `JTY_IMPORT_TOO_MANY_ROWS` |
| `journal_import.accept_mode` | আংশিক গ্রহণ চলবে কি না। ভুল হলে অর্ধেক তথ্য ঢোকে তালিকা অসম্পূর্ণ থাকে | `all_or_nothing` বা `partial`। `partial`-এ নির্বাচিত বা সব বৈধ group, user স্পষ্ট নিশ্চিত করে | `partial` | Company, batch-এ override | হ্যাঁ | বানায়: `accounting.journal_import.manage`। অনুমোদন: Maker-Checker | নতুন batch-এ। চলমান batch তার নিজের মোড ধরে | Batch `accept`-এর পর মোড lock। Event `accounting.journal_import.accepted` (মোড সহ) |
| `attachment.scan_required` | Malware scan ছাড়া ফাইল দেখানো বা Submit আটকানো। বন্ধ হলে ম্যালওয়্যার ও আডিট ঝুঁকি | `true` বা `false` | `true` | Company, type-override | হ্যাঁ | বানায়: `accounting.attachment_policy.manage`। অনুমোদন: `true` থেকে `false`-এ Maker-Checker | নতুন upload-এ। আগের ফাইলের scan অবস্থা অপরিবর্তিত | Event `accounting.attachment_policy.changed`, আগে-পরে দেখায় |
| `attachment.max_bytes`, `attachment.allowed_ext` | ফাইলের আকার ও ধরনের সীমা। বড় হলে storage ও scan বাড়ে | সর্বোচ্চ ERPFlow-এর বিদ্যমান file নিয়মের (P0-02) সীমার বেশি নয়। তালিকা: pdf, png, jpg, xlsx, csv, docx | 10 MB; pdf, png, jpg, xlsx, csv, docx | Company, type-override | হ্যাঁ | বানায়: `accounting.attachment_policy.manage`। অনুমোদন: নিরাপত্তা দুর্বল করলে (নতুন ধরন যোগ) Maker-Checker | নতুন upload-এ। আগের ফাইল থাকে | Event `accounting.attachment_policy.changed` |
| `attachment.retention_years` | সংযুক্তি কত বছর রাখা। কম হলে আইনি ঝুঁকি, বেশি হলে storage | পূর্ণসংখ্যা 1 থেকে 30। Posted তারিখ থেকে গননা (OD-116-এর সাথে মিলানো) | 7 (OD-116 প্রস্তাব) | Company | হ্যাঁ | বানায়: `accounting.attachment_policy.manage`। অনুমোদন: কমালে Maker-Checker | শুধু নতুন posted journal-এর `retention_until`। আগের মান কখনো কমে না (বাড়ানো যায়) | Event `accounting.attachment_policy.changed` |
| `journal.comment_edit_minutes` | Comment কত সময় পর্যন্ত লেখক সম্পাদনা করতে পারবে। বেশি হলে আলোচনার প্রমাণ বদলাতে পারে | পূর্ণসংখ্যা 0 থেকে 60 | 15 | Company | ঐচ্ছিক | বানায় ও বদলায়: Accounting Admin। অনুমোদন লাগে না | নতুন সম্পাদনায়। আগের comment অপরিবর্তিত | Event `accounting.journal_comment.edited` (আগের পাঠ সহ)। Setting বদল audit-এ |
| `dimension.max_count` | Company-তে সক্রিয় dimension-এর সর্বোচ্চ সংখ্যা। বেশি হলে line-প্রতি সারি ও UI জটিল | পূর্ণসংখ্যা 1 থেকে 12। সক্রিয় dimension-এর চেয়ে কম হতে পারে না | 8 | Company | হ্যাঁ | বানায়: `accounting.dimension.manage`। অনুমোদন: বাড়ালে Maker-Checker | নতুন dimension বানানোর সময়। বিদ্যমান অপরিবর্তিত | Event `accounting.dimension.setting_changed`। ত্রুটি `DIM_LIMIT_EXCEEDED` |
| `dimension.max_depth` | Value গাছের সর্বোচ্চ গভীরতা। গভীর হলে roll-up ধীর | পূর্ণসংখ্যা 1 থেকে 10 | 6 | Company | হ্যাঁ | বানায়: `accounting.dimension.manage`। অনুমোদন: কমালে ও বর্তমান গভীরতার চেয়ে কম সম্ভব নয় | নতুন value বা সরানোয়। বিদ্যমান গাছ অপরিবর্তিত | Event `accounting.dimension.setting_changed`। ত্রুটি `DIM_DEPTH_EXCEEDED` |
| `dimension.allow_balancing` | Dimension-ভিত্তিক balancing (fund accounting) company-তে অনুমতি। ভুলে চালু হলে সাধারণ journal আটকায় | `true` বা `false` | `false` | Company | ঐচ্ছিক (বাগিয়া fund-কেন্দ্রিক হলে হ্যাঁ) | বানায়: `accounting.dimension.manage`। অনুমোদন: Maker-Checker, ভূমিকা Finance Controller | শুধু নতুন journal। পুরোনো posted journal প্রস্থানিকভাবে যাচাই হয় না | চালুর পর কোনো dimension-এ balancing চালু হলে বন্ধ করার আগে দ্বিতীয় অনুমোদন। Event `accounting.dimension.balancing_changed` |
| `dimension.<code>.is_balancing` (প্রতি dimension) | কোন dimension-এ প্রতি value-র Debit = Credit হতে হবে। ভুল হলে বৈধ journal আটকায় | `true` বা `false`। `dimension.allow_balancing` চালু থাকতে হবে। Built-in `cost_center`-এ নয় | `false` | Dimension | ঐচ্ছিক | বানায়: `accounting.dimension.manage`। অনুমোদন: `accounting.dimension.approve` (দ্বিতীয় ব্যক্তি) | শুধু বদলের পর Submit হওয়া journal-এ। Draft-ও আগের সময়ের হলে Submit-এ নতুন নিয়মে যাচাই | প্রথম posted balancing journal-এর পর বন্ধ করতে অনুমোদন লাগে। Event `accounting.dimension.balancing_changed` |
| `dimension.default_requirement` | যে account-এ কোনো নিয়ম নেই, সেখানে dimension কী ধরা হবে। `forbidden` রাখলে নিয়মহীন account-এ dimension দেওয়া যায় না | `optional`, `mandatory`, `forbidden` | `optional` | Company | হ্যাঁ | বানায়: `accounting.dimension.manage`। অনুমোদন: `mandatory` বা `forbidden`-এ সরালে Maker-Checker | নতুন Draft ও Submit-এ। Posted অপরিবর্তিত | Event `accounting.dimension.setting_changed`। Dry-run-এ কতগুলো Draft ব্যর্থ হবে দেখায় |
| `dimension.mirror_sync_interval_min` | Cost center ও project mirror কত মিনিট পরপর হালনাগাদ। বেশি হলে নতুন value দেরিতে আসে | পূর্ণসংখ্যা 5 থেকে 1440 | 15 | Company | ঐচ্ছিক | বানায় ও বদলায়: `accounting.dimension.manage`। অনুমোদন লাগে না | পরের sync থেকে। আগের value অপরিবর্তিত | Event `accounting.dimension.setting_changed`। Alert `dimension_mirror_stale` (3 বিরতি পার হলে) |
| `dimension_rule.<set>` (প্রতি rule set) | কোন account বা group-এ কোন dimension বাধ্যতামূলক, ঐচ্ছিক বা নিষিদ্ধ। ভুল হলে ঠিক journal আটকায় বা ভুল জমা হয় | `mandatory`, `optional`, `forbidden`, default value। Conflict থাকলে save হয় না (`DIM_RULE_CONFLICT`) | কোনো rule নেই (`dimension.default_requirement` প্রযোজ্য) | Company, rule set | ঐচ্ছিক | বানায়: `accounting.dimension.manage`। অনুমোদন: `accounting.dimension.approve` (`mandatory` বা `forbidden` যোগে) | নতুন Draft ও Submit-এ। Posted line অপরিবর্তিত | Rule set save-এ `version` বাড়ে। Event `accounting.dimension_rule.changed`, আগে ও পরে সহ |

### ১৬.৬ Operational Workflow

সাতটা প্রক্রিয়া। প্রতিটার ধাপ-সারণি, তারপর সবার উপর একটা exception তালিকা।

**WF-A: Post-এ gapless নম্বর বরাদ্দ**

| ধাপ | কে বা system | Data Entity | API বা boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1. Post অনুরোধ | Poster | `acc_journals`, `acc_idempotency_keys` | `POST /journals/{id}/post` | `Idempotency-Key` আছে | `accounting.journal.post` | Key নেই হলে ১০০ বাদে ত্রুটি (P1-09) | একই key = আগের ফল | পরের ধাপে | AC6.5 |
| 2. Transaction ও lock | `PostingService` (P2-12) | journal, approval request, account, period | অভ্যন্তরীণ | `approved`, period open, SubmitValidator Post-মোড (type flag সহ) | SoD (P2-10) | `JRN_*` ত্রুটি, rollback | Posted হলে একই ফল (R2.13) | নেই | AC6.5 |
| 3. Series বাছাই | `NumberSeriesService::assign` | `acc_number_series`, `_assignments` | অভ্যন্তরীণ | Series active, `valid_from..valid_to`-এ তারিখ, type মিল | নেই | `NSR_SERIES_NO_ASSIGNMENT`, `NSR_SERIES_INACTIVE`: rollback, নম্বর খরচ হয় নি | একই journal একই series বাছে (নির্ধারিত) | নেই | AC6.4 |
| 4. Counter বাড়ানো | `NumberSeriesService::next` | `acc_number_series_counters` | অভ্যন্তরীণ | Transaction চালু (না হলে `NSR_NOT_IN_POST_TX`), `last_no` overflow নয় | নেই | Deadlock হলে পুরো Post বার তিনেক আবার | Row lock commit পর্যন্ত | নেই | G6.1 |
| 5. Allocation লেখা | `NumberSeriesService` | `acc_voucher_allocations` | অভ্যন্তরীণ | `uq (company_id, voucher_no)`, `uq (company_id, series_id, counter_key, seq_no)` | নেই | Unique ভাঙলে rollback ও alert (ঘটার কথা নয়) | `uq (company_id, journal_id)` | নেই | G6.1 |
| 6. Dimension পুনরায় যাচাই | `DimensionService::lockAndRecheck` | `acc_dimension_values` `FOR SHARE` (id ক্রমে) | অভ্যন্তরীণ | Value active, তারিখ effective, balancing মিল | নেই | `DIM_VALUE_INACTIVE`: rollback, counter ফেরত | পুনরায় চালালে একই ফল | নেই | AC6.9 |
| 7. Status লেখা | `PostingService` | `acc_journals` (`voucher_no`, `series_id`, `status`) | অভ্যন্তরীণ | `version` মিল | নেই | `JRN_CONFLICT`: rollback | নেই | `accounting.journal.posted` (বিদ্যমান) | AC6.4 |
| 8. Commit ও afterCommit | `PostingService` | cache stale, `JournalPosted` | event | নেই | নেই | Commit ব্যর্থ = সব rollback, নম্বর৊ ফেরত | নেই | নেই | G6.1 |

**WF-B: Template থেকে Draft**

| ধাপ | কে বা system | Data Entity | API বা boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1. Template বাছাই | Preparer | `acc_journal_templates`, `_shares` | `GET /journal-templates?usable=1` | Share scope অনুযায়ী দৃশ্য | `accounting.journal_template.use` | অদৃশ্য হলে 404 | নেই | নেই | AC6.6 |
| 2. Parameter দেওয়া | Preparer | `_versions.params` | `POST /journal-templates/{id}/create-draft` | Parameter ধরন, পরিসর, বাধ্যতামূলক | ঐ | `JTY_TEMPLATE_PARAM_INVALID` (field সহ) | `Idempotency-Key` | নেই | AC6.6 |
| 3. Version ঠিক করা | `JournalTemplateService` | `_versions` | অভ্যন্তরীণ | শুধু চলমান (`active`) version, retired হলে নয় | নেই | `JTY_TEMPLATE_NOT_FOUND`, `JTY_TEMPLATE_PENDING_APPROVAL` (শুধু দেখানোর সময় তথ্য, আগের version চলে) | নেই | নেই | AC6.6 |
| 4. Amount হিসাব | `ExpressionEvaluator` | `_lines.amount_expr` | অভ্যন্তরীণ | Decimal, 4 ঘরে গোল, শূন্য-ভাগ নয় | নেই | `JTY_TEMPLATE_EXPR_INVALID` | নেই | নেই | G6.9 |
| 5. Draft তৈরি | `JournalService::createDraft` | `acc_journals`, `_lines`, `acc_journal_line_dimensions` | অভ্যন্তরীণ | Draft-মোড validator (P2-03): balance, account, type flag, dimension, তারিখ | নেই | Rollback, কিছু তৈরি হয় না | Key একই হলে একই Draft | `accounting.journal_template.used` | AC6.6 |
| 6. Use লগ | `JournalTemplateService` | `acc_journal_template_uses` | অভ্যন্তরীণ | নেই | নেই | একই transaction | `uq (company_id, journal_id)` | নেই | AC6.6 |

**WF-C: Bulk import**

| ধাপ | কে বা system | Data Entity | API বা boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1. Upload | Preparer | `acc_journal_import_batches`, ফাইল storage | `POST /journal-imports` | Extension, MIME, আকার, ZIP সীমা, hash, সারি সংখ্যা, দ্বিতীয়বার hash | `accounting.journal_import.manage` | `JTY_IMPORT_FILE_INVALID`, `JTY_IMPORT_DUPLICATE_FILE` (409) | `uq (company_id, file_sha256, created_by, day)` ছাড়া warn; Key | `accounting.journal_import.uploaded` | AC6.7 |
| 2. Parse | `JournalImportParseJob` | `_rows`, `_groups` | queue `accounting-import` | কলাম ম্যাপ, তারিখ/সংখ্যা পার্স, group\_ref | নেই | পার্স ব্যর্থ = batch `failed`, সারি লেখা হয় নি | Batch একবারই parse (status মেশিন) | নেই | AC6.7 |
| 3. যাচাই | `JournalImportService::validate` | `_groups.status`, `_rows.error_codes` | queue | Account code → id, dimension code → id, তারিখ, balance (group-এ), type ও dimension নিয়ম (dry-run) | নেই | প্রতি group আলাদা, একটা ব্যর্থ অন্যগুলো থামায় না | পুনরায় চালালে একই ফল | `accounting.journal_import.validated` | AC6.7 |
| 4. পর্যালোচনা | Preparer | নেই | `GET /journal-imports/{id}`, `/rows` | নেই | `accounting.journal_import.view` | নেই | নেই | নেই | AC6.7 |
| 5. গ্রহণ | `JournalImportCommitJob` | `acc_journals` (Draft), `_groups.journal_id` | `POST /journal-imports/{id}/accept` | Batch `validated`, mode, group তালিকা | `accounting.journal_import.commit` | Group আলাদা transaction, ব্যর্থ group `failed`, অন্যরা এগোয় | `source_type = import_batch`, `source_id = group id` + `source_active` unique | `accounting.journal_import.committed` | G6.7 |
| 6. শেষ | Job | `_batches.status` | নেই | নেই | নেই | সব group শেষ → `completed` বা `partially_accepted` | নেই | নেই | AC6.7 |

**WF-D: Dimension বসানো, যাচাই, Post**

| ধাপ | কে বা system | Data Entity | API বা boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1. Line-এ value দেওয়া | Preparer | `acc_journal_lines`, `acc_journal_line_dimensions` | `PUT /journals/{id}` (প্রতি line-এ `dimensions: {code: value_code}`) | Value আছে, সঠিক dimension-এর, active, `journal_date` effective | বিদ্যমান Draft সম্পাদনা permission | `DIM_VALUE_NOT_FOUND`, `DIM_VALUE_WRONG_DIMENSION`, `DIM_VALUE_INACTIVE`, `DIM_VALUE_NOT_EFFECTIVE` (field `lines[n].dimensions.<code>`) | PUT পুরো line-সেট বদলায়, পুনরায় একই ফল | বিদ্যমান draft audit | AC6.8 |
| 2. Default বসানো | `DimensionService::applyDefaults` | `acc_dimension_rules.default_value_id` | অভ্যন্তরীণ | ফাঁকা dimension-এই বসে, দেওয়া মান কখনো overwrite নয় | নেই | Default অকার্যকর হলে ফাঁকা, বাধ্যতামূলক হলে Submit-এ ধরা | একই ইনপুটে একই ফল | নেই | AC6.8 |
| 3. Cost center প্রতিফলন | `DimensionLineWriter` | `cost_center_id`, dimension সারি | অভ্যন্তরীণ | দুটো এক transaction-এ, একই মান | নেই | অমিল হলে rollback | নির্ধারিত | নেই | G6.5 |
| 4. Submit যাচাই | `SubmitValidator` (P2-03) → `DimensionService::validateLines` | rule set, নিয়ম, balancing | `POST /journals/{id}/submit` | সব line একসাথে: mandatory, forbidden, balancing, type-এর mandatory | Submit permission | সব ত্রুটি এক তালিকায় (`DIM_REQUIRED`, `DIM_FORBIDDEN`, `DIM_BALANCING_VIOLATION`) | পুনরায় Submit একই ফল | বিদ্যমান submit audit | G6.3, G6.4 |
| 5. Approval | P2-08 | approval request | বিদ্যমান | Rule বদলালেও এখানে আবার যাচাই নয় | Approver | নেই | নেই | বিদ্যমান | AC6.8 |
| 6. Post | WF-A ধাপ 6 | `acc_dimension_values` | অভ্যন্তরীণ | আবার সব নিয়ম নতুন করে (সস্তা, এক query) | Post | `DIM_*` rollback | Post একই ফল | বিদ্যমান | AC6.9 |
| 7. Cache | listener | `acc_dimension_balances` | event | নেই | নেই | ব্যর্থ হলে retry, Post প্রভাবিত নয় | `balance_version` ধরে | নেই | AC6.12 |

**WF-E: Dimension reclassification**

| ধাপ | কে বা system | Data Entity | API বা boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1. Preview | Finance Controller | `acc_dimension_reclass_requests` | `POST /dimension-reclassifications` | দুই value এক dimension-এর, from ≠ to, as-of তারিখ open period-এ, from-এ balance আছে | `accounting.dimension.reclassify` | `DIM_RECLASS_EMPTY`, `DIM_VALUE_WRONG_DIMENSION`, `JRN_PERIOD_LOCKED` | `Idempotency-Key` | `accounting.dimension.reclass_previewed` | AC6.10 |
| 2. Balance হিসাব | `ReclassificationService` | posted ledger (`LedgerQuery::balancesAsOf` + dimension filter) | অভ্যন্তরীণ | Snapshot hash সংরক্ষণ | নেই | Read-only, কিছু বদলায় না | নির্ধারিত (একই snapshot = একই hash) | নেই | G6.6 |
| 3. Draft তৈরি | `ReclassificationService` → `JournalService::createDraft` | `acc_journals` (`adjustment`, `source_type = dimension_reclass`), lines, dimensions | `POST /dimension-reclassifications/{id}/create-draft` | একই account, from-এ Credit/Debit আর to-এ উল্টো, নিট শূন্য | ঐ | Rollback | `source_active` unique: এক request এক journal | `accounting.dimension.reclass_drafted` | G6.6 |
| 4. Submit-এ তাজা যাচাই | `SubmitValidator` | snapshot hash | submit | Ledger আবার হিসাব করে hash তুলনা | Submit | `DIM_RECLASS_STALE`, request `superseded` | নির্ধারিত | বিদ্যমান | AC6.10 |
| 5. Approval ও Post | P2-08, P2-12 | সাধারণ | সাধারণ | সাধারণ | Approver, Poster | সাধারণ | সাধারণ | বিদ্যমান | G6.6 |

**WF-F: Voucher counter cutover**

| ধাপ | কে বা system | Data Entity | API বা boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1. Dry-run | Accounting Admin | `acc_voucher_counters` (পড়া), `acc_number_series_counters` | `POST /number-series/cutover?dry_run=1` | প্রতি (company, fiscal\_year, series) ছক, অমিল তালিকা | `accounting.number_series.manage` | অমিল হলে থামা, সর্ব ত্রুটি দেখায় | পুনরায় নিরাপদ | `accounting.number_series.cutover_dry_run` | AC6.3 |
| 2. Precondition | Command | `acc_settings`, change request | CLI `accounting:numbering-cutover` | প্রস্তবিত অনুমোদন, রখণাবেক্ষণ window, ব্যাকআপ নিশ্চিত | Operator | `NSR_CUTOVER_PRECONDITION` | নিরাপদ | লগ | AC6.3 |
| 3. Copy | Command, এক company এক transaction | legacy `FOR UPDATE`, নতুন counter upsert | CLI | `last_no = GREATEST(নতুন, পুরোনো)`, `cutover_base_no` লেখা, দুই পক্ষ তুলনা | Operator | যেকোনো ত্রুটিতে rollback, কিছু বদলায় না | পুনরায় চালালে একই মান | `accounting.number_series.cutover` | G6.2 |
| 4. Flag চালু | Admin ও Checker | flag, `acc_enablement_log` | change request | RC1n সবুজ | Maker-Checker | Precondition না মিললে `SET_INCOMPLETE` | নির্ধারিত | বিদ্যমান | AC6.3 |
| 5. পরিবীক্ষণ | Operator | পরের Post | বিদ্যমান | পরের `voucher_no` = আগের + 1, একই রূপ | নেই | অমিল হলে rollback command | নেই | লগ | G6.2 |
| 6. Rollback (দরকার হলে) | Command | নতুন থেকে legacy | CLI `accounting:numbering-rollback` | Flag আগে বন্ধ, legacy `last_no = GREATEST` | Operator | Transaction | নির্ধারিত | লগ | AC6.3 |

**WF-G: Mirror sync**

| ধাপ | কে বা system | Data Entity | API বা boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1. চালু | Scheduler অথবা হাতে | `DimensionMirrorSyncJob` | `POST /dimensions/mirror-sync` | Job unique (company, source\_type) | `accounting.dimension.manage` | আগেরটা চললে ইগনোর | নির্ধারিত | নেই | AC6.11 |
| 2. পড়া | `MasterDataReader` (শুধু পড়া) | ERPFlow cost center, project | পৃষ্ঠা ধরে | প্রতি সারির hash | নেই | পাওয়া না গেলে backoff, শেষ সফল মান থাকে | নির্ধারিত | নেই | AC6.11 |
| 3. Diff | `DimensionMirrorService` | `acc_dimension_values`, `_value_history` | অভ্যন্তরীণ | `(source_type, source_id)` ধরে: নতুন insert, বদলালে update + history, না পেলে inactive | নেই | প্রতি পৃষ্ঠায় এক transaction, parent আগে | পুনরায় চালালে পরিবর্তন শূন্য | `accounting.dimension_value.mirrored` | AC6.11 |
| 4. Cycle বা গভীরতা | ঐ | ঐ | ঐ | `DIM_CYCLE_DETECTED`, `DIM_DEPTH_EXCEEDED` | নেই | ওই সারি ডিঙিয়ে যাওয়া, alert | নির্ধারিত | ইবেন্ট | AC6.11 |
| 5. শেষ | Job | `mirror_synced_at`, alert state | নেই | 3 বিরতি পার হলে `dimension_mirror_stale` | নেই | নেই | নির্ধারিত | নেই | AC6.11 |

**Exception scenario তালিকা:**

| পরিস্থিতি | কী ঘটে ও কী নিয়ম | ত্রুটি বা ফল |
| --- | --- | --- |
| Duplicate Post অনুরোধ বা দ্বিতীয় ট্যাপ | Posted হলে আগের ফল। নতুন নম্বর খরচ হয় না | একই `voucher_no` |
| Duplicate import ফাইল | 90 দিনের মধ্যে একই hash আটকায়, কারণ সহ `force` চলে | `JTY_IMPORT_DUPLICATE_FILE` |
| Duplicate import group commit (retry) | `source_active` unique একই group-এ দ্বিতীয় Draft দেয় না | আগের Draft ফেরত |
| Posting ব্যর্থ (dimension, period, বা conflict) | সব rollback, counter সহ। নম্বরে ফাঁক পড়ে না | `DIM_*`, `JRN_*` |
| Invalid mapping | Import-এ অজানা account code বা dimension code। Template-এ retired account। Rule set-এ inactive dimension | `JTY_IMPORT_ROW_INVALID`, `JTY_TEMPLATE_PARAM_INVALID`, `DIM_RULE_CONFLICT` |
| Series assignment নেই | Post আটকায়, কিছু বদলায় না | `NSR_SERIES_NO_ASSIGNMENT` |
| Period lock | Import সারির তারিখ বন্ধ period-এ হলে সারি invalid। Reclass as-of বন্ধ period-এ হলে তৈরি হয় না। Type-এর backdate সীমা period-র চেয়ে কঠোর হতে পারে | `JRN_PERIOD_LOCKED`, `JTY_BACKDATE_LIMIT` |
| Reversal | Reversal Draft মূলের dimension সহ কপি করে। মূলের value এখন inactive হলেও `reversal` type-এ শুধু সেই value চলে (OD-28d)। Reversal-এর নম্বর `reversal` type-এর series থেকে | সফল Post |
| Adjustment | Adjustment journal-ও একই নিয়মে। Reclassification একটা adjustment | নিয়ম অনুযায়ী |
| Concurrency: দুই Post এক series-এ | Counter row lock ক্রম তৈরি করে। দুটো নম্বর ভিন্ন, ফাঁকহীন | সবাই সফল |
| Concurrency: Post চলার সময় value inactive | `FOR SHARE` থাকায় deactivate Post commit পর্যন্ত অপেক্ষা করে | দ্বিতীয় অপরাধী নয়, শুধু অপেক্ষা |
| Concurrency: দুজন template সম্পাদনা করে | `version` optimistic check | `JTY_CONFLICT` |
| Concurrency: Tree সরানো ও নতুন child | Dimension সারি `FOR UPDATE` দিয়ে serial | `DIM_CONFLICT` বা অপেক্ষা |
| Retry | Import commit, scan, mirror, backfill সব idempotent। Post deadlock হলে তিনবার | ডুপ্লিকেট না |
| Submit-এর পর সংযুক্তি infected ধরা পড়ল | Post-এ scan আবার শনাক্তি। Approver reject করলে Draft-এ ফেরত গিয়ে সংযুক্তি বদলানো যায় | `JTY_ATTACH_INFECTED` |
| Cutover চলার সময় Post চলছে | Command legacy সারি `FOR UPDATE` নেয়ায় চলমান পুরোনো-path Post commit হতে অপেক্ষা করে | কোনো নম্বর হারায় না |

### ১৬.৭ Dependencies

**Phase 6 যার ওপর দাঁড়িয়ে (বিদ্যমান card):**

| Card | কেন দরকার |
| --- | --- |
| P1-04, P1-05 | Setting change request (`ChangeApplier` prefix যোগ), checklist-এ নতুন দুটো যাচাই |
| P1-09, P1-12 | `Idempotency-Key`, `AccountingJob`, `AccountingAudit::log` |
| P1-10 | DB guard trigger: নতুন "অপরিবর্তনীয় column" ও posted line-এর dimension সারি সুরক্ষা |
| P2-03 | `JournalService`, `SubmitValidator` (Draft, Submit, Post মোড)। নতুন নিয়ম এখানেই বসে |
| P2-08, P2-10 | Approval resolver (`approval_rule_type` পড়া), SoD |
| P2-11, P2-12 | `VoucherService` facade হয়, `PostingService`-এ নিয়মিত ধাপ দুটো বদলায় |
| P2-13, P2-14 | Reversal ও Adjustment ধরন seed, reclassification adjustment হিসেবে |
| P3-01 থেকে P3-06, P3-13 | `LedgerQuery` (`movements`, `balancesAsOf`)-এ dimension filter, TB, GL, P&L, BS, reconciliation framework |
| Phase 5 (P5 alert) | `acc_alert_state` (mirror stale, scan backlog) |

**Phase 6 যা দেয় (অন্য phase শুধু এগুলোই ডাকবে):**

| Interface | কারা ব্যবহার করবে |
| --- | --- |
| `JournalTypeRegistry::get(company, code)` | Phase 7, 8, 10, 11, 12, 15 নিজের type সক্রিয় করতে ও নিয়ম পড়তে |
| `NumberSeriesService::next(company, seriesCode, date)` | সব phase (সরাসরি নয়, `PostingService` দিয়ে `assign`) |
| `DimensionService::validateLines(company, lines)` | Phase 7 (FX line), 9, 11, 12 (allocation), 14 (posting rule), 15 |
| `acc_journal_line_dimensions` | Phase 12 allocation, Phase 13 budget by dimension, Phase 14 rule mapping, Phase 15 consolidation, Phase 16 partitioning ও archival |
| `DimensionAccessGuard` | Phase 13, 16 (data security, auditor access) |
| Journal template ও import staging | Phase 12 (recurring template-ভিত্তিক), Phase 16 (data migration framework) |

**Phase 1 থেকে 5 card-এ দরকারি বদল:**

| Card | বদল |
| --- | --- |
| P1-10 | `acc_journals.document_date` ও `series_id` অপরিবর্তনীয় তালিকায়। `acc_journal_line_dimensions`-এ posted line-এর সারি INSERT, UPDATE, DELETE বন্ধ। `acc_voucher_allocations` `Schema::appendOnly()` |
| P1-05 | Checklist-এ S11 (journal type seed সম্পূর্ণ) ও S12 (voucher cutover প্রস্তুত), শুধু `journal_mgmt.enabled` চালুর সময় দেখা |
| P1-04 | `ChangeApplier` registry-তে prefix `jtemplate:` |
| P2-03 | Validator-এ নতুন hook: type flag, তারিখ নিয়ম, `DimensionService::validateLines`, সংযুক্তির নিয়ম। Hook তিন মোডে (Draft, Submit, Post) |
| P2-08 | Approval resolver `approval_rule_type = strict` পড়বে |
| P2-11 | `VoucherService` facade, `acc_voucher_counters` পড়া-only (cutover-এর পর) |
| P2-12 | ধাপ 11: `VoucherService::next` সরিয়ে `NumberSeriesService::assign(journal)`। নতুন ধাপ 11a: `DimensionService::lockAndRecheck`। Lock ক্রম নতুন lock শেষে |
| P3-01, P3-03 থেকে P3-06 | `LedgerQuery` ও report endpoint-এ dimension filter ও group-by parameter |
| P3-13 | Reconciliation framework নতুন sub-check id (নিচে §১৬.৯) গ্রহণ করবে |
| P2-02 | Journal পাতায় document date, tag, comment, copy, clone |

### ১৬.৮ Implementation Tasks ও Subtasks

মোট ১৫টা card। ID Pack-এর সাথে হুবহু মিলে। Build ক্রম: P6-01, 02, 03, 04, 05, 09, 10, 11, 06, 07, 08, 12, 13, 14, 15।

| Workstream | Task (Card) | Subtasks | নির্ভরতা | Deliverable | Acceptance |
| --- | --- | --- | --- | --- | --- |
| A Types | P6-01 Journal type registry schema ও migration | Table তৈরি; প্রতি company-তে 16 type seed; নতুন company-র seed hook; orphan যাচাই; composite FK; `ck_acc_journals_type` সরানো; trigger হালনাগাদ | P2-03, P1-10 | Migration, seeder, test | AC6.1 |
| A Types | P6-02 Type আচরণ ও API | `JournalTypeRegistry`, validator hook (manual, range, সংযুক্তি, auto-reverse, approval type), admin API | P6-01 | Service, API, test | AC6.2 |
| B Series | P6-03 Number series schema ও service | 4 table, token parser, `assign`, `next`, `preview`, allocation | P6-01, P2-11 | Service, API, test | AC6.4, AC6.5 |
| B Series | P6-04 Counter migration ও VoucherService facade | Cutover command, dry-run, rollback, facade, Post integration | P6-03, P2-12 | Command, runbook, test | AC6.3, AC6.4 |
| C Dates | P6-05 Document date, posting date, backdate | Column, validator, setting, UI field | P6-02 | Rule, test | AC6.2 |
| D Dimension | P6-09 Dimension framework schema ও service | 3 table + line dimension, built-in `cost_center`, backfill job, `DimensionLineWriter` | P1-10, P3-01 | Schema, service, job | AC6.8 |
| D Dimension | P6-10 Rule, default, validation, balancing | Rule set, resolver, `validateLines`, `lockAndRecheck`, balancing | P6-09, P2-03, P2-12 | Service, test | AC6.8, AC6.9 |
| D Dimension | P6-11 Value lifecycle ও mirror | Hierarchy, effective date, rename history, mirror job, delete rule | P6-09 | Job, API, test | AC6.11 |
| E Template | P6-06 Journal template | 6 table, expression evaluator, version approval, create-draft | P6-02, P6-09 | Service, API, test | AC6.6 |
| F Import | P6-07 Bulk import | 3 table, parse ও validate job, accept, sample file | P6-02, P6-10 | Jobs, API, test | AC6.7 |
| G সংযুক্তি ও নোট | P6-08 Attachment policy, comment, tag, copy, clone, filter, search | 7 table, scan job, download gate, search index | P6-02 | Service, API, test | AC6.13 |
| D Dimension | P6-12 Reclassification | Request table, হিসাব, draft, stale check | P6-10, P3-01, P2-14 | Service, API, test | AC6.10 |
| H Report | P6-13 Report filter, group-by, cache, index, security hook | `LedgerQuery` extension, `acc_dimension_balances`, index, `DimensionAccessGuard` | P6-09, P6-10, P3-01 থেকে P3-06, P3-13 | Query, cache, benchmark | AC6.12 |
| I UI | P6-14 Admin ও journal form UI | Type, series, template, import wizard, dimension admin, line picker, comment, filter | সবার API | Screens, test | AC6.2 থেকে AC6.13 |
| J Release | P6-15 Enablement, RC sub-check, runbook, UAT | Flag, precondition, RC1d, RC1m, RC1n, runbook, UAT | সব | Gate report | AC6.14 |

### ১৬.৯ Testing Requirements

সব test MySQL-এ, class নাম `{Capability}Test`।

| ধরন | কী যাচাই | Class |
| --- | --- | --- |
| Unit | Format token parser, expression evaluator (decimal, sandbox), নিয়ম resolver (account > group > type), tree path ও cycle, তারিখসীমা | `NumberSeriesFormatTest`, `TemplateExpressionTest`, `DimensionRuleResolverTest`, `DimensionTreeTest`, `JournalDateRuleTest` |
| Integration | Type seed ও FK migration, Post-এ series ও allocation, template → Draft, import পুরো পথ, scan gate, mirror sync, reclassification, report filter | `JournalTypeMigrationTest`, `NumberSeriesTest`, `JournalTemplateTest`, `JournalImportTest`, `AttachmentPolicyTest`, `DimensionMirrorTest`, `DimensionReclassTest`, `DimensionReportTest` |
| Concurrency | এক series-এ বহু Post (ব্যর্থতা inject সহ), Post ও value deactivate, Post ও tree সরানো, দুই template সম্পাদনা, দুই import এক company-তে, cutover ও চলমান Post | `GaplessConcurrencyTest`, `DimensionConcurrencyTest`, `VoucherCutoverTest` |
| Accounting-validation | প্রতি value-তে Debit = Credit (balancing), dimension যোগফল = ledger মোট, voucher ক্রমে গর্ত নেই, RC1d, RC1m, RC1n pass ও fail | `DimensionBalancingTest`, `ReconciliationDimensionTest`, `VoucherGaplessReconTest` |
| Security | Company isolation (ID অনুমানে অন্য company-র তথ্য), permission matrix, SoD, EICAR ফাইল, `xlsm`, ZIP bomb, export formula injection, template expression injection, filter JSON injection, attachment IDOR, rate limit | `JournalMgmtSecurityTest`, `DimensionSecurityHookTest` |
| Performance | 10M line × 4 dimension synthetic data-য়: TB by dimension, GL filter পৃষ্ঠা, `EXPLAIN` index ব্যবহার, 500 line Post, 5,000 সারি import, backfill গতি (লক্ষ্য 20,000 line/সেকেন্ড), এক series-এ 100 Post/সেকেন্ড পাঁচ মিনিট | `DimensionPerformanceTest`, `PostThroughputTest` |
| Regression | Flag বন্ধ থাকলে Phase 5-এর সব test হুবহু পাস | বিদ্যমান suite |

**নতুন Reconciliation sub-check (নতুন RC নম্বর নয়):** মাস্টার প্ল্যানে Phase 6-এর জন্য RC10 থেকে RC18 বরাদ্দ নেই, তাই তিনটা sub-check RC1 পরিবারে যোগ হয়, P3-13 framework নতুন sub-check id গ্রহণ করবে।

| Check | কী মিলায় | Fail হলে |
| --- | --- | --- |
| `RC1d` | যে dimension-এ balancing চালু, সেখানে প্রতি posted journal-এ প্রতি value-র যোগফল Debit = Credit (check window-এ) | Journal, value, পার্থক্য |
| `RC1m` | (ক) প্রতি posted line-এ `cost_center_id` আর `cost_center` dimension সারি একই। (খ) প্রতি account ও period-এ সব value + unassigned-এর যোগফল = ledger মোট। (গ) `acc_dimension_balances` = ledger থেকে নতুন হিসাব | Line, account, period, পার্থক্য |
| `RC1n` | প্রতি counter: `cutover_base_no + 1` থেকে `last_no` পর্যন্ত allocation ক্রমিক, নকল নেই। প্রতি posted journal-এর allocation আছে, `voucher_no` মিলে। Legacy `last_no` ≤ `cutover_base_no` | Series, counter\_key, গর্তের নম্বর |

**Golden test:**

| ID | পরিস্থিতি | প্রত্যাশিত ফল |
| --- | --- | --- |
| G6.1 | Series `JV`, gapless, `yearly`, FY2026, `last_no = 100`। 50টা Post একসাথে, তার মধ্যে 7টা (১৭, ১৩, ২১, ২৯, ৩৭, ৪৪, ৪৮ নম্বর অনুরোধের) allocation-এর পর exception | 43টা posted, `voucher_no` 101 থেকে 143 ক্রমিক, গর্ত নেই, নকল নেই। Allocation 43, `last_no = 143`। ব্যর্থ 7 journal-এ `voucher_no` শূন্য |
| G6.2 | Legacy `(A, 2026, JV)` `last_no = 1250`, মান `JV-ACME-2026-000123` রূপ। Cutover চালানো | নতুন counter 1250, `cutover_base_no = 1250`। পরের Post `JV-ACME-2026-001251`। দুই পক্ষ অমিল হলে থামে (`NSR_CUTOVER_PRECONDITION`) |
| G6.3 | Account 6100 (group 6000-এর সন্তান)। Group set: `cost_center` mandatory, `location` forbidden। Type (expense) set: `project` mandatory। Account 6100-এর নিজস্ব set: `project` optional | 6100-এর ফল: cost\_center mandatory, location forbidden, project optional (account জিতে type-কে)। CC ছাড়া line `DIM_REQUIRED` field `lines[1].dimensions.cost_center`। Location সহ `DIM_FORBIDDEN`। CC সহ, project ছাড়া সফল |
| G6.4 | Dimension `fund` balancing। L1 Dr 1,000.0000 fund A, L2 Cr 1,000.0000 fund B | `DIM_BALANCING_VIOLATION` (A +1,000, B -1,000)। L3 Cr 1,000 fund A ও L4 Dr 1,000 fund B যোগ করলে সফল |
| G6.5 | Migration-এর আগের 3টা posted line, `cost_center_id = 5`, Dr 100, 200, 300 | Backfill-এর পর 3টা dimension সারি। TB by CC5 = Dr 600 = আগের query। RC1m pass। Test পরিবেশে এক সারি মুছলে RC1m fail |
| G6.6 | Account 6100, জানুয়ারি: CC-Sales Dr 5,000, CC-Marketing 0। Reclass 2,000 Sales → Marketing, as-of 31 Jan | Draft (`adjustment`): Dr 6100 \[Marketing\] 2,000, Cr 6100 \[Sales\] 2,000। Post-এর পর Sales 3,000, Marketing 2,000, account মোট 5,000, TB মোট অপরিবর্তিত। Submit-এর আগে Sales-এ আরও Dr 500 Post হলে `DIM_RECLASS_STALE` |
| G6.7 | Import 120 সারি, 30 group। Group 4 (unbalanced), 9 (অজানা account), 17 (বন্ধ period), 22 (mandatory dimension নেই) invalid। `partial` accept | 26টা Draft (`import` type), 4 group error কোড সহ। আবার accept চালালেও 26টা। কোনো Post হয় নি |
| G6.8 | Custom type `x_petty`, `backdate_days_limit = 5`, আজ 2026-10-13 | `journal_date` 2026-10-08 সফল। 2026-10-07 `JTY_BACKDATE_LIMIT`। 10-08 period বন্ধ হলে `JRN_PERIOD_LOCKED` |
| G6.9 | Template, parameter `base = 1000`, তিন line: `ROUND(base / 3, 4)`, `ROUND(base / 3, 4)`, `base - ROUND(base / 3, 4) * 2`; উল্টোদিকে Cr `base` | Dr 333.3333, 333.3333, 333.3334; মোট 1,000.0000 = Cr। শেষ expression `base / 3` হলে `JRN_UNBALANCED` |
| G6.10 | Account 6100, Q1: CC A নিজে 200, A-এর সন্তান A1 400, B 400, unassigned 250 | `group_by=dim:cost_center` সারির যোগফল 1,250 = unfiltered মোট। `dim[cost_center]=A` + descendants = 600। সারিসহ `dim_unassigned=1` = 250 |

### ১৬.১০ Security ও Audit Requirements

| Permission | কাজ | SoD |
| --- | --- | --- |
| `accounting.journal_type.view`, `.manage`, `.approve` | Type দেখা, বানানো ও বদলানো, সংবেদনশীল বদল অনুমোদন | Maker ≠ Checker |
| `accounting.number_series.view`, `.manage`, `.approve` | Series, assignment, cutover | Maker ≠ Checker |
| `accounting.journal_template.view`, `.manage`, `.use`, `.approve` | Template বানানো/সম্পাদনা, ব্যবহার, সম্পাদনা অনুমোদন | Template সম্পাদক ≠ অনুমোদক |
| `accounting.journal_import.view`, `.manage`, `.commit` | Upload, পর্যালোচনা, গ্রহণ | Import করা ব্যক্তি (`created_by`) সেই Draft Post করতে পারে না (P2-10) |
| `accounting.attachment_policy.manage` | সংযুক্তি policy | Maker ≠ Checker (নিরাপত্তা দুর্বল করলে) |
| `accounting.journal_comment.create`, `accounting.journal_tag.manage` | Comment, tag | নেই |
| `accounting.dimension.view`, `.manage`, `.approve`, `.reclassify` | Dimension, value, rule; rule অনুমোদন; reclassification | Rule maker ≠ approver; reclass maker ≠ journal poster |

**Audit event (সব `AccountingAudit::log`, আগে ও পরের মান সহ):** `accounting.journal_type.created`, `.updated`, `.activated`, `.deactivated`, `.ranges_changed`; `accounting.number_series.created`, `.updated`, `.assigned`, `.cutover`, `.cutover_dry_run`; `accounting.journal_template.created`, `.edit_requested`, `.edit_approved`, `.retired`, `.used`; `accounting.journal_import.uploaded`, `.validated`, `.accepted`, `.committed`, `.cancelled`; `accounting.attachment_policy.changed`, `accounting.journal_attachment.scanned`, `.quarantined`, `.downloaded`; `accounting.journal_comment.created`, `.edited`; `accounting.journal_tag.changed`; `accounting.journal.copied`, `.cloned`; `accounting.dimension.created`, `.updated`, `.deactivated`, `.balancing_changed`; `accounting.dimension_value.created`, `.renamed`, `.moved`, `.deactivated`, `.mirrored`, `.deleted`; `accounting.dimension_rule.changed`; `accounting.dimension.reclass_previewed`, `.reclass_drafted`, `.backfilled`।

**Data protection:** সংযুক্তি private storage-এ, download signed short-lived URL-এ, শুধু journal দেখার permission থাকলে। Comment-এ পাসওয়ার্ড/কার্ড নম্বর লিখলে সতর্কতা দেখায় (প্রাথমিক pattern)। Dimension data security hook `DimensionAccessGuard::allowedValues(user, dimension)` শুধু interface, default সব value। Phase 13 বিধি বসাবে: report-এ সারি লুকানো বা সমষ্টি মান দেখানো (OD-29a)। Export formula injection রোধ। Retention: সংযুক্তি `retention_until` পর্যন্ত মুছা যায় না, তারপর Phase 16-এর archival পথে (legal hold hook সম্মান করে)।

### ১৬.১১ Deliverables

| Deliverable | Card |
| --- | --- |
| Migration সেট (31টা table, 2টা বিদ্যমান table-এ expand), seeder, `down()` নিরাপত্তা মন্তব্য | P6-01, 03, 05, 06, 07, 08, 09, 10, 12, 13 |
| Service: `JournalTypeRegistry`, `NumberSeriesService`, `JournalTemplateService`, `JournalImportService`, `AttachmentPolicyService`, `DimensionService`, `ReclassificationService`, `DimensionMirrorService` | P6-02 থেকে P6-12 |
| Job ও command: parse, commit, scan, mirror, backfill, balance rebuild, cutover, rollback | P6-04, 07, 08, 09, 11, 13 |
| REST API ও OpenAPI সংযোজন (§১৬.৪-এর তালিকা) | সব card |
| UI: type, series, template, import wizard, dimension admin, rule editor, line picker, comment, tag, saved filter | P6-14 |
| Report সম্প্রসারণ: TB, GL, P&L, BS-এ dimension filter ও group-by | P6-13 |
| Reconciliation sub-check `RC1d`, `RC1m`, `RC1n` | P6-15 |
| Sample import file (CSV ও xlsx), কলাম বর্ণনা | P6-07 |
| Runbook: cutover, rollback, mirror stale, scan backlog, dimension backfill, নম্বরে গর্ত সন্দেহ | P6-15 |
| Test suite (§১৬.৯), golden test G6.1 থেকে G6.10, benchmark রিপোর্ট | P6-15 |
| UAT script ও Gate report | P6-15 |
| Phase 1 থেকে 5 card-এ বদলের merge প্রমাণ (§১৬.৭) | P6-15 |

### ১৬.১২ Acceptance Criteria

| ID | শর্ত |
| --- | --- |
| AC6.1 | Migration প্রতি বিদ্যমান company-তে ঠিক 16টা type seed করে (5 system, 11 phase-defined, শেষেরগুলো inactive)। Orphan journal 0টা। `ck_acc_journals_type` নেই। অজানা type-সহ journal INSERT FK-তে আটকায় |
| AC6.2 | Type flag কার্যকর: manual নিষিদ্ধ type-এ `JTY_MANUAL_NOT_ALLOWED`; account পরিসরের বাইরে `JTY_ACCOUNT_RANGE_VIOLATION`; সংযুক্তি `required` হলে Submit আটকায়; backdate ও future সীমা (G6.8); document date policy; `strict` approval rule না থাকলে Submit আটকায় |
| AC6.3 | Cutover: dry-run পার্থক্য দেখায়। Copy-র পর পরের `voucher_no` গর্ত/নকল/রূপপরিবর্তন ছাড়া পরের নম্বর (G6.2)। Rollback কাজ করে। চলমান Post-এর ওপর প্রভাব নেই |
| AC6.4 | Gapless series-এ প্রতি ব্যর্থ Post নম্বর খরচ করে না (G6.1)। Transaction-এর বাইরে `next` ডাকলে `NSR_NOT_IN_POST_TX` |
| AC6.5 | Post পুনরায় চালালে একই ফল, নতুন নম্বর নয়। Series বাছাই নির্ধারিত (priority, `effective_from`)। সমান priority-এর দুটো assignment save হয় না। `voucher_no` companyজুড়ে unique |
| AC6.6 | Template থেকে শুধু Draft। সম্পাদনা অনুমোদন ছাড়া কার্যকর নয়, আগের version চলে। ব্যবহৃত version অপরিবর্তনীয়। Share scope মানা। Expression sandbox (G6.9) |
| AC6.7 | Import শুধু staging ও Draft। আংশিক গ্রহণ কাজ করে, পুনরায় চালালে নকল নয় (G6.7)। সীমা ও duplicate ফাইল নিয়ম খাটে। কোনো অবস্থায় auto-post নয় |
| AC6.8 | Draft ও Submit-এ dimension নিয়ম: mandatory, forbidden, optional (G6.3), default, effective date, inactive value, balancing (G6.4)। সব ত্রুটি এক তালিকায়, প্রতিটার field সহ। `cost_center_id` ও dimension সারি সবসময় একই (G6.5) |
| AC6.9 | Post নিয়ম আবার যাচাই করে ও value-র `FOR SHARE` নেয়। Post চলার সময় deactivate অপেক্ষা করে। 1,000 অসম্পর্কিত লেখার stress test-এ deadlock 0। Posted line-এর dimension সারি trigger-সুরক্ষিত |
| AC6.10 | Reclassification Draft বানায়, একই account একই মোট, নিট শূন্য। Ledger বদলালে Submit আটকায় (G6.6)। পুরোনো posted line কখনো বদলায় না |
| AC6.11 | Mirror: নতুন, rename, মুছে যাওয়া source সবগুলো ঠিক ধরে। Mirror value সম্পাদনা যায় না। পুনরায় চালালে পরিবর্তন শূন্য। ব্যবহৃত value মুছা যায় না। 3 বিরতি পার হলে alert |
| AC6.12 | TB, GL, P&L, BS-এ filter ও group-by। যোগফল মিলে (G6.10)। RC1m pass। Performance লক্ষ্য §১৬.৩ পূরণ। `EXPLAIN` index ব্যবহার দেখায় |
| AC6.13 | সংযুক্তি policy: scan সবুজ না হলে download ও Submit বন্ধ, infected quarantine। Comment, tag, copy, clone, saved filter, search কাজ করে, সব company-scoped, `posted_hash` অপরিবর্তিত |
| AC6.14 | দুটো flag বন্ধ থাকলে Phase 5-এর সব test পাস। চালুর শর্ত না মিললে `SET_INCOMPLETE`। RC1d, RC1m, RC1n কাজ করে। Static test দেখায় `acc_journals.status` শুধু `PostingService` লেখে |

### ১৬.১৩ Entry ও Exit Criteria

| Gate | শর্ত |
| --- | --- |
| Entry (Phase শুরু) | Phase 5 Release 1 gate পাস। P2-03, P2-11, P2-12, P1-10 স্থির (merge হয়েছে)। বর্তমান `journal_type` column-এর আসল মান ও `opening` এর manual তৈরির আচরণ মুল কোড থেকে নিশ্চিত (seed মানের জন্য)। Production-copy-এ staging |
| Entry (card ধরে) | P6-01 আগে OD-24b; P6-03 আগে OD-25a; P6-04 আগে OD-25b; P6-05 আগে OD-26b; P6-07 আগে OD-27a; P6-10 আগে OD-28a; baseline মানলে লিখিত সম্মতি |
| Exit | AC6.1 থেকে AC6.14 পাস। G6.1 থেকে G6.10 সবুজ। Production-copy-এ cutover রিহার্সাল (dry-run, copy, rollback) সফল। RC1d, RC1m, RC1n UAT data-য় সবুজ। Benchmark সংখ্যা লিখিত। Security test পাস। Phase 1 থেকে 5 card-এ বদল merge। Runbook পর্যালোচিত। Accounting Expertise, Technical Review, Business Approval-এর সিগন-অফ |

### ১৬.১৪ Open Decisions

Phase 6-এর পরিসর OD-24 থেকে OD-29। ছয়টা সংখ্যার ভেতর সব প্রশ্ন আটকাতে পারে না, তাই একই নম্বরের উপর a, b, c, d অক্ষর। অক্ষর অন্য phase-এর নম্বরে যায় না। সব প্রস্তাবিত সিদ্ধান্ত baseline হিসেবে ধরে লেখা। Master OD-117 (gapless আইনি বাধ্যতা), OD-116 (retention) এখানে উত্তর দেওয়া হয়নি।

| OD | কেন জরুরি | বিকল্প, সুবিধা, সীমাবদ্ধতা, প্রভাব | প্রস্তাবিত সিদ্ধান্ত | কে সিদ্ধান্ত নেবে | কখন |
| --- | --- | --- | --- | --- | --- |
| OD-24a Custom journal type | না ঠিক হলে P6-02-এর নিয়ম ও UI লিখা যায় না | (ক) বন্ধ, শুধু system ও phase type: সহজ, রিপোর্ট সহজ, কিন্তু ক্লায়েন্টের নিজস্ব খাতার চাহিদা মেটে না। (খ) অনুমতি, কোড `x_` দিয়ে: নমনীয়, ভবিষ্যতের phase code-এর সাথে ধাক্কা নেই, type বেশি হলে রিপোর্ট জটিল। (গ) অনুমতি, prefix ছাড়া: পরে phase code ধাক্কা খাবে | (খ), flag `journal_types.allow_custom` default বন্ধ | Business Approval | এখনই (P6-02-এর আগে) |
| OD-24b Phase-defined type কখন seed | Composite FK-র আগে সব code থাকতে হবে, নইলে বড় table-এ বারবার migration | (ক) Phase 6-এ 11টা সব inactive seed: এক migration, পরে পরে শুধু `is_active` বদলায়, কিন্তু না ব্যবহৃত তালিকা দেখায়। (খ) প্রতি phase নিজের type INSERT করে: পরিষ্কার, কিন্তু প্রতি company-তে data migration লাগে | (ক) | Technical Review | এখনই (P6-01-এর আগে) |
| OD-25a Gapless default | আইনি ঝুঁকি ও সর্বোচ্চ concurrency দুটোর বানিজ্য | (ক) সব series gapless (P2-11-এর মতো): নিরাপদ, এক series-এ Post সিরিয়াল। (খ) শুধু বাধ্যতামূলক type gapless, বাকি (import, event\_generated) `allocate_on = submit`: গতি বেশি, গর্ত হতে পারে। (গ) কোনো series gapless নয়: আইনি থেকে সবচেয়ে দুর্বল | (ক) default, (খ) শুধু স্পষ্ট series-ওয়ার Maker-Checker-এ | Accounting Expertise (সাথে Business Approval, OD-117-এর সাথে মিলিয়ে) | এখনই (P6-03-এর আগে) |
| OD-25b পুরোনো counter-এর ভাগ্য | Cutover-এর পর rollback ও দুই counter-র drift | (ক) Freeze + rollback command: সহজ, এক lock; rollback হাতে। (খ) কয়েক release দুই জায়গায় লেখা: rollback সহজ, কিন্তু দুটো lock, drift ঝুঁকি। (গ) সরাসরি drop: rollback নেই | (ক), legacy table একটা period close চক্র পর contract migration-এ বাদ | Technical Review | এখনই (P6-04-এর আগে) |
| OD-25c Reset ও বছর | বছর বদলে নম্বর কোথায় শুরু হবে | (ক) আর্থিক বছর (`fiscal_year`) ধরে yearly: P2-11-এর মতো। (খ) ইংরেজি বছর। (গ) মাস ধরে: বড় লেনদেনে ভালো, নম্বর গড়ে কমে | (ক) | Accounting Expertise | এখনই (P6-03-এর আগে) |
| OD-26a Template সম্পাদনার অনুমোদন | ভুল template সবার journal ভুল করাবে | (ক) সবসময় দ্বিতীয় অনুমোদন। (খ) শুধু company/role share করা template-এ। (গ) কখনো নয়: দ্রুত, কিন্তু নিয়ন্ত্রণ দুর্বল | (ক) | Business Approval | এখনই (P6-06-এর আগে) |
| OD-26b Backdate ও future সীমা | পুরোনো তারিখে journal ঢোকা আটকাতে | (ক) System type-এ কোনো সীমা নেই (শুধু period নিয়ম), custom-এ বাধ্যতামূলক। (খ) manual 31 দিন আগে, 0 পরে: কঠোর, পুরোনো অভ্যাস ভাঙতে পারে। (গ) 7 দিন: সবচেয়ে কঠোর | (ক) শুরুতে, দুই close চক্রের ডেটা দেখে (খ)-এ নেওয়া | Business Approval ও Accounting Expertise | এখনই (P6-05-এর আগে) |
| OD-26c Document date পরে হলে | ভবিষ্যতের নথিতে আগের তারিখে হিসাব ঢোকে | (ক) `warn`। (খ) `block`: কঠোর, বৈধ কেস আটকায় (যেমন advance)। (গ) `allow` | (ক) | Accounting Expertise | P6-05-এর আগে |
| OD-27a Import সীমা ও মোড | বড় ফাইলে সময়, ভুলে আংশিক ডেটা | (ক) `partial`, 5,000 সারি, csv ও xlsx। (খ) `all_or_nothing`: সহজ হিসাব, এক ভুলে পুরো ফাইল আটকায়। (গ) শুধু csv: xlsx-এর নিরাপত্তা ঝুঁকি নেই, কিন্তু ব্যবহারকারীর কষ্ট | (ক) | Business Approval | P6-07-এর আগে |
| OD-27b Malware scanner | Scan ছাড়া সংযুক্তি নিরাপদ নয় | (ক) Self-hosted scanner (ClamAV-ধরন), বন্ধ থাকলে fail-closed। (খ) Cloud scan API: নথি বাইরে যায়, OD-115 (data residency) ধরতে হবে। (গ) Scan বন্ধ: প্রস্তাব নয় | (ক), fail-closed | Technical Review | P6-08-এর আগে |
| OD-28a Dimension-level balancing | Fund accounting client-এ দরকার, নাহলে দরকার নেই | (ক) অন্তর্ভুক্ত, default বন্ধ: ভবিষ্যতে migration লাগে না, কোড বাড়ে। (খ) পরে যোগ করা: সহজ হলেও trigger ও RC1d পরে লেখা | (ক) | Business Approval ও Accounting Expertise | P6-10-এর আগে |
| OD-28b Hierarchy-র ইতিহাস | Value সরালে আগের roll-up বদলায় | (ক) বর্তমান গাছ ধরে roll-up: সহজ, দ্রুত, ইতিহাস বদলায়। (খ) as-of গাছ সংরক্ষণ: সঠিক, কিন্তু query জটিল ও বড় | (ক), সরানোর আগে সতর্কতা, বন্ধ period-এর report-এর জন্য বন্ধের সময় snapshot export | Accounting Expertise | P6-11-এর আগে |
| OD-28c Mirror এক্স ও দ্বন্দ্ব | Master data ERPFlow-এর (G8), নাম ও মুছে যাওয়ার আচরণ দরকার | (ক) Mirror পড়া-only, মুছলে inactive। (খ) Accounting-এ নিজস্ব alias নাম অনুমতি: রিপোর্ট সুবিধা, কিন্তু দুই সত্য। Department ও location ERPFlow-এ master থাকলে তাও mirror (কোন source তালিকা, Technical Review) | (ক) | Technical Review | P6-11-এর আগে |
| OD-28d Reversal-এ inactive value | Reversal আটকে গেলে ভুল শোধরানো যায় না | (ক) `reversal` type মূলের value-তে ছাড়: সহজ, এক পথ। (খ) আগে value চালু করতে বাধ্য করা: নিয়ম খাঁটি, কিন্তু ব্যবহারকারীর কষ্ট। (গ) default value-তে ম্যাপ: বিশ্লেষণ ভুল | (ক) | Accounting Expertise | P6-10-এর আগে |
| OD-29a Dimension data security | কোন value দেখা যাবে, প্রথমে নির্ধারণ না হলে Phase 13 আটকাবে | (ক) সারি লুকিয়ে দেওয়া, "restricted" সারিতে মোট দেখানো: যোগফল মেলে, মোট ফাঁস হয়। (খ) সারি ও মোট উভয় লুকানো: ফাঁস নেই, যোগফল মেলে না | (ক) | Business Approval | Phase 13-এর আগে |
| OD-29b Reclassification-এর ধরন | Auditor কোন ব্যাখ্যা চাবে | (ক) একটা adjustment journal, নিট balance সরানো: সহজ, কিন্তু প্রতিটা মূল journal-এর সূত্র নেই। (খ) মূল journal প্রতি reversal + নতুন value-তে rebook: সূত্র পরিষ্কার, ভারী ও অনেক journal | (ক), (খ) পরে চাইলে | Accounting Expertise | P6-12-এর আগে |
| OD-29c Dimension projection table | Benchmark লক্ষ্য না মিললে নকশা বদলাতে হবে | (ক) Join + derived cache `acc_dimension_balances`। (খ) Post-এর সময় append-only `acc_dimension_postings` (date, account, amount কপি): দ্রুত, কিন্তু Post ভারী ও P2-12 বদলাতে হয়। (গ) `acc_journal_line_dimensions`-এ date/account কপি: Post-এর আগে সঠিক নয় | (ক), লক্ষ্য (TB by dimension p95 3 সেকেন্ড) না মিললে (খ) | Technical Review | P6-13 benchmark-এর পর, Phase 16-এর আগে |

## ১৭. Phase 7: Multi-Currency ও FX

### ১৭.১ Objective ও Scope

**Objective:** Company-র base currency ছাড়া অন্য currency-তে লেনদেন লেখা, rate-এর উৎস ও অনুমোদন নিয়ন্ত্রণ করা, আর মাস শেষে unrealized ও settlement-এ realized gain-loss হিসাব করা। `debit` ও `credit` সবসময় base currency-তে থাকে (G3)। বিদেশি currency-র অঙ্ক থাকে `txn_debit` ও `txn_credit`-এ, সাথে ব্যবহৃত `exchange_rate`, `rate_type_id` ও `rate_date`। Posted journal-এর rate কখনো বদলায় না। Rate ভুল হলে নতুন লেনদেনে শুধরানো হয়, পুরোনোটা Reversal বা Adjustment দিয়ে।

Baseline: একটা company-র একটাই base (functional) currency। Phase 1-এর `base_currency` ও `currency_precision` setting বিদ্যমান। Capability flag `multi_currency.enabled` default বন্ধ। বন্ধ থাকলে আচরণ Phase 6 শেষের মতোই, শুধু base currency।

| দিক | বিবরণ |
| --- | --- |
| ভেতরে | Currency master ও ISO 4217 যাচাই, currency-ভিত্তিক precision ও rounding mode |
| ভেতরে | Rate type (spot, average, closing, historical, budget), তারিখ-ভিত্তিক rate, inverse ও cross rate (base দিয়ে) |
| ভেতরে | Rate উৎস: manual, CSV import, provider API (provider interface তৈরি, বাছাই OD-31), rate অনুমোদন ও tolerance, ব্যবহারের পর lock |
| ভেতরে | বিদেশি currency journal: line-এ `txn_*` column, `FxValidator`, rounding-difference line, account-এর `allow_foreign_currency` ও `currency_restriction` |
| ভেতরে | Account-এর monetary ও non-monetary শ্রেণি, equity-র historical rate |
| ভেতরে | Period-end revaluation (preview, simulation, final, unrealized journal `fx_revaluation`, পরের period-এ auto-reversal), Phase 9-এর open item hook |
| ভেতরে | `FxRealizedService`: settlement-এ realized gain-loss, Phase 9 ও 10 ডাকবে |
| ভেতরে | Phase 4-এর `event_currency_policy`-তে নতুন মান `convert` (অনুমোদনসহ) |
| ভেতরে | GL, TB, account ledger-এ বিদেশি currency column, currency-ভিত্তিক balance report, FX gain-loss report |
| ভেতরে | Phase 15-এর translation-এর জন্য rate সরবরাহ, reconciliation RC14, audit, UAT script |
| বাইরে | Translation ও consolidation (Phase 15), কারণ entity group লাগে। শুধু rate দেওয়া হয় |
| বাইরে | Hyperinflation accounting ও base currency বদল, কারণ G3 অনুযায়ী base স্থির (OD-36a) |
| বাইরে | Open item-এর নিজস্ব settlement logic (Phase 9) ও bank matching (Phase 10)। এখানে শুধু `FxRealizedService` |
| বাইরে | Hedge accounting ও derivative, কারণ standard-নির্ভর, G5 অনুযায়ী কোডে বাঁধা নয় |
| বাইরে | Budget rate type-এর ব্যবহার, শুধু সংরক্ষণ (Phase 13 পড়বে) |
| বাইরের কেন | Subledger, bank আর group-এর নিজস্ব ডেটা ও lock আছে। এখানে ঢোকালে lock ক্রম ভাঙার ঝুঁকি |

### ১৭.২ Functional Requirements

**ক. Currency master**

- ক.১ Company নিজের enabled currency-র তালিকা রাখে। Code ISO 4217 তালিকা (config file, কোডে বাঁধা নয়) থেকে যাচাই হয়।
- ক.২ Base currency আপনা আপনি `is_base` হয়ে থাকে (Phase 1-এর `base_currency` থেকে seed)। Disable বা বদল যায় না।
- ক.৩ প্রতি currency-র `precision` (0 থেকে 4) ও `rounding_mode`। প্রথম ব্যবহারের পর precision lock।
- ক.৪ Currency disable করলে নতুন লেনদেন আটকায়, পুরোনো লেনদেন ও report যেমন আছে তেমন।

**খ. Rate type ও rate**

- খ.১ Seed rate type: `spot`, `average`, `closing`, `historical`, `budget`। Custom type যোগ করা যায়।
- খ.২ Rate সর্বদা তারিখ-ভিত্তিক (`effective_date`)। এক pair, এক type, এক তারিখে একটাই approved rate।
- খ.৩ Inverse ও cross rate হিসাব করা হয়, জমা রাখা হয় না। Cross rate base currency দিয়ে (triangulation)।
- খ.৪ উৎস: manual entry, CSV import, provider API। প্রতিটা rate-এ `source` ও `source_ref` থাকে।
- খ.৫ Rate অনুমোদন (maker-checker) ও আগের rate-এর সাথে tolerance যাচাই। Tolerance ছাড়ালে অনুমোদন বাধ্যতামূলক।
- খ.৬ Posted journal-এ ব্যবহৃত rate lock হয়। ভুল হলে supersede (নতুন rate, অনুমোদনসহ), পুরোনো সারি থাকে (OD-34)।
- খ.৭ Rate না থাকলে আচরণ: `block` বা `use_previous` (সীমাসহ, OD-32a)।

**গ. Foreign-currency journal**

- গ.১ Line-এ `txn_currency`, `txn_debit`, `txn_credit`, `exchange_rate`, `rate_type_id`, `rate_date`। Draft-এ rate অটো ভরা, অনুমতিসহ override।
- গ.২ `FxValidator`: এক foreign currency-র journal-এ transaction currency-তে ও base-এ balance। Rounding পার্থক্য configured FX rounding account-এ একটা line।
- গ.৩ Account-এ `allow_foreign_currency` ও `currency_restriction` যাচাই।
- গ.৪ Account-এর monetary, non-monetary, equity historical শ্রেণি (`acc_fx_account_profiles`)।
- গ.৫ Mixed-currency journal শুধু তালিকাভুক্ত journal type-এ (settlement, revaluation ইত্যাদি, OD-36)।

**ঘ. Revaluation**

- ঘ.১ Period-end run: preview, simulation (কাল্পনিক rate-সহ), final। Final run একটা Draft journal (`fx_revaluation`) বানায়।
- ঘ.২ Unrealized gain-loss account-ও-currency ধরে (granularity OD-33)। Phase 9 এলে open item ধরে hook।
- ঘ.৩ পরের period-এ auto-reversal, Phase 12-এর `auto_reverse_on` mechanism দিয়ে (OD-35)।
- ঘ.৪ এক period-এ একটাই সক্রিয় final run। Posted হলে আবার চালানো যায় না। ভুল হলে Reversal ও নতুন run।

**ঙ. Realized FX**

- ঙ.১ `FxRealizedService::compute(settlement)` পুরোনো বানিয়ার rate ও settlement rate তুলনা করে gain-loss line দেয়। Phase 9 ও 10 নিজেদের settlement journal-এ সেই line বসায়।
- ঙ.২ Equity ও non-monetary account কখনো revalue হয় না, historical rate-এ থাকে।

**চ. Event, report ও অন্য phase**

- চ.১ Phase 4 `event_currency_policy`-এ `convert`: event-এর তারিখের rate দিয়ে base-এ রূপান্তর, অনুমোদনসহ (OD-33a)।
- চ.২ GL, TB, ledger-এ foreign column, currency-ভিত্তিক balance, FX gain-loss report, rate history report।
- চ.৩ Phase 15-এর জন্য period-ভিত্তিক closing ও average rate-এর সেট সরবরাহ (`FxService::rate`)।
- চ.৪ Rate বদলের impact রিপোর্ট: পুরোনো posted কিছু বদলায় না, Draft-এর জন্য আন্দাজি পার্থক্য দেখায়।
- চ.৫ RC14 reconciliation, অডিট, alert (সক্রিয় currency-র rate নেই)।

### ১৭.৩ Technical Requirements

**Service**

| Service | কাজ | কোথায় ডাকা হয় |
| --- | --- | --- |
| `CurrencyService` | Currency enable, disable, precision, ISO যাচাই | API, setup |
| `RateTypeService` | Rate type seed ও custom type | API, enable পথ |
| `RateService` | Rate তৈরি, submit, approve, reject, supersede, lock | API, import, provider job |
| `RateImportService` | CSV validate ও apply (batch) | API |
| `RateProvider` (interface) | Provider-এর কাছ থেকে rate আনা (adapter OD-31) | `FxRateFetchJob` |
| `FxService` | `rate()`, `convert()`, inverse, cross, rounding, missing-rate আচরণ (Master Plan ৭) | JournalService, Phase 9, 10, 15 |
| `FxValidator` | Journal-এ currency অনুযায়ী balance, account নিয়ম | P2-03 validator hook |
| `FxAccountService` | `acc_fx_accounts` mapping ও account profile | API, `FxRevaluationService` |
| `FxRealizedService` | `compute(settlement)`, ফল ইতিহাসসহ জমা | Phase 9, 10 |
| `FxRevaluationService` | `run(company, periodId)`, preview, simulation | API, job, Phase 12 |
| `FxEventConversionService` | Held event-এর `convert` প্রস্তাব ও অনুমোদন | Phase 4 intake |
| `FxRateLockHook`, `FxBalanceHook` | Post-এর সময় rate lock ও currency balance cache | `PostingService` (hook point) |
| `FxEnableService` | Flag চালুর precondition ও seed | Settings change applier |

**Job ও queue**

| Job | Queue | Schedule | কাজ |
| --- | --- | --- | --- |
| `FxRevaluationJob` | `accounting-fx` | চালু করলে | Run-এর হিসাব chunk-এ (account-currency 500টা করে) |
| `FxRateFetchJob` | `accounting-fx` | দৈনিক (cron, setting-এ) | Provider থেকে rate, draft হিসেবে |
| `FxRateGapMonitorJob` | `accounting-fx` | দৈনিক | সক্রিয় currency-র rate নেই কিনা, `acc_alert_state`-এ alert |
| `FxPreviewPurgeJob` | `accounting-fx` | দৈনিক | পুরোনো preview run মুছা |
| `FxBalanceRebuildJob` | `accounting-fx` | on-demand | `acc_period_currency_balances` পুনর্নির্মাণ |

সব job `AccountingJob` base (P1-12) ধরে, company context সহ।

**Concurrency ও lock**

- Global lock ক্রম বদলায় না: journal, approval request, account, period, voucher counter। Phase 7-এর নতুন lock শেষে: `acc_exchange_rates` (id ক্রমে), তারপর `acc_period_currency_balances` (account\_id, currency ক্রমে)।
- Revaluation run: run row `FOR UPDATE`, তারপর transaction শেষ করে আলাদা transaction-এ `JournalService` দিয়ে Draft, তারপর আলাদা transaction-এ run-এ `journal_id` বসানো। এতে run lock আর journal lock উল্টাবার হয় না।
- এক period-এ একটা সক্রিয় final run: generated `active_key` ও unique key।
- Rate approve ও একইসময়ে journal post: post transaction rate row-এ `locked_at` বসায়, approve transaction `version` check করে।
- Rate import ও manual entry-এর দ্বন্দ্ব: unique key সিদ্ধান্ত নেয়, হারা পক্ষ `FX_RATE_DUPLICATE` পায়।

**Performance লক্ষ্য**

| বিষয় | লক্ষ্য |
| --- | --- |
| `FxService::rate` (cache hit বা miss) | p95 5ms (request-cache, প্রতিটা rate বদলে version bump) |
| `FxValidator` 100 line-এর journal | অতিরিক্ত p95 20ms |
| CSV import 10,000 row | 60 সেকেন্ডে validate, 120 সেকেন্ডে apply |
| Final revaluation, 2,000 account-currency group | 5 মিনিট (cache `acc_period_currency_balances` থেকে পড়া) |
| Balance-by-currency report, এক period | p95 1 সেকেন্ড |
| Post-এ hook-এর অতিরিক্ত সময় | p95 15ms |

**Failure handling ও security**

- Hook ব্যর্থ হলে পুরো post transaction rollback হয়, journal Approved অবস্থায় থাকে, error `FX_POST_HOOK_FAILED`। আংশিক post হয় না।
- Provider ব্যর্থ হলে আগের rate অক্ষত, job retry backoff সহ, তিনবার ব্যর্থ হলে alert। Provider-এর secret Setting-এর secret store reference, doc বা log-এ নয়।
- CSV file-এ formula injection আটকানো (`=`, `+`, `-`, `@` দিয়ে শুরু ঘর অগ্রাহ্য), আকার সীমা 5 MB।
- Rate approve ও journal-এ rate override আলাদা permission। সব write API `Idempotency-Key` (P1-09) নেয়।
- Revaluation-এর ফল পুনরায় চালালে একই input hash একই ফল দেয় (deterministic)।

### ১৭.৪ Data Model ও API

**নতুন table (14টা, সব `acc_` prefix, পুরো column spec Pack-এ)**

| Table | কাজ | মূল column |
| --- | --- | --- |
| `acc_currencies` | Company-র enabled currency | `code`, `minor_unit`, `precision`, `rounding_mode`, `is_base`, `is_active`, `first_used_at` |
| `acc_rate_types` | Rate type | `code`, `kind`, `usage_hint`, `tolerance_pct`, `requires_approval` |
| `acc_exchange_rates` | তারিখ-ভিত্তিক rate | `from_currency`, `to_currency`, `rate_type_id`, `effective_date`, `rate DECIMAL(20,10)`, `source`, `status`, `locked_at`, `supersedes_rate_id` |
| `acc_exchange_rate_events` | Rate-এর ইতিহাস (append-only) | `rate_id`, `event`, `old_value`, `new_value`, `actor_id`, `reason` |
| `acc_exchange_rate_usages` | কোন posted journal কোন rate ব্যবহার করেছে (append-only) | `rate_id`, `journal_id`, `is_override`, `line_count` |
| `acc_rate_import_batches` | CSV import batch | `file_sha256`, `status`, `rows_ok`, `rows_error`, `error_report` |
| `acc_rate_sources` | Provider কনফিগ (OD-31) | `provider`, `pairs`, `secret_ref`, `schedule`, `last_status` |
| `acc_fx_accounts` | FX system account (realized, unrealized, rounding) | `purpose`, `currency_code`, `account_id` |
| `acc_fx_account_profiles` | Account-এর FX প্রোফাইল | `account_id`, `monetary_class`, `revalue_enabled`, `reval_rate_type_id` |
| `acc_fx_revaluation_runs` | Revaluation run | `period_id`, `mode`, `status`, `journal_id`, `input_hash`, `active_key` |
| `acc_fx_revaluation_lines` | Run-এর হিসাব line | `account_id`, `currency_code`, `foreign_balance`, `carrying_base`, `revalued_base`, `adjustment`, `open_item_ref` |
| `acc_fx_realized_entries` | Settlement-এ realized gain-loss (idempotent) | `settlement_ref_type`, `settlement_ref_id`, `item_ref`, `realized_amount`, `journal_id` |
| `acc_fx_event_conversions` | Event `convert` প্রস্তাব ও অনুমোদন | `inbox_id`, `rate_snapshot`, `converted_amount`, `status` |
| `acc_period_currency_balances` | Currency-ভিত্তিক balance cache | `period_id`, `account_id`, `currency_code`, `txn_debit_total`, `txn_credit_total`, `base_debit_total`, `base_credit_total` |

**বিদ্যমান table-এ expand migration (Master Plan ৬-এর নাম হুবহু)**

| Table | নতুন column (সব nullable বা default সহ) |
| --- | --- |
| `acc_journals` | `txn_currency CHAR(3)` |
| `acc_journal_lines` | `txn_currency`, `txn_debit`, `txn_credit` DECIMAL(20,4), `exchange_rate` DECIMAL(20,10), `rate_type_id`, `rate_date` |
| `acc_accounts` | `allow_foreign_currency` (default 0), `currency_restriction` (NULL = সব enabled currency) |

বাস্তবতা: base currency line-এ `txn_*` NULL থাকে, পড়ার সময় `COALESCE`। তাই পুরোনো সারি আর পুরোনো code অপরিবর্তিত। এই ছয় column P1-10 trigger-এর "অপরিবর্তনীয় column" তালিকায় যোগ হয় (P7-07)।

**API (prefix `/api/accounting/fx`, সব write-এ `Idempotency-Key`)**

| গ্রুপ | Endpoint | Permission |
| --- | --- | --- |
| Currency | `GET/POST /currencies`, `PATCH /currencies/{code}`, `POST /currencies/{code}/disable` | `view`, `manage` |
| Rate type | `GET/POST /rate-types`, `PATCH /rate-types/{id}` | `view`, `manage` |
| Rate | `GET /rates`, `POST /rates`, `POST /rates/{id}/submit`, `/approve`, `/reject`, `/supersede`, `GET /rates/{id}/events`, `GET /rates/lookup` | `view`, `rate_enter`, `rate_approve` |
| Import | `POST /rate-imports`, `GET /rate-imports/{id}`, `POST /rate-imports/{id}/apply`, `/discard` | `rate_import` |
| Provider | `GET/POST/PATCH /rate-sources`, `POST /rate-sources/{id}/fetch` | `manage` |
| FX account | `GET /accounts`, `PUT /accounts/{purpose}`, `GET/PUT /account-profiles/{accountId}` | `view`, `manage` |
| Revaluation | `POST /revaluation-runs`, `GET /revaluation-runs`, `GET /revaluation-runs/{id}`, `/lines`, `POST /{id}/create-journal`, `/cancel` | `view`, `revalue` |
| Realized | `GET /realized-entries` (compute শুধু service) | `view` |
| Event conversion | `GET /event-conversions`, `POST /{id}/approve`, `/reject` | `convert_approve` |
| Report | `GET /reports/balance-by-currency`, `/fx-gain-loss`, `/rate-usage`, `/rate-change-impact`; বিদ্যমান GL, TB, ledger-এ `currency`, `include_txn` parameter | `view` |
| Translation | `GET /rates/translation-set?period_id=` (Phase 15 পড়বে) | `view` |
| Enable | `POST /enable-check` (precondition তালিকা), flag বদল Settings change request-এ | `manage` |

### ১৭.৫ Business Rules ও Configuration

**Business Rules (R7.1 থেকে R7.33)**

| R | নিয়ম | কোথায় প্রয়োগ |
| --- | --- | --- |
| R7.1 | Base currency প্রথম posted journal-এর পর আর বদলায় না। `debit` ও `credit` সবসময় base-এ | P7-01, P7-07 |
| R7.2 | Currency code ISO 4217-এর তিন অক্ষরের তালিকার (config file) মধ্যে থাকতে হবে | P7-01 |
| R7.3 | `precision` 0 থেকে 4। প্রথম ব্যবহারের পর (`first_used_at`) precision ও `rounding_mode` lock। Base-এর precision Phase 1-এর `currency_precision`-এর সাথে এক | P7-01 |
| R7.4 | সব rounding একটা জায়গায় (`FxService::round`), target currency-র `rounding_mode` ধরে। অন্য কোথাও নিজস্ব rounding নয় | P7-03 |
| R7.5 | Rate `DECIMAL(20,10)`, শূন্যের বেশি। সাধারণত pair হল X থেকে base: 1 X = rate base (OD-30) | P7-02 |
| R7.6 | এক (company, from, to, type, তারিখ)-এ একটাই approved rate | P7-02 |
| R7.7 | Inverse = 1 ÷ rate, cross A→B = rate(A→base) ÷ rate(B→base); 10 দশমিকে half-even। দুই পানের type ও তারিখ একই। জমা রাখা হয় না | P7-03 |
| R7.8 | চাওয়া তারিখের আগের সবচেয়ে নতুন approved rate। Line-এ `rate_date` = যে rate-এর `effective_date` ব্যবহৃত হয়েছে | P7-03 |
| R7.9 | Rate না থাকলে `block`। `use_previous` শুধু বয়স `fx.max_rate_age_days`-এর মধ্যে, নাহলে `FX_RATE_STALE` | P7-03 |
| R7.10 | Journal শুধু `approved` (আর supersede হলেও lock করা) rate নেয়। Draft, pending, rejected নয় | P7-03 |
| R7.11 | Rate-এর maker আর approver আলাদা মানুষ (সেটিংয়ে বন্ধ করা যায়, তবে audit-এ দেখায়) | P7-04 |
| R7.12 | আগের approved rate-এর তুলনায় পার্থক্য `tolerance_pct`-এর বেশি হলে "outside tolerance" চিহ্ন ও কারণ বাধ্যতামূলক। সীমার ভেতরে স্বয়ংক্রিয় অনুমোদন শুধু CSV ও provider-এ সেটিং অনুযায়ী | P7-04, P7-05 |
| R7.13 | Journal line-এ হাতে দেওয়া rate সরথিত rate-থেকে `fx.journal_rate_override_tolerance_pct`-এর বেশি সরলে `rate_override` permission ও কারণ লাগে | P7-08 |
| R7.14 | Journal post হলে ব্যবহৃত rate row-এ `locked_at` বসে আর usage row লেখা হয়। Locked rate edit, delete, reject যায় না | P7-08, P7-04 |
| R7.15 | Locked rate ভুল হলে supersede (নতুন rate, কারণ, দ্বিতীয় অনুমোদন)। পুরোনো সারি সেই মানে থাকে, নতুন লেনদেন নতুন rate নেয় (OD-34) | P7-04 |
| R7.16 | Draft-এর rate পরে supersede বা reject হলে Submit আটকায় (`FX_RATE_SUPERSEDED`) যতক্ষণ না "rate refresh" করা হয় | P7-08 |
| R7.17 | Foreign line: `txn_currency` ≠ base, `exchange_rate` > 0, `rate_type_id`, `rate_date` বাধ্যতামূলক। Base line-এ ছয় column সব NULL। `txn_debit`, `txn_credit`-এর একটাই ধনাত্মক (revaluation line-এ দুটোই 0) | P7-07 |
| R7.18 | Line-এর base amount = `round(txn × rate)`। `fx.line_conversion_tolerance_units`-এর বেশি পার্থক্য `FX_LINE_CONVERSION_MISMATCH`, তালিকাভুক্ত mixed type ছাড়া (OD-36) | P7-08 |
| R7.19 | এক foreign currency-র journal-এ সবাই ওই currency-র txn line-এর যোগফল Debit = Credit, আর base-এও Debit = Credit | P7-08 |
| R7.20 | Base-এর rounding পার্থক্য `fx.rounding_max_units` পর্যন্ত হলে একটা rounding line FX rounding account-এ। বেশি হলে `FX_BASE_ROUNDING_EXCEEDED` | P7-08 |
| R7.21 | `allow_foreign_currency = 0` account-এ শুধু base line। `= 1` হলে enabled currency, `currency_restriction` থাকলে সেই তালিকায় | P7-08 |
| R7.22 | Disabled currency-তে নতুন Draft, Submit, Post হয় না। Post-এও আবার যাচাই | P7-08 |
| R7.23 | প্রতি বিদেশি-অনুমত account-এ `monetary_class`: `monetary`, `non_monetary`, `equity_historical`। Profile ছাড়া account-এ foreign line নয় | P7-06, P7-08 |
| R7.24 | Revaluation শুধু posted line, period `end_date` পর্যন্ত। লক্ষ্য = `round(foreign × closing rate)`, adjustment = লক্ষ্য − carrying base | P7-10 |
| R7.25 | এক period-এ একটা সক্রিয় final run। Period `closed` হলে নয়। Rate বাকি থাকলে পুরো run ব্যর্থ (আংশিক নয়) | P7-10, P7-11 |
| R7.26 | Revaluation journal: type `fx_revaluation`, Draft `JournalService` দিয়ে, strict approval, post শুধু `PostingService`। `journal_date` = period শেষ। `auto_reverse_on` = পরের period-এর প্রথম দিন | P7-11 |
| R7.27 | Preview ও simulation journal বানায় না, rate lock করে না, পুরোনো হলে purge | P7-10 |
| R7.28 | Settlement-এ realized = settlement-এর base − মূল carrying base (পাওনায় এক দিক, দেনায় উল্টো দিক)। একই (settlement, item) দুইবার হয় না | P7-09 |
| R7.29 | Equity ও non-monetary line নিজের তারিখের historical rate-এ থাকে, revalue বা realized হয় না | P7-06, P7-10 |
| R7.30 | Event policy `convert`: প্রস্তাব, snapshot, অনুমোদন, তারপর mapping আবার চালানো। প্রত্যাখ্যানে `held_currency` | P7-12 |
| R7.31 | Flag বন্ধ করা যায় শুধু যদি কোনো posted foreign line না থাকে (`FX_DISABLE_BLOCKED`) | P7-16 |
| R7.32 | Rate বদলালে posted ডেটা বদলায় না। Draft তার নিজের rate ধরে থাকে যতক্ষণ না refresh করা হয় | P7-04, P7-13 |
| R7.33 | Closed period-এর তারিখে নতুন rate শুধু যদি ওই তারিখে কোনো usage ও final revaluation না থাকে (নাহলে `FX_RATE_PERIOD_CLOSED`) | P7-04 |

**Configuration সারণি ক: Currency, rate ও উৎস**

সব key `SettingRegistry`-তে, company scope, সবাই `accounting.fx.manage` দিয়ে বদলায়। "দ্বিতীয় অনুমোদন" মানে Phase 1-এর `acc_setting_change_requests` পথ (`ChangeApplier`), যা তালিকায় থাকা key-এ নিয়মিত। `sensitive_settings`-এ যোগ হয়: `multi_currency.enabled`, `event_currency_policy`, `fx.mixed_currency_journal_types`, `fx.revaluation_rate_type`, `fx.revaluation_auto_reverse`, `fx.rate_approval_mode`।

| Key | উদ্দেশ্য ও Business Impact | Option ও Validation | Default Policy | Scope | বাধ্যতামূলক কি | কে বানায়, বদলায়, অনুমোদন করে | বদলের প্রভাব | Lock ও Audit |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `multi_currency.enabled` (flag) | Phase 7 চালু বা বন্ধ। ভুল করলে rate ছাড়া foreign journal ও ভুল balance | bool, চালুর আগে P7-16-এর precondition সবুজ | false (বন্ধ) | Company | হ্যাঁ, নাহলে Phase 7 অসক্রিয় | Admin `manage` অনুরোধ করে। দ্বিতীয় অনুমোদন লাগে (Finance Controller) | পুরোনো লেনদেন অপরিবর্তিত (সব base)। নতুন লেনদেনে foreign line অনুমত। বন্ধ শুধু foreign line না থাকলে (R7.31) | Lock-after-use: posted foreign line থাকলে বন্ধ যায় না। `accounting.fx.enabled` ও `.disabled`, আগে-পরে মান |
| Currency master (`acc_currencies`): `precision`, `rounding_mode` | Rounding-এর মান। ভুল হলে প্রতি line-এ cent-পার্থক্য | precision 0 থেকে 4, mode `half_up`, `half_even`, `up`, `down` | ISO minor unit, `half_up` | Company, currency-ভিত্তিক | হ্যাঁ, প্রতি enabled currency-তে | `manage` বানায় ও বদলায়। প্রথম ব্যবহারের আগে অনুমোদন লাগে না। পরে বদল নেই | পুরোনো line-এর মান স্থির (amount নিজে রাখা)। নতুনগুলোতে পরিবর্তন নেই কারণ lock | `first_used_at` বসলে lock। `accounting.currency.updated` |
| `fx.default_rate_type` | Journal line-এ rate type না দিলে কোনটা নেওয়া হবে | বিদ্যমান active rate type code | `spot` | Company | হ্যাঁ | `manage` বদলায়, দ্বিতীয় অনুমোদন লাগে না | পুরোনো line-এ rate\_type\_id নিজে রাখা থাকে, অপরিবর্তিত। নতুন Draft-এ নতুন মান | Post-এর পর line-এ স্থায়ী। সেটিংয়ের history, আগে ও পরের মান দেখায় |
| `fx.missing_rate_behavior` | Rate না থাকলে আটকাবে নাকি আগের rate নেবে। ভুল হলে পুরোনো rate-এ লেনদেন | `block` বা `use_previous` | `block` | Company | হ্যাঁ | `manage`। দ্বিতীয় অনুমোদন না (তবে OD-32a) | পুরোনো লেনদেন অপরিবর্তিত। নতুন লেনদেনে নতুন আচরণ। `use_previous`-এ line-এ `rate_date` আগের তারিখ দেখায় | সেটিং history। Post-এ lock নেই, line-এ স্থায়ী তথ্য থাকে |
| `fx.max_rate_age_days` | `use_previous`-এ কত পুরোনো rate চলবে। সমান খালি হলে বানিজ্যিক ছুটির দিনের বাইরে ঠিক নায় | পূর্ণসংখ্যা 0 থেকে 30 | 3 | Company | শুধু `use_previous`-এ | `manage` | পুরোনো লেনদেন অপরিবর্তিত। নতুনে সীমা বদলায় | সেটিং history |
| Rate type `tolerance_pct` ও `fx.rate_tolerance_default_pct` | আগের rate-এর তুলনায় কতটুকু লাফ অস্বাভাবিক। ভুল টাইপিং ধরা পড়ে | DECIMAL(9,4) শতাংশ, 0 থেকে 100 | default 5.0000, `budget` ও `historical` এর জন্য সীমাহীন | Company, type-ভিত্তিক | হ্যাঁ | `manage`। দ্বিতীয় অনুমোদন না (কিন্তু rate approver-এর কাছে দেখায়) | প্রতিটা rate নিজের অনুমোদন অবস্থায় আছে। পুরোনো approved rate-এর ফল বদলায় না | সেটিং history। স্থানীয়ভাবে rate row-এ ওই সময়ের শতাংশ রাখা হয় (`events`) |
| `fx.rate_approval_mode` (manual entry) | Manual rate-এ কতটা নিয়ন্ত্রণ। ঢিলে ভুল rate ঢোকে | `always` বা `outside_tolerance` | `always` | Company | হ্যাঁ | `manage` অনুরোধ, দ্বিতীয় অনুমোদন লাগে | আগের approved rate অপরিবর্তিত। নতুন entry-এ নতুন নিয়ম। Pending rate পরের নিয়মে আর আটকাবে না (নিজের অবস্থায় থাকে) | সেটিং history, প্রতি rate-এ কোন mode-এ অনুমোদিত `events`-এ |
| `fx.import_approval_mode` (CSV, provider) | Bulk rate-এ নিয়ন্ত্রণ ও গতির মাঝে সমতা | `always` বা `outside_tolerance` | `outside_tolerance` | Company | হ্যাঁ | `manage`, দ্বিতীয় অনুমোদন লাগে না (sensitive নয়)। `always` হলে সব approver দেখে | আগের batch অপরিবর্তিত। নতুন batch-এ নতুন নিয়ম | সেটিং history। স্বয়ংক্রিয় অনুমোদনে actor = system, `accounting.fx_rate.approved` |
| `fx.rate_sod_maker_cannot_approve` | যিনি rate ঢোকান, তিনি অনুমোদন করতে পারবেন না | bool | true | Company | হ্যাঁ | `manage`। `false` করতে দ্বিতীয় অনুমোদন লাগে (small company, OD-32) | আগের অনুমোদন নিজের অবস্থায়। নতুন অনুমোদনে নতুন নিয়ম | সেটিং history। স্ব-অনুমোদন হলে rate event-এ চিহ্ন |
| `fx.provider_enabled` ও `fx.provider_schedule` | Provider API থেকে স্বয়ংক্রিয় rate। বন্ধ থাকলে শুধু manual ও CSV | bool, cron অভ্যন্তরীণ যাচাই | false, দৈনিক সকাল | Company | না (OD-31 সিদ্ধান্ত নিলে হ্যাঁ) | `manage` বানায়, দ্বিতীয় অনুমোদন লাগে (কারণ বাইরের উৎস সম্পর্ক) | পুরোনো rate অপরিবর্তিত। চালু হলে নতুন rate draft হিসেবে আসবে | `accounting.fx_rate.fetched`, সেটিং history |
| `fx.import_max_rows` | এক CSV-তে কত row। বড় file system ধীর করে | পূর্ণসংখ্যা 1 থেকে 50000 | 20000 | Company | না | `manage` | কেবল নতুন upload-এ | সেটিং history |
| `fx.rate_gap_alert_days` | কত দিন rate না থাকলে alert। ভুল হলে সময়মতো জানা যায় না | পূর্ণসংখ্যা 1 থেকে 30 | 2 | Company | না | `manage` | শুধু alert, লেনদেনে প্রভাব নেই | সেটিং history |

**Configuration সারণি খ: Journal, revaluation ও event**

| Key | উদ্দেশ্য ও Business Impact | Option ও Validation | Default Policy | Scope | বাধ্যতামূলক কি | কে বানায়, বদলায়, অনুমোদন করে | বদলের প্রভাব | Lock ও Audit |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `fx.journal_rate_override_tolerance_pct` | হাতে দেওয়া rate সরথিত rate থেকে কতটুকু সরতে পারে। বেশি হলে জাল বা ভুল rate ঢোকে | DECIMAL(9,4) শতাংশ | 2.0000 | Company | হ্যাঁ | `manage`। দ্বিতীয় অনুমোদন না | পুরোনো Draft অপরিবর্তিত থাকে, Submit-এ নতুন সীমায় যাচাই। Posted স্পর্শ হয় না | সেটিং history। Override হলে `accounting.fx.rate_override_used` |
| `fx.line_conversion_tolerance_units` | txn × rate আর line-এর base amount-এর পার্থক্যের সীমা (base-এর minor unit-এ) | পূর্ণসংখ্যা 0 থেকে 10 | 0 | Company | হ্যাঁ | `manage` | নতুন Submit/Post-এ প্রযোজ্য। Posted অপরিবর্তিত | সেটিং history |
| `fx.rounding_max_units` | Rounding line-এর উর্ধ্বসীমা। বেশি হলে আসল ভুল লুকিয়ে যায় | পূর্ণসংখ্যা 1 থেকে 100 | 5 | Company | হ্যাঁ | `manage`। দ্বিতীয় অনুমোদন না | পুরোনো Draft-এ rounding line পুনর্নির্মাণে নতুন সীমা। Posted অপরিবর্তিত | সেটিং history। প্রতি rounding line journal-এ দেখা যায় |
| FX account mapping (`acc_fx_accounts`): `realized_gain`, `realized_loss`, `unrealized_gain`, `unrealized_loss`, `rounding` | কোন account-এ gain, loss, rounding যাবে। ভুল হলে P&L ভুল line-এ | সক্রিয়, group নয় এমন account। Gain/loss revenue বা expense type, rounding-ও P&L | বাধ্যতামূলক, Admin দেবে | Company, প্রয়োজনে currency-ভিত্তিক override | হ্যাঁ, চালুর আগে | `manage` বানায়, দ্বিতীয় অনুমোদন লাগে (`mapping:` prefix-এর পথ, `sod_mapping_editor_cannot_approve` প্রযোজ্য) | পুরোনো journal-এ সেই সময়ের account অপরিবর্তিত। নতুন run/লেনদেনে নতুন account (`effective_from`) | Posted journal পেলে পুরোনো row `is_active=0` করা যায়, মুছা যায় না। `accounting.fx_account.mapped` |
| `fx.mixed_currency_journal_types` | কোন journal type-এ এক journal-এ দুই foreign currency আর base override চলবে। বেশি খুললে per-currency balance নিয়ম দুর্বল | Journal type code-এর তালিকা (`acc_journal_types`-এ আছে এমন) | `fx_revaluation`, `bank`, `event_generated`, `adjustment` | Company | হ্যাঁ | `manage` অনুরোধ, দ্বিতীয় অনুমোদন লাগে (OD-36) | পুরোনো journal অপরিবর্তিত। নতুন Submit-এ নতুন তালিকায় যাচাই | Sensitive key। সেটিং history। সদ্য Draft আছে এমন type বাদ দিলে সতর্কতা |
| `fx.revaluation_rate_type` | Period-end revaluation-এর rate। ভুল type নিলে balance sheet-এর মান ভুল | Active rate type | `closing` (OD-35a) | Company | হ্যাঁ | `manage` অনুরোধ, দ্বিতীয় অনুমোদন লাগে | আগের final run অপরিবর্তিত (run-এ rate\_type স্ন্পশোট)। পরের run-এ নতুন type | Sensitive key। সেটিং history। Final run থাকলে সেই period-এর জন্য বদলাবে না |
| `fx.revaluation_granularity` | Control account-এ account ধরে নাকি open item ধরে revalue। Open item বাঁচানোর উপর নির্ভর করে | `account` বা `open_item` (Phase 9 চালু থাকলে) | `account` | Company | হ্যাঁ | `manage`। দ্বিতীয় অনুমোদন না (OD-33) | আগের run অপরিবর্তিত। পরের run-এ নতুন। অর্ধেক period-এ বদলালে আবার মিলাতে হয়, তাই Phase 9 cutover-এ বদলানো ভালো | সেটিং history। Run-এ snapshot |
| `fx.revaluation_offset_aggregation` | Unrealized gain-loss account-এ currency ধরে নাকি এক line। Detail বনাম সহজ পড়া | `per_currency` বা `single` | `per_currency` | Company | হ্যাঁ | `manage` | আগের journal অপরিবর্তিত | সেটিং history |
| `fx.revaluation_auto_reverse` | Revaluation journal পরের period-এ অটো reverse হবে কিনা। বন্ধ করলে realized হিসাবের ভিত্তি বদলায় (OD-35) | bool | true | Company | হ্যাঁ | `manage` অনুরোধ, দ্বিতীয় অনুমোদন লাগে | আগের journal যেমন আছে (তাদের `auto_reverse_on` বদলাবে না)। নতুন run-এ নতুন মান | Sensitive key। সেটিং history। কোনো final run থাকলে বদলে warning |
| `fx.revaluation_required_for_close` | Revaluation ছাড়া period close আটকাবে কিনা (Phase 12 checklist পড়ে) | bool | true | Company | না | `manage` | আগে বন্ধ period অপরিবর্তিত | সেটিং history |
| `fx.preview_retention_days` | Preview run কতদিন থাকবে | 1 থেকে 365 | 30 | Company | না | `manage` | শুধু preview | `FxPreviewPurgeJob` log |
| `event_currency_policy` (Phase 4, বাড়ানো) | Foreign currency event কী হবে: `hold`, `ignore`, নতুন `convert` | `hold`, `ignore`, `convert` (`convert` শুধু flag চালু থাকলে) | `hold` | Company | হ্যাঁ (Phase 4-এর মতো) | `manage`, দ্বিতীয় অনুমোদন লাগে (OD-33a) | আগে `held_currency` হয়ে থাকা event নিজে বদলায় না; অ্যাডমিন "re-evaluate" চালালে প্রস্তাব বানায়। নতুন event নতুন নিয়মে | Sensitive key। সেটিং history। প্রতি event-এ সিদ্ধান্ত `acc_fx_event_conversions`-এ |
| `fx.event_conversion_rate_type`, `fx.event_conversion_approval` | Event রূপান্তরে কোন rate type, আর অনুমোদন কতটা | type code, `required` বা `within_tolerance` | `spot`, `required` | Company | হ্যাঁ | `manage` অনুরোধ, দ্বিতীয় অনুমোদন লাগে | আগের রূপান্তর অপরিবর্তিত। পরের event-এ নতুন মান | সেটিং history। `accounting.fx_event_conversion.*` |

সব config-এর নিয়ম এক: সেটিং বদলালে পুরোনো posted line বদলায় না, কারণ line নিজের `exchange_rate`, `rate_type_id`, `rate_date` রাখে। ওই তিন column P1-10 trigger-এর অপরিবর্তনীয় তালিকায়।

১৭.৬ থেকে ১৭.১৪ (Operational Workflow, Dependencies, Tasks, Testing, Security, Deliverables, Acceptance, Gate, Open Decisions) সীমার কারণে আলাদা tab-এ: Phase 7 (১৭.৬-১৭.১৪): Multi-Currency ও FX

## ১৮. Phase 8: Tax Management

### ১৮.১ Objective ও Scope

**উদ্দেশ্য:** ERPFlow Accounting-এ tax-এর একটাই কেন্দ্র তৈরি করা। Tax code, rate, group, determination rule আর account map সব configuration। কোনো jurisdiction, tax-এর নাম বা rate কোডে বাঁধা নয়। Journal line-এর সাথে tax লেখা হয় `acc_journal_line_taxes`-এ। Posted data থেকে tax ledger ও return তৈরি হয়। Settlement journal সবসময় Draft হয়ে Phase 2-এর অনুমোদন ও `PostingService` পথেই যায়। Bangladesh-এর VAT, TDS ও VDS এখানে শুধু configuration-এর উদাহরণ, বাধ্যতামূলক seed নয়। কোন jurisdiction-এ আগে চালু হবে তা OD-37। বৈশ্বিক OD-112 (statutory report কোন jurisdiction-এর) এই Phase উত্তর দেয় না।

**Scope-এর ভেতরে:**

- Jurisdiction, company-র নিজের tax registration, return-এর box কাঠামো
- Tax code ও সাত ধরন: input, output, withholding, reverse charge, zero-rated, exempt, out of scope
- তারিখ-ভিত্তিক rate, rate বদলের নিয়ম, tax group (compound ও একাধিক component)
- Recoverable, partially recoverable ও non-recoverable input tax
- Tax-inclusive ও tax-exclusive amount, rounding mode (line বা document), rounding method
- Tax determination rule: party tax status, item বা service category, jurisdiction, transaction type
- Tax account map: payable, receivable, non-recoverable expense, withholding, settlement
- Manual journal-এ tax line স্বয়ংক্রিয় তৈরি, permission ও audit সহ manual override
- Event-generated journal-এর জন্য tax hook (Phase 14-এর posting rule এটা ডাকবে)
- Withholding tax, certificate (issued ও received), deposit tracking
- Tax ledger (code ও period ধরে), tax return period, return prepare, review, approve, lock, amendment
- Settlement journal (output বিয়োগ input, payable বা refundable), deposit tracking
- Tax adjustment ও correction শুধু Reversal বা Adjustment দিয়ে
- Tax report: summary, detail, by code, by party, return register, withholding register, deposit tracker, reconciliation RC13
- Export hook (ফাইল তৈরি), party tax registration validation hook (Phase 9-এর জন্য), multi-jurisdiction per company
- `tax.enabled` capability flag, Admin Settings, permission, audit, UAT

**Scope-এর বাইরে ও কেন:**

- E-filing ও statutory submission। কারণ দায় Accounting-এর নয়, OD-118 (baseline: Billing)। এখানে শুধু export hook থাকে
- Invoice-এর মূল tax হিসাব। কারণ Billing ও Procurement-এর মালিকানা। Accounting শুধু `TaxService::calculate` দেয় ও journal-এ tax রাখে
- Party master ও party-র registration সংরক্ষণ। কারণ Phase 9 (G8)। এখানে validation hook-এর interface থাকে
- Income tax (corporate), deferred tax, transfer pricing, customs duty। কারণ আলাদা গণনা, আলাদা Phase লাগবে
- Statutory return-এর আনুষ্ঠানিক form-এর চেহারা। কারণ jurisdiction-নির্ভর (OD-112)। Box কাঠামো configurable
- Event থেকে tax-সহ Draft বানানোর rule। কারণ Phase 14-এর কাজ। এখানে শুধু hook

**Baseline অনুমান:** G1 (company = legal entity, একটা company-তে একাধিক jurisdiction চলতে পারে), G3 (tax amount base currency-তে সংরক্ষিত), G9 (system-তৈরি journal Draft)। বদলালে কী ভাঙে তা প্রতিটা OD-তে লেখা।

### ১৮.২ Functional Requirements

**ক. Jurisdiction ও registration:** একটা company-তে একাধিক jurisdiction। প্রতিটার কর-কর্তৃপক্ষ, return-এর frequency, due দিন, নিজের registration নম্বর (তারিখসহ), rounding override। Registration-এর তারিখের বাইরে tax code ব্যবহার হলে সতর্কতা বা বাধা (`tax.registration_check`)।

**খ. Tax code ও ধরন:** সাত ধরন। `input`, `output`, `withholding`, `reverse_charge`, `zero_rated`, `exempt`, `out_of_scope`। প্রতিটা code একটা jurisdiction-এর। Calc method `percent` বা `fixed_amount`। Default price basis `exclusive` বা `inclusive`। Recoverable mode `full`, `none`, `partial` (শতাংশসহ)। Return-এর box code (tax ও base আলাদা)।

**গ. তারিখ-ভিত্তিক rate:** একটা code-এর অনেক rate সারি, প্রতিটার `effective_from` ও `effective_to`। Overlap নেই। Rate বদল মানে নতুন সারি, পুরোনো সারি বন্ধ হয়। ব্যবহৃত সারির মান কখনো বদলায় না। Rate বদলের অনুমোদন লাগে (maker-checker)।

**ঘ. Tax group:** এক লেনদেনে একাধিক tax component (যেমন দুই স্তরের কর)। Component-এর ক্রম আছে। প্রতিটা component `base` অথবা `base_plus_previous` (compound) ভিত্তিতে চলে। Group-এর সব component এক jurisdiction-এর।

**ঙ. Recoverable ও non-recoverable:** Input tax পুরো, আংশিক বা একদম ফেরতযোগ্য নয়। আংশিক হলে এক line থেকে দুটো tax সারি (recoverable ও non-recoverable)। Non-recoverable অংশ expense-এ যায় (আলাদা line বা base line-এ মিশিয়ে, `tax.nonrecoverable_posting`)।

**চ. Inclusive ও exclusive:** Inclusive হলে gross থেকে base ও tax বের করা হয়, যাতে base + tax ঠিক gross হয়। Exclusive হলে base-এর উপর tax যোগ।

**ছ. Rounding:** `line` বা `document` mode, method `half_up` বা `half_even`, precision সেটিং থেকে। Document mode-এ ফারাক নির্দিষ্ট নিয়মে একটা line পায়। Company ও jurisdiction স্তরে configurable।

**জ. Determination rule:** party-র tax status (registered, unregistered, exempt entity, foreign), item বা service category, jurisdiction, transaction type, account, পরিমাণের সীমা ধরে কোন code বা group লাগবে তা rule ঠিক করে। Priority ও specificity আছে। Rule-ও তারিখ-ভিত্তিক।

**ঝ. Account map:** প্রতিটা code ও role-এর জন্য account (payable, receivable, non-recoverable expense, withholding payable, withholding receivable)। Jurisdiction স্তরে settlement account (net payable, refundable, rounding)। Map-ও তারিখ-ভিত্তিক।

**ঞ. Manual journal-এ tax:** ব্যবহারকারী base line ও tax code বা group বাছলে tax line স্বয়ংক্রিয়ভাবে Draft-এ তৈরি হয়। হাতে বদলাতে `accounting.tax.override` ও কারণ লাগে। Override audit log-এ যায়, tolerance পেরোলে বাড়তি অনুমোদন।

**ট. Event-generated journal-এ tax:** `TaxService::calculate` ও `TaxLineBuilder` Phase 14-এর posting rule-এর জন্য খোলা থাকে। Phase 14 আসার আগে কোনো event থেকে tax-সহ journal হয় না।

**ঠ. Withholding tax:** দুই দিক। আমরা কাটি (liability, কর্তৃপক্ষে deposit, certificate issue) আর গ্রাহক আমাদের থেকে কাটে (asset, certificate গ্রহণ ও মেলানো)। Deposit-এর ট্র্যাকিং ও due তারিখ।

**ড. Tax ledger:** posted data থেকে code ও period ধরে tax ledger। এটাই return ও report-এর একমাত্র উৎস।

**ঢ. Return:** period তৈরি, posted data থেকে prepare, review, approve, lock, amendment। Lock হলে return আর বদলায় না। Lock-এর পরের late entry পরের return-এ আলাদা চিহ্নে যায়।

**ণ. Settlement:** locked return থেকে (output বিয়োগ input বিয়োগ suffered withholding) payable বা refundable। Settlement journal `tax_settlement` ধরনের Draft। Deposit আলাদা payment journal, লিংক করা।

**ত. Adjustment ও correction:** posted tax কখনো সম্পাদনা হয় না। ভুল Reversal (tax সারি mirror) বা Adjustment (নিজের tax সারি) দিয়ে ঠিক হয়।

**থ. Report ও export:** Tax Summary, Tax Detail, By Code, By Party, Return Register, Withholding Register, Deposit Tracker, Reconciliation (RC13)। Export hook দিয়ে jurisdiction-ভিত্তিক ফাইল। Submission নেই।

**দ. Period lock-এর সাথে মিল:** Accounting period lock `PeriodGuard` চালায়, অপরিবর্তিত। Return lock তার উপরের আলাদা স্তর। দুটো পরস্পরের সাথে মেলানো (R8.31, R8.39)।

### ১৮.৩ Technical Requirements

**Service ও hook:**

| Service | কাজ | দেয় যাকে |
| --- | --- | --- |
| `TaxService::calculate(company, TaxInput): TaxResult` | Pure হিসাব। কিছু লেখে না। Rate resolve, determination, inclusive ও exclusive, rounding, recoverable ভাগ | Manual journal, Phase 14, Phase 9 |
| `TaxLineBuilder` | `TaxResult` থেকে journal line ও `acc_journal_line_taxes` সারি তৈরি, `forEvent`, `mirror` (reversal-এর জন্য) | `JournalService`, Phase 14 |
| `TaxLineValidator` | `SaveValidator`, `SubmitValidator` ও post-এর আগের পরীক্ষায় tax সারি আবার হিসাব করে মেলায় | P2-03, P2-12 |
| `TaxRateResolver`, `TaxDeterminationEngine`, `TaxRounding` | তারিখ থেকে rate, rule থেকে code, rounding | `TaxService` |
| `TaxLedgerQuery`, `TaxLedgerProjector` | Tax ledger পড়া, cache তৈরি | Return, report, RC13 |
| `TaxReturnService` | prepare, review, approve, lock, amend | Return screen |
| `TaxSettlementService`, `TaxDepositService` | Settlement Draft, deposit লিংক | Return screen |
| `WithholdingService` | Certificate তৈরি, বাতিল, মেলানো | Withholding screen |
| `TaxRegistrationValidator` | Hook। Phase 9 party data দেয় | Determination, journal |
| `TaxReconciler` | RC13 | Reconciliation framework (P3-13) |
| `TaxExportHook` | Jurisdiction ও format ধরে exporter registry | `acc_report_exports` |

**Job ও queue (queue `accounting-tax`, সব `AccountingJob`):** `TaxLedgerProjectJob` (journal post হওয়ার commit-এর পরে, at-least-once, idempotent), `TaxReturnPrepareJob`, `TaxRc13Job` (রাতে), `TaxDueAlertJob` (deposit ও filing due, `acc_alert_state`), `TaxExportJob`। প্রতিটা job company context সহ চলে।

**Performance লক্ষ্য (test-এ মাপা):** 50 line-এর journal-এ `calculate` p95 20 ms-এর নিচে (config প্রতি request-এ cache, key `rate_epoch` সহ)। Return prepare 10 লাখ tax সারিতে 10 মিনিটের নিচে (keyset pagination, 5,000 সারির ব্যাচ)। এক quarter-এর report 5 লাখ সারিতে 5 সেকেন্ডের নিচে। Index পরিকল্পনা P8-07, P8-10-এ।

**Concurrency:** rate বদলে সংশ্লিষ্ট `acc_tax_codes` সারি `FOR UPDATE` (শুধু নিজের table)। এক jurisdiction-এর period-এ একটাই active return (unique)। Return lock-এ return সারি `FOR UPDATE`। এক return-এ একটাই active settlement (`source_active` unique)। Lock ক্রম অপরিবর্তিত: journal, approval request, account, period, voucher counter। Phase 8-এর table-lock সবসময় নিজের table-এ। `PostingService` transaction কখনো tax table-এ lock নেয় না, শুধু পড়ে।

**Failure handling:** `calculate` ব্যর্থ হলে Draft save পুরো আটকায়, আংশিক tax line থাকে না। Projector ব্যর্থ হলে backoff সহ পাঁচবার, তারপর dead letter ও alert, আর RC13 অমিল ধরে। Settlement Draft তৈরি ব্যর্থ হলে return `locked`ই থাকে, আবার চেষ্টা নিরাপদ।

**Security:** `BelongsToCompany`, `CompanyScope`, composite FK। প্রতি endpoint-এ permission। Regex (`tax_id_pattern`) দৈর্ঘ্য সীমা ও সময়সীমাসহ চলে (ReDoS প্রতিরোধ)। Tax id log-এ মাস্ক। Export ফাইল signed URL-এ।

### ১৮.৪ Data Model ও API

পূর্ণ column spec Pack-এর ১৩ নম্বর section-এ। এখানে শুধু তালিকা ও মূল column। সব table-এ `company_id`, composite FK `(company_id, x_id)`, `uq (company_id, id)`। টাকা `DECIMAL(20,4)`, rate `DECIMAL(20,10)`। Migration expand-only।

| Table | মূল column | Card |
| --- | --- | --- |
| `acc_tax_jurisdictions` | code, name, filing\_frequency, filing\_due\_days, tax\_id\_pattern, rounding\_mode | P8-01 |
| `acc_tax_registrations` | jurisdiction\_id, registration\_no, registered\_from, registered\_to, status | P8-01 |
| `acc_tax_return_boxes` | jurisdiction\_id, box\_code, line\_kind, amount\_basis, sign | P8-01 |
| `acc_tax_codes` | jurisdiction\_id, code, tax\_type, calc\_method, price\_basis, recoverable\_mode, recoverable\_pct, withholding\_side, rate\_epoch | P8-02 |
| `acc_tax_rates` | tax\_code\_id, rate, fixed\_amount, effective\_from, effective\_to, status | P8-02 |
| `acc_tax_config_history` (append-only) | entity, entity\_id, action, old\_value, new\_value, changed\_by | P8-02 |
| `acc_tax_groups`, `acc_tax_group_members` | group\_id, tax\_code\_id, seq\_no, apply\_on | P8-03 |
| `acc_tax_account_map` | jurisdiction\_id, tax\_code\_id, role, account\_id, effective\_from, effective\_to | P8-04 |
| `acc_tax_determination_rules` | priority, txn\_type, party\_tax\_status, item\_category, service\_category, result tax\_code বা tax\_group, effective\_from | P8-06 |
| `acc_journal_line_taxes` (shared, §6) | journal\_line\_id, tax\_code\_id, tax\_rate\_id, base\_amount, tax\_amount, direction, is\_recoverable + Phase 8-এর নিজস্ব nullable column | P8-07 |
| `acc_tax_ledger_cache` (derived) | tax\_code\_id, period\_id, direction, base\_total, tax\_total, row\_count | P8-10 |
| `acc_tax_returns`, `acc_tax_return_lines`, `acc_tax_return_sources` | jurisdiction\_id, period\_from, period\_to, status, snapshot\_hash, box\_code, journal\_line\_tax\_id | P8-11 |
| `acc_tax_settlements` | return\_id, settlement\_type, amount, journal\_id, status | P8-13 |
| `acc_tax_deposits` | settlement\_id বা certificate batch, amount, deposit\_date, journal\_id | P8-13, P8-14 |
| `acc_withholding_certificates`, `acc_withholding_certificate_items` | direction, certificate\_no, tax\_code\_id, party\_ref, withheld\_amount, status | P8-14 |

**Shared column (Master Plan §6):** `acc_journal_line_taxes`-এর `company_id, journal_line_id, tax_code_id, tax_rate_id, base_amount, tax_amount, direction (input, output, withholding), is_recoverable` হুবহু এই নামে। `acc_accounts.tax_default_code_id` (nullable) Phase 8 চালু করে, অর্থাৎ account-এ default tax code বসানো যায় (শুধু প্রস্তাব, জোর করে নয়)। `acc_journals.journal_type = 'tax_settlement'` বিদ্যমান seed (Phase 6, `is_active = 0`)। Phase 8 চালুর সময় সেটা সক্রিয় হয়। Journal-এর কোনো নতুন column Phase 8 যোগ করে না। Posted journal ও line কোথাও বদলায় না।

**API (সব `/api/accounting/...`, write-এ `Idempotency-Key`):**

| Endpoint | কাজ | Permission |
| --- | --- | --- |
| `GET/POST /tax/jurisdictions`, `GET/PUT /tax/jurisdictions/{id}`, `/registrations`, `/boxes` | Jurisdiction, registration, box | view, manage |
| `GET/POST /tax/codes`, `/tax/codes/{id}/rates` | Code ও rate প্রস্তাব | view, manage, approve (rate সক্রিয়) |
| `GET/POST /tax/groups` | Group | view, manage |
| `GET/PUT /tax/account-map` | Account map (version সহ) | view, manage, approve |
| `GET/POST /tax/determination-rules` | Rule | view, manage, approve |
| `POST /tax/calculate` | Preview, কিছু লেখে না | view |
| `POST /journals/{id}/tax-lines/generate` | Draft journal-এ tax line তৈরি | `accounting.journal.create` ও view |
| `PUT /journals/{id}/lines/{lineId}/tax` | Override | `accounting.tax.override` |
| `GET /tax/ledger` | Code ও period ধরে ledger | view |
| `POST /tax/returns`, `.../{id}/prepare`, `.../submit-review`, `.../approve`, `.../lock`, `.../amend` | Return জীবনচক্র | `accounting.tax.return`, approve, lock |
| `POST /tax/returns/{id}/settlement` | Settlement Draft | `accounting.tax.settle` |
| `GET/POST /tax/deposits` | Deposit লিংক | settle বা withholding |
| `GET/POST /tax/withholding/certificates`, `.../issue`, `.../cancel`, `.../match` | Certificate | `accounting.tax.withholding` |
| `GET /tax/reports/{key}`, `POST /tax/reports/{key}/export` | Report ও export | view, `accounting.report.export` |
| `POST /tax/reconciliation/run`, `GET /tax/reconciliation/{id}` | RC13 | `accounting.recon.run` |
| `GET /tax/enablement-status` | `tax.enabled` precondition | `accounting.enable.manage` |

**১৮.৫ থেকে ১৮.১৪ (Business Rules ও Configuration থেকে Open Decisions):** Blueprint tab-এর block সীমা (২০,০০০) পূর্ণ হওয়ায় এই অংশ আলাদা উপ-tab-এ: Phase 8: Tax Management (১৮.৫ থেকে ১৮.১৪)

## ১৯. Phase 9: Subledgers (AR ও AP) ও Party

### Objective ও Scope

Phase 9 GL-এর control account-কে party ধরে ভাগ করে। প্রতিটা receivable ও payable line একটা open item হয়, payment সেই item-এর সাথে settle হয়, আর aging, party ledger, statement ও RC10 reconciliation চলে। Capability flag: `subledger.enabled`, default বন্ধ। বন্ধ থাকলে আচরণ Phase 8 শেষের মতোই।

| দিক | বিবরণ |
| --- | --- |
| ভেতরে | Party mirror (customer, vendor, employee, other) ও status; party merge; party tax registration data (Phase 8 ব্যবহার করে); control account নিয়ম; payment terms; open item; settlement (full, partial, many-to-many, cross-document, credit note application, advance ও unapplied cash, refund); realized FX (Phase 7-এর মাধ্যমে); ছোট difference write-off; unmatch; aging (configurable bucket); party ledger ও statement; RC10; credit limit hook; party balance cache; Phase 14, Phase 7 ও Phase 16 hook |
| বাইরে | Party master সম্পাদনা; invoice, bill, payment তৈরি (Billing ও Procurement); bank matching (Phase 10); tax হিসাব (Phase 8); revaluation চালানো (Phase 7); dunning letter ও statement email পাঠানো (OD-45a); bad debt provision (OD-51a); AR-AP contra settlement (OD-46a) |
| বাইরের কেন | Master সম্পাদনার দায় ERPFlow-এর module-এর (G8)। চিঠি পাঠানো ও provision-এ business নীতি লাগে, তাই OD। Bank matching-এর নিজস্ব rule আছে, Phase 10 শুধু `OpenItemService::settle` ডাকবে |

ভিত্তি ধারণা: G8 অনুযায়ী party শুধু পড়ার mirror, চাবি `(source_type, source_id)`। Journal তৈরি শুধু `JournalService` দিয়ে Draft-এ। Status বদল শুধু `PostingService`। Phase 9 এই দুই নিয়ম ভাঙে না। Settlement-এর journal-ও Draft হয়ে approval পথে যায়।

### Functional Requirements

**ক. Party mirror ও lifecycle।** ERPFlow-এর customer, vendor, employee ও অন্য master থেকে `acc_parties` গড়ে ওঠে। Sync idempotent। Status: `active`, `blocked`, `merged`, `archived`। Accounting-এ party নাম বা tax id সম্পাদনা যায় না (`PTY_SOURCE_READONLY`)। Block ও merge শুধু Accounting-এর নিজস্ব নিয়ন্ত্রণ-তথ্য।

**খ. Control account নিয়ম।** `acc_accounts.requires_party = 1` ও `party_type` থাকলে ওই account-এর প্রতিটা line-এ `party_id` লাগে, type মিলতে হবে। Submit-এ না মিললে reject (`PTY_PARTY_REQUIRED`, `PTY_PARTY_TYPE_MISMATCH`)। Draft-এ শুধু সতর্কতা।

**গ. Open item তৈরি।** Journal post হলে একই transaction-এ control account-এর প্রতিটা line-এর জন্য ঠিক একটা open item: side (debit বা credit), মূল ও খোলা amount (transaction ও base currency), due date, status `open`, `partial`, `settled`।

**ঘ. Payment terms ও due date।** Terms (net দিন, মাসের শেষ, discount দিন) company-তে configurable। Due date line-এ থাকলে সেটাই, নইলে document date ও party-র terms থেকে, নইলে default term।

**ঙ. Settlement।** Debit item আর credit item মেলানো: পূর্ণ, আংশিক, বহু-থেকে-বহু, ভিন্ন document, credit note প্রয়োগ, advance বা unapplied cash প্রয়োগ, refund। একই party (merge ধরে), একই type, এক settlement currency।

**চ. Realized FX ও write-off।** বিদেশি currency item settle হলে `FxRealizedService::compute` দিয়ে gain-loss, Draft journal হিসেবে। ছোট বাকি difference limit-এর ভেতরে write-off, কারণসহ, সাধারণ approval পথে।

**ছ. Unmatch।** Settlement মুছে না। নতুন unmatch settlement আর দরকারে controlled reversal journal। আগের অবস্থা ফিরে আসে।

**জ. Aging।** AR ও AP aging, as-of তারিখ, currency, dimension ধরে, configurable bucket (`acc_aging_profiles`)। Party-ভিত্তিক drilldown।

**ঝ. Party ledger ও statement।** Party-র সব line ও open balance, running balance, মেয়াদ নির্বাচন, export (`acc_report_exports`)।

**ঞ. Credit limit।** Customer-এর exposure limit ছাড়ালে Submit-এ warn বা block (OD-45), override কারণসহ।

**ট. RC10 reconciliation।** প্রতি party type-এ open item-এর যোগফল = control account balance। Party ধরে পার্থক্যের তালিকা।

**ঠ. Integration।** Phase 14 event-এর invoice, credit note, bill, payment-এ party, due date, document ref line-এ বসে। Phase 7 revaluation open item পড়ে। Phase 16 privacy hook। Phase 10 bank match `OpenItemService::settle` ডাকে।

**ড. Dunning ও statement পাঠানো।** Statement-এর data ও report এই phase-এ। চিঠি বা email পাঠানো OD-45a-র সিদ্ধান্ত পর্যন্ত বাইরে।

### Technical Requirements

| বিষয় | প্রয়োজন |
| --- | --- |
| Service | `PartyService` (sync, resolve, status, merge, defaultAccount), `OpenItemService` (`create`, `settle`, `unmatch`), `AgingService`, `SubledgerReconciler` (RC10)। Journal তৈরি শুধু `JournalService`, post শুধু `PostingService`। Phase 9 `acc_journals.status` লেখে না |
| Post hook | `PostingService` (P2-12) transaction-এর ভেতরে, voucher counter lock-এর পরে `OpenItemService::create(journal)` ডাকে। ব্যর্থ হলে পুরো Post rollback (`OPI_CREATE_FAILED`)। Settlement journal পোস্ট হলে একই hook settlement apply করে |
| Job | সব `AccountingJob` base (P1-12), company context সহ: `PartySyncJob`, `PartyBalanceRebuildJob`, `Rc10Job` (রাতে ও period close-এর আগে), `StaleSettlementSweepJob` (বাতিল journal-এর settlement ছাড়ানো), `OpeningItemLoadJob`। Queue: `accounting-subledger`, প্রতি company-তে rebuild একসাথে একটাই |
| Lock ক্রম | বিদ্যমান ক্রম অপরিবর্তিত (journal, approval request, account, period, voucher counter)। Phase 9 নিজের table-এ শেষে: `acc_parties` (FOR SHARE), `acc_open_items` (id ক্রমে FOR UPDATE), `acc_settlements`, `acc_party_balances`। Settlement API কখনো আগে থেকে থাকা journal বা account-এ X lock নেয় না, তাই Post path-এর সাথে চক্র হয় না |
| Concurrency | একই item-এ দুই settlement: দ্বিতীয়টি lock-এ অপেক্ষা করে, তারপর `open_txn - reserved_txn` দেখে `OPI_AMOUNT_EXCEEDS_OPEN` পায়। Idempotency key ও `uq (company_id, idempotency_key)` দ্বিতীয় নিরাপত্তা |
| Performance | Aging ও statement index-নির্ভর (`company_id, party_id, status, due_date`)। Party balance cache `acc_party_balances` (rebuild-যোগ্য, সত্যের উৎস নয়)। 50 হাজারের বেশি row-এর report async export। Past as-of aging `acc_open_item_events` থেকে। লক্ষ্য: 1 লাখ open item-এ aging 3 সেকেন্ডের নিচে, settlement (50 line) 1 সেকেন্ডের নিচে |
| Failure handling | Sync ব্যর্থ হলে cursor এগোয় না, retry। Settlement-এর draft journal reject বা cancel হলে sweep job settlement বাতিল করে reservation ছাড়ে। Hook-এ ভুল পেলে Post আটকায়, ভাঙা item বানায় না |
| Security | Permission slug `accounting.party.*` ও `accounting.subledger.*`। Write-off ও settle আলাদা slug। Party PII report-এ mask করার সুযোগ। সব write API-তে `Idempotency-Key` |
| Migration | Expand-only। `acc_journal_lines`-এ তিনটা nullable column, `acc_accounts`-এ পরিচিত দুই column-এর constraint। বড় table-এ index online build। Flag বন্ধ থাকলে নতুন table কেউ পড়ে না |

### Data Model ও API

**নতুন table (পুরো column spec Pack-এ):**

| Table | মূল column | Card |
| --- | --- | --- |
| `acc_parties` | `company_id, source_type, source_id, party_type, code, display_name, legal_name, status, merged_into_party_id, default_currency, payment_term_id, source_hash, pii_redacted_at, version` | P9-01 |
| `acc_party_status_history` | append-only: `party_id, from_status, to_status, merged_into_party_id, reason, actor_id` | P9-01 |
| `acc_party_tax_registrations` | `party_id, jurisdiction_code, registration_type, registration_no, valid_from, valid_to, is_primary` | P9-01 |
| `acc_party_sync_runs` | `run_id, source_type, cursor, counts, status` | P9-01 |
| `acc_payment_terms` | `code, name, kind, net_days, day_of_month_cutoff, discount_days, discount_percent, is_active, version` | P9-04 |
| `acc_party_accounts` | `party_type, purpose, currency, party_id, account_id` | P9-04 |
| `acc_party_credit_limits` | `party_id, currency, limit_amount, mode, effective_from, effective_to, status` | P9-04 |
| `acc_open_items` | `party_id, account_id, journal_id, journal_line_id, side, item_kind, document_ref, due_date, currency, original_txn, open_txn, reserved_txn, original_base, open_base, reval_adjust_base, status, item_ref, origin, version` | P9-05 |
| `acc_open_item_events` | append-only: `open_item_id, event_type, effective_date, delta_txn, delta_base, settlement_id` | P9-05 |
| `acc_settlements` | `settlement_no, settlement_date, settlement_type, party_id, currency, status, journal_id, reverses_settlement_id, idempotency_key, source_type, source_id` | P9-06 |
| `acc_settlement_lines` | `settlement_id, open_item_id, line_kind, applied_txn, applied_settle_ccy, applied_base_item, applied_base_settle, fx_gain_loss_base` | P9-06 |
| `acc_write_off_rules` | `party_type, currency, max_per_item, max_per_settlement, expense_account_id, requires_reason, is_active` | P9-06 |
| `acc_aging_profiles` | `code, name, basis, as_of_default, version, is_default` | P9-09 |
| `acc_aging_profile_buckets` | `profile_id, bucket_no, label, from_day, to_day` | P9-09 |
| `acc_party_balances` | cache: `party_id, account_id, currency, balance_txn, balance_base, overdue_base, balance_version` | P9-09 |

**বিদ্যমান table-এ expand:** `acc_journal_lines` (`party_id`, `due_date`, `document_ref`), `acc_accounts` (`requires_party`, `party_type` সক্রিয়, constraint)। P1-10 trigger-এর অপরিবর্তনীয় column তালিকায় তিনটা যোগ হয় (P9-02)।

**Endpoint (prefix `/api/accounting`):**

| Method ও path | কাজ | Permission |
| --- | --- | --- |
| GET `/parties`, `/parties/{id}` | তালিকা ও বিবরণ | `accounting.party.view` |
| POST `/parties/sync` | sync চালানো | `accounting.party.sync` |
| POST `/parties/{id}/block`, `/unblock`, `/archive` | status বদল | `accounting.party.block` |
| POST `/parties/{id}/merge`, `/unmerge` | merge ও তোলা | `accounting.party.merge` (দ্বিতীয় অনুমোদন) |
| GET, PUT `/party-accounts`, `/payment-terms`, `/parties/{id}/credit-limit` | configuration | `accounting.party.manage` |
| GET `/open-items`, `/open-items/{id}` | item খোঁজা | `accounting.subledger.view` |
| POST `/open-items/{id}/due-date` | due date বদল (কারণসহ) | `accounting.subledger.manage` |
| POST `/settlements/suggest` | মেলানোর প্রস্তাব (কিছু লেখে না) | `accounting.subledger.view` |
| POST `/settlements` | settlement তৈরি | `accounting.subledger.settle` |
| POST `/settlements/{id}/unmatch` | unmatch | `accounting.subledger.unmatch` |
| GET, PUT `/write-off-rules`, `/aging-profiles` | configuration | `accounting.subledger.manage` |
| GET `/reports/aging`, `/reports/party-ledger`, `/reports/party-statement` | report | `accounting.subledger.view` |
| POST `/reconciliation/rc10/run`, GET `/reconciliation/rc10/{run}` | RC10 | `accounting.subledger.reconcile` |
| GET `/subledger/enable-check` | flag চালুর precondition | `accounting.subledger.manage` |

### Business Rules ও Configuration

**Business Rules (R9.n):**

| Rule | নিয়ম | ভাঙলে error |
| --- | --- | --- |
| R9.1 | Party mirror শুধু sync দিয়ে বদলায়। নাম, tax id, ঠিকানা Accounting-এ সম্পাদনা নয়। চাবি `(company_id, source_type, source_id)` | `PTY_SOURCE_READONLY` |
| R9.2 | `party_type` হয় `customer`, `vendor`, `employee`, `other`। Type source\_type থেকে নির্ধারিত, পরে বদলায় না। একই মানুষ customer ও vendor হলে দুটো আলাদা party | `PTY_DUPLICATE_SOURCE` |
| R9.3 | Status: `active`, `blocked`, `merged`, `archived`। `blocked`-এ নতুন Submit আটকায় (`party.block_mode` অনুযায়ী), বাকি item settle হয়। `archived`-এ নতুন line নয়, settle চলে। `merged`-এ survivor দেখায় | `PTY_PARTY_BLOCKED`, `PTY_PARTY_ARCHIVED`, `PTY_PARTY_MERGED` |
| R9.4 | Control account: `requires_party = 1` হলে `party_type` বাধ্যতামূলক। Line-এ `party_id` না থাকলে Submit reject। Type না মিললে reject। Post-এ আবার সস্তা পরীক্ষা (lock ছাড়া) | `PTY_PARTY_REQUIRED`, `PTY_PARTY_TYPE_MISMATCH` |
| R9.5 | Account-এ `requires_party` চালু বা `party_type` বদল শুধু যখন ওই account-এ posted line নেই বা সব posted balance open item-দ্বারা ঢাকা (opening load)। পরে type বদলানো যায় না | `PTY_ACCOUNT_RULE_VIOLATION` |
| R9.6 | Posted line `party_id`, `due_date`, `document_ref` আর বদলায় না (P1-10 trigger)। ভুল হলে reversal বা adjustment | DB trigger error |
| R9.7 | Control account line post হলে ঠিক একটা open item, একই transaction-এ। `uq (company_id, journal_line_id)`। Side line-এর side। Settlement-এর journal-এর control line (source\_type `subledger_settlement`) নতুন item বানায় না, ভাগ হিসেবে প্রয়োগ হয় | `OPI_CREATE_FAILED` |
| R9.8 | Amount: transaction currency-তে প্রথমে `txn_debit`/`txn_credit`, নইলে base। Base সবসময় line-এর `debit`/`credit` | CHECK |
| R9.9 | Due date ক্রম: line `due_date` → document date + party term → document date + default term → document date। Item-এ একবার সংরক্ষিত। পরে বদলাতে কারণ ও event লাগে (ledger বদলায় না, শুধু aging) | `OPI_REASON_REQUIRED` |
| R9.10 | Settlement-এ সব item একই party (merge ধরে), একই `party_type`, এক settlement currency। অন্যথায় reject | `OPI_PARTY_MISMATCH` |
| R9.11 | কমপক্ষে একটা debit-side আর একটা credit-side item। যোগফল সমান (settlement currency-তে)। Write-off আলাদা line, match-এ গন্ডিত নয় | `OPI_SAME_SIDE`, `OPI_UNBALANCED_SETTLEMENT` |
| R9.12 | প্রতি item-এ applied ≤ `open_txn - reserved_txn`। আংশিক অনুমত। `hold_flag` item dunning-এ যায় না, settle আটকায় না | `OPI_AMOUNT_EXCEEDS_OPEN` |
| R9.13 | Base অংশ = `ROUND(original_base × applied_txn / original_txn, 4)`, শেষ settlement-এ বাকি সবটুকু। সব settlement মিলিয়ে যোগফল ঠিক `original_base` | CHECK, RC10 |
| R9.14 | বিদেশি currency item settle হলে `FxRealizedService::compute(settlement)` অবশ্যের ফল থেকে Draft journal, status `pending_journal`। Phase 7 বন্ধ থাকলে শুধু base-currency item settle হয় | `OPI_FX_UNAVAILABLE` |
| R9.15 | Write-off শুধু একটা item-এর residual-এ, `acc_write_off_rules` limit-এর ভেতরে, কারণ কোড সহ। Journal Draft (`adjustment` type, OD-48a), approval সাধারণ rule-এ। Limit পার হলে reject, কোনো override নেই | `OPI_WRITEOFF_LIMIT`, `OPI_WRITEOFF_DISABLED` |
| R9.16 | Settlement journal Draft থাকার সময় item-এ amount `reserved_txn`-এ আটকানো। Journal post হলে apply, reject বা cancel হলে ছাড়া ও settlement `cancelled` | `OPI_SETTLEMENT_STATE` |
| R9.17 | Settlement date open period-এ হতে হবে আর সব item-এর document date-র পরে | `OPI_PERIOD_CLOSED`, `OPI_DATE_BEFORE_ITEM` |
| R9.18 | Unmatch নতুন settlement (`reverses_settlement_id`)। Journal আছে হলে controlled reversal Draft। Reversal post হলে item ফিরে আসে। এক settlement একবারই unmatch | `OPI_UNMATCH_BLOCKED` |
| R9.19 | কোনো journal reverse হলে (`reverses_journal_id`) মূল item settle থাকলে আগে unmatch দরকার। না থাকলে `subledger.auto_settle_reversal` অনুযায়ী স্বয়ংক্রিয় settlement | `OPI_REVERSAL_NEEDS_UNMATCH` |
| R9.20 | দ্বিতীয়বার settle অসম্ভব: row lock, `reserved_txn`, `ck_open_le_original`, idempotency key | `OPI_DUPLICATE_SETTLEMENT` |
| R9.21 | Aging bucket মাপা: `as_of - due_date` (profile `basis` অনুযায়ী বা document date)। `due_date ≥ as_of` হলে Current। Profile version ও snapshot report-এ থাকে | `OPI_AGING_PROFILE_INVALID` |
| R9.22 | RC10: প্রতি company ও `party_type`: Σ signed `open_base` (+ `reval_adjust_base`) = ওই type-এর control account-গুলোর posted balance (debit − credit)। Tolerance default 0 | RC10 ব্যর্থ |
| R9.23 | Credit limit: customer-এ exposure = খোলা signed base + এই journal-এর debit। Mode `off`, `warn`, `block`। Override শুধু `accounting.party.override_limit`, কারণ সহ | `PTY_CREDIT_LIMIT_EXCEEDED` |
| R9.24 | Party merge: একই type, দ্বিতীয় অনুমোদন, pending settlement নেই। Posted line বদলায় না, report survivor-এ দেখায় (alias, OD-46)। Unmerge শুধু যদি merge-পর cross-party settlement না থাকে | `PTY_MERGE_INVALID` |
| R9.25 | `acc_party_balances` cache; সত্যের উৎস open item। Cache মিল না থাকলে report open item থেকে পড়ে ও alert | RC10 sub-check |
| R9.26 | PII redact (Phase 16 hook): নাম, email, phone, ঠিকানা mask, id ও code থাকে, ledger অক্ষত (OD-50) | `PTY_PII_REDACTED` |

**Configuration (প্রতি সারির ১ থেকে ৯ কলাম ভরা):**

অক্ষরের অর্থ: (ক) উদ্দেশ্য ও Business Impact, (খ) Option ও Validation, (গ) Default Policy, (ঘ) Scope, (ঙ) বাধ্যতামূলক কি, (চ) কে বানায়, বদলায়, অনুমোদন করে, (ছ) বদলের প্রভাব (পুরোনো ও নতুন লেনদেন), (জ) Lock ও Audit। সারির বোল্ড নাম হলো Key।

**`subledger.enabled`.** (ক) উদ্দেশ্য: Subledger চালু বা বন্ধ; ভুলে চালু হলে party ছাড়া line আটকাবে। (খ) Option: `0` বা `1`; `1` হতে `enable-check` পাস (P9-15)। (গ) Default: `0`। (ঘ) Scope: Company (Platform Setting)। (ঙ) বাধ্যতামূলক: হ্যাঁ, সবার আগে। (চ) কে: Platform Admin বানায় ও বদলায় (`accounting.subledger.manage`), Finance Controller দ্বিতীয় অনুমোদন (change request)। (ছ) প্রভাব: পুরোনো journal-এ item নেই, cutover-এর balance opening load-এর মাধ্যমে; নতুন post-এ item; পুরোনো row বদলায় না। (জ) Lock ও Audit: চালুর পর বন্ধ করা যায় না (item আছে); `accounting.subledger.enabled`, আগে ও পরে enable-check সারাংশ।

**`party.block_mode`.** (ক) উদ্দেশ্য: Blocked party-র কাজ কতটুকু আটকায়। (খ) Option: `block_new` (নতুন line), `block_all` (settle-ও)। (গ) Default: `block_new`। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: হ্যাঁ, flag চালু হলে। (চ) কে: Accounting Admin (`accounting.party.manage`), অনুমোদন Finance Manager। (ছ) প্রভাব: নতুন Submit-এ; আগে Submit হওয়া journal-এ নয়। (জ) Lock ও Audit: lock নেই; `accounting.party.setting_changed` (আগের ও পরের মান)।

**`subledger.credit_limit_mode`.** (ক) উদ্দেশ্য: Customer exposure নিয়ন্ত্রণ; ভুল হলে sales আটকায় বা limit বাইপাস। (খ) Option: `off`, `warn`, `block`। (গ) Default: `warn` (OD-45)। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: ঐচ্ছিক। (চ) কে: Finance Manager বদলায় (`accounting.party.manage`), `block`-এ যাওয়ায় Controller অনুমোদন। (ছ) প্রভাব: নতুন Submit-এ। (জ) Lock ও Audit: lock নেই; `accounting.party.setting_changed`।

**`subledger.default_payment_term`.** (ক) উদ্দেশ্য: term-হীন party-র due date; ভুল হলে aging বাঁকা। (খ) Option: `acc_payment_terms.code`, active। (গ) Default: বাধ্যতামূলক, Admin দেবে। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: হ্যাঁ, flag চালুর আগে। (চ) কে: Accounting Admin, অনুমোদন Finance Manager। (ছ) প্রভাব: নতুন item-এ due date; পুরোনো item-এ সংরক্ষিত তারিখ অপরিবর্তিত। (জ) Lock ও Audit: item সংরক্ষণই lock; `accounting.party.setting_changed`।

**`subledger.writeoff_enabled`.** (ক) উদ্দেশ্য: ছোট difference write-off চালু; ভুল হলে আয় কমে বা অপব্যবহার। (খ) Option: `0`/`1`; `1` হলে অন্তত একটা active rule। (গ) Default: `0`। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: ঐচ্ছিক। (চ) কে: Accounting Admin (`accounting.subledger.manage`), অনুমোদন Finance Controller। (ছ) প্রভাব: নতুন settlement-এ; আগের write-off অপরিবর্তিত। (জ) Lock ও Audit: lock নেই; `accounting.subledger.setting_changed`।

**`subledger.auto_settle_reversal`.** (ক) উদ্দেশ্য: Reversal post-এ মূল item-এর সাথে স্বয়ংক্রিয় match; বন্ধ হলে হাতে match। (খ) Option: `0`/`1`। (গ) Default: `1`। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: ঐচ্ছিক। (চ) কে: Accounting Admin, অনুমোদন নেই। (ছ) প্রভাব: নতুন reversal-এ; আগের match অপরিবর্তিত। (জ) Lock ও Audit: lock নেই; `accounting.subledger.setting_changed`।

**`subledger.default_aging_profile`.** (ক) উদ্দেশ্য: Report-এর default bucket; ভুল হলে ভিন্ন মাপ। (খ) Option: active `acc_aging_profiles.code`, bucket বিচ্ছিন্ন নয়। (গ) Default: সীড `STD` (Current, 1-30, 31-60, 61-90, 90+)। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: হ্যাঁ। (চ) কে: Accounting Admin, অনুমোদন নেই। (ছ) প্রভাব: শুধু নতুন run; আগের export snapshot-এ profile version ধরা। (জ) Lock ও Audit: lock নেই; `accounting.subledger.setting_changed`।

**`subledger.rc10_tolerance`.** (ক) উদ্দেশ্য: RC10-এর গ্রহণযোগ্য পার্থক্য; বেশি হলে ভুল লুকায়। (খ) Option: ≥ 0, base currency, সর্বোচ্চ 1.0000। (গ) Default: `0.0000`। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: হ্যাঁ। (চ) কে: Finance Controller (`accounting.subledger.manage`), দ্বিতীয় অনুমোদন CFO ভূমিকায়। (ছ) প্রভাব: নতুন RC10 run-এ; আগের run-এর ফল অপরিবর্তিত। (জ) Lock ও Audit: lock নেই; `accounting.subledger.setting_changed`।

**`party.sync_mode`.** (ক) উদ্দেশ্য: Mirror কীভাবে হালনাগাদ হয়; দেরি হলে নতুন party-র line আটকায়। (খ) Option: `event`, `poll` (1 থেকে 60 মিনিট)। (গ) Default: `event` সাথে দৈনিক `poll` নিরাপত্তা (OD-50a)। (ঘ) Scope: System-wide। (ঙ) বাধ্যতামূলক: হ্যাঁ। (চ) কে: Platform Admin, অনুমোদন Technical Review। (ছ) প্রভাব: নতুন sync run-এ; আগের mirror row অক্ষত। (জ) Lock ও Audit: lock নেই; `accounting.party.setting_changed`।

**`party.mask_pii_in_reports`.** (ক) উদ্দেশ্য: Report-এ ব্যক্তিগত তথ্য লুকানো; বন্ধ হলে তথ্য ফাঁস। (খ) Option: `0`/`1`; চালু থাকলে `employee`-এ সবসময় mask। (গ) Default: `employee`-এ `1`, বাকি `0`। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: ঐচ্ছিক। (চ) কে: Accounting Admin, অনুমোদন ডেটা সুরক্ষা দায়িত্বপ্রাপ্ত ব্যক্তি। (ছ) প্রভাব: নতুন report; আগের export ফাইল অপরিবর্তিত। (জ) Lock ও Audit: lock নেই; `accounting.party.setting_changed`।

**Record `acc_payment_terms`.** (ক) উদ্দেশ্য: net দিন, discount শর্ত; ভুল হলে due date ভুল। (খ) Option: `kind` (`net_days`, `end_of_month`, `fixed_day`), `net_days` 0 থেকে 365, discount\_days ≤ net\_days। (গ) Default: সীড `NET30`, `IMMEDIATE`। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: হ্যাঁ, অন্তত একটা। (চ) কে: Accounting Admin (`accounting.party.manage`), অনুমোদন নেই। (ছ) প্রভাব: নতুন item-এ; আগের item-এ due date সংরক্ষিত, term বদলালেও নড়ে না। (জ) Lock ও Audit: ব্যবহৃত term মুছা যায় না (`PTY_TERMS_IN_USE`), version check; `accounting.party.terms_changed`।

**Record `acc_write_off_rules`.** (ক) উদ্দেশ্য: write-off-এর সর্বোচ্চ সীমা ও expense account; বড় limit মানে সম্পদ লুকিয়ে ফেলা। (খ) Option: party\_type + currency প্রতি একটা active, limit > 0, account expense type। (গ) Default: বাধ্যতামূলক (write-off চালু হলে), Admin দেবে (OD-48)। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: শর্তসাপেক্ষ। (চ) কে: Accounting Admin তৈরি, Finance Controller দ্বিতীয় অনুমোদন। (ছ) প্রভাব: নতুন settlement-এ; পুরোনো settlement-এ rule snapshot থাকে। (জ) Lock ও Audit: ব্যবহৃত rule মুছা যায় না; `accounting.subledger.writeoff_rule_changed`।

**Record `acc_aging_profiles`.** (ক) উদ্দেশ্য: bucket সংজ্ঞা; ভুল হলে বিশ্লেষণ বিভ্রান্তিকর। (খ) Option: ফাঁক বা overlap নেই, শেষ bucket open-ended, সর্বোচ্চ 10টা। (গ) Default: সীড `STD`। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: হ্যাঁ, একটা default। (চ) কে: Accounting Admin (`accounting.subledger.manage`), অনুমোদন নেই। (ছ) প্রভাব: নতুন run-এ; আগের report অপরিবর্তিত (version snapshot)। (জ) Lock ও Audit: version বাড়ে; `accounting.subledger.aging_profile_changed`।

**Record `acc_party_credit_limits`.** (ক) উদ্দেশ্য: party অনুযায়ী limit; ভুল হলে sales আটকায় বা limit ফাঁকা। (খ) Option: limit ≥ 0, currency নির্দিষ্ট, effective তারিখ overlap নেই। (গ) Default: limit নেই মানে সীমা নেই। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: ঐচ্ছিক। (চ) কে: Finance Manager (`accounting.party.manage`), বড় বৃদ্ধিতে Controller অনুমোদন। (ছ) প্রভাব: নতুন Submit-এ; আগের Submit-এ নয়। (জ) Lock ও Audit: আগের row অক্ষত (effective\_to দিয়ে বন্ধ); `accounting.party.credit_limit_changed`।

**Record `acc_party_accounts`.** (ক) উদ্দেশ্য: party type-অনুযায়ী default control/advance account; ভুল account-এ ডাবে। (খ) Option: account-এ `requires_party = 1`, `party_type` মিলে, `is_group = 0`। (গ) Default: প্রতি type-এ একটা `control`, Admin দেবে। (ঘ) Scope: Company। (ঙ) বাধ্যতামূলক: হ্যাঁ। (চ) কে: Accounting Admin (`accounting.party.manage`), Finance Controller অনুমোদন। (ছ) প্রভাব: নতুন সাজেশনে; posted line অপরিবর্তিত। (জ) Lock ও Audit: account-এ posted line থাকলে নতুন row, পুরোনো বন্ধ; `accounting.party.account_changed`।

### Operational Workflow

**W1. Invoiceর post থেকে open item।**

| ধাপ | কে / system | Data Entity | API / boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | Billing event → Inbox (Phase 4) → `PostingRuleEngine::build` | `acc_finance_event_inbox`, `acc_parties` | `PartyService::resolve(source_type, source_id)` | Party আছে, status নিষিদ্ধ নয়; না থাকলে sync চালিয়ে আবার | system | party অজানা হলে event `needs_attention` (Phase 4 ops), retry | Inbox identity | `accounting.party.resolve_failed` | অজানা party-র event journal বানায় না |
| 2 | `JournalService` Draft | `acc_journals`, `acc_journal_lines` (`party_id`, `due_date`, `document_ref`) | Phase 14 spec → JournalService | Control account line-এ party ও type | system | Draft সম্পূর্ণ না হলে কিছু লেখা হয় না | `source_active` unique | বিদ্যমান journal audit | Draft-এ party সব line-এ |
| 3 | Submit (user/system) | journal, credit limit | P2-13 Submit → validators | R9.4, R9.23 credit limit | `accounting.journal.submit` | reject সহ error list | Submit স্থিতিশীল | বিদ্যমান + `accounting.party.credit_limit_warned` | Party ছাড়া Submit আটকায় |
| 4 | Approval (Phase 3) | approval request | বিদ্যমান | বিদ্যমান SoD | বিদ্যমান | বিদ্যমান | বিদ্যমান | বিদ্যমান | বিদ্যমান |
| 5 | `PostingService::post` | `acc_open_items`, `acc_open_item_events` | `OpenItemService::create(journal)` একই transaction | প্রতি control line-এ একটা item, due date সিদ্ধান্ত R9.9 | system | ব্যর্থ হলে পুরো Post rollback, `OPI_CREATE_FAILED` | `uq (company_id, journal_line_id)` | `accounting.subledger.item_created` (journal-প্রতি একটা, সংখ্যা সহ) | Post-এর পর item সংখ্যা = control line সংখ্যা |
| 6 | Cache job | `acc_party_balances` | commit-র পর হালনাগাদ | `balance_version` | system | ব্যর্থ হলে পরের rebuild; report open item থেকে পড়ে | upsert | নিম্নমান | Cache সত্যের সাথে মেলে |

**W2. Payment receive ও settlement (FX ও write-off সহ)।**

| ধাপ | কে / system | Data Entity | API / boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | Payment journal post (W1-এর মতো) | credit-side item (`item_kind` `payment` বা `advance`) | PostingService hook | R9.7 | system | W1 | W1 | W1 | Unapplied item তৈরি |
| 2 | Finance user বা Phase 10 বা Phase 14 allocation job | নয়, read | POST `/settlements/suggest` | একই party, open item | `accounting.subledger.view` | কিছু লেখে না, তাই ব্যর্থতা সহজ | প্রযোজ্য নয় | নেই | প্রস্তাব document\_ref ও তারিখ ধরে |
| 3 | `OpenItemService::settle` শুরু | `acc_parties` (FOR SHARE), `acc_open_items` (id ক্রমে FOR UPDATE) | POST `/settlements` + `Idempotency-Key` | R9.10 থেকে R9.13, period, party status | `accounting.subledger.settle` | ব্যর্থ হলে rollback, কিছু লেখা হয় না | key + `uq (company_id, idempotency_key)`; সমান payload-এ পুরোনো ফল ফেরত | `accounting.subledger.settlement_started` | দুই user একই item একসাথে settle করলে একজন সফল |
| 4 | বিদেশি currency হলে FX | `acc_settlement_lines.fx_gain_loss_base` | `FxRealizedService::compute(settlement)` | Phase 7 চালু, rate আছে | system | rate না থাকলে `OPI_FX_UNAVAILABLE`, rollback | ক্ষতি নেই (pure) | ইনপুট rate snapshot settlement-এ | gain-loss সঠিক |
| 5 | Write-off থাকলে rule পরীক্ষা | `acc_write_off_rules` | ভেতরে | limit, কারণ | `accounting.subledger.writeoff` | `OPI_WRITEOFF_LIMIT` | সমান | `accounting.subledger.writeoff_requested` | Limit পার হলে আটকায় |
| 6 | Draft journal | `acc_journals` | `JournalService::createDraft` (একই transaction, শুধু INSERT) | balanced, dimension | system | ব্যর্থ হলে পুরো rollback | settlement\_id দিয়ে source key | বিদ্যমান | Draft balanced |
| 7 | Reserve | `acc_open_items.reserved_txn`, settlement `pending_journal` | ভেতরে | `reserved ≤ open` | system | rollback | নেই | `accounting.subledger.item_reserved` | Reservation ছাড়া না হওয়া পর্যন্ত অন্য settle আটকায় |
| 8 | Journal Submit, approval, Post (বিদ্যমান পথ) | journal | বিদ্যমান | বিদ্যমান | বিদ্যমান + SoD (settle করার user write-off approve করতে পারে না) | বিদ্যমান | বিদ্যমান | বিদ্যমান | Post হলে পরের ধাপ |
| 9 | Post hook apply | item `open_txn`, `open_base`, `status`; settlement `completed` | `OpenItemService::settle` apply অংশ (hook) | settlement `pending_journal`, reservation থাকা | system | ব্যর্থ হলে Post rollback | settlement স্থিতি দেখে দ্বিতীয় apply শূন্য | `accounting.subledger.settlement_completed` | Item `settled`/`partial`; RC10 মিলে |
| 10 | Journal reject/cancel হলে sweep | settlement `cancelled` | `StaleSettlementSweepJob` | journal status | system | retry | সমান | `accounting.subledger.settlement_cancelled` | Reservation ছাড়া হয় |

যে settlement-এ journal লাগে না (একই base currency, difference শূন্য), ধাপ 4 থেকে 8 বাদ, ধাপ 3-এর শেষেই apply হয়, status সরাসরি `completed`।

**W3. Unmatch (settlement reversal)।**

| ধাপ | কে / system | Data Entity | API / boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | User কারণ দিয়ে চায় | `acc_settlements` | POST `/settlements/{id}/unmatch` + key | আগের settlement `completed`, আগে unmatch হয়নি, কারণ আছে | `accounting.subledger.unmatch` | `OPI_UNMATCH_BLOCKED` | সমান key সমান ফল | `accounting.subledger.unmatch_requested` | দ্বিতীয়বার হয় না |
| 2 | Lock | settlement (FOR UPDATE), item (id ক্রমে) | ভেতরে | item বর্তমানে অন্য settlement-এ reserved নয় | system | rollback | নেই | নেই | নেই |
| 3 | Journal থাকলে reversal Draft | `acc_journals` (`reversal`) | বিদ্যমান reversal পথ, নতুন date open period-এ | মূল journal posted, period | system | rollback | reversal\_active unique | বিদ্যমান | একটাই reversal |
| 4 | নতুন unmatch settlement `pending_journal` | `acc_settlements.reverses_settlement_id` | ভেতরে | নেই | system | নেই | নেই | নেই | নেই |
| 5 | Reversal post হলে | item amount ফেরত, মূল settlement `unmatched` | post hook | নেই | system | Post rollback | স্থিতি দেখে | `accounting.subledger.settlement_unmatched` | Item ফিরে আসে, RC10 মিলে |
| 6 | Journal ছাড়া settlement | item | ধাপ 3 বাদ, ধাপ 5 সরাসরি | নেই | system | rollback | নেই | নেই | নেই |

**W4. Party sync ও merge।**

| ধাপ | কে / system | Data Entity | API / boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | `PartySyncJob` (event বা poll) | `acc_party_sync_runs`, `acc_parties` | ERPFlow master read API, cursor | `source_hash` বদলালে হালনাগাদ | system | cursor এগোয় না, retry | `uq (company_id, source_type, source_id)` | `accounting.party.synced` | দ্বিতীয়বার চালালে কোনো বদল নেই |
| 2 | Source-এ মুছে গেলে | `acc_parties.status` | ভেতরে | item আছে হলে `archived`, না মুছে ফেলা | system | alert | নেই | `accounting.party.source_removed` | Party কখনো মুছে না |
| 3 | User merge চায় | `acc_party_status_history` | POST `/parties/{id}/merge` | একই type, pending settlement নেই, survivor active | `accounting.party.merge` | `PTY_MERGE_INVALID` | key | `accounting.party.merge_requested` | নেই |
| 4 | Approver অনুমোদন | party | অনুমোদন API | requester ≠ approver | `accounting.party.merge_approve` | নেই | স্থিতিশীল | `accounting.party.merged` | কলাস `merged_into_party_id` বসে |

**W5. Enable (cutover) ও W6. মাসের শেষে aging ও RC10।**

| ধাপ | কে / system | Data Entity | API / boundary | Validation | Permission | Failure Handling | Idempotency | Audit | Acceptance |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| E1 | Admin precondition দেখে | সব নতুন table | GET `/subledger/enable-check` | তালিকা: সব party matched, control account সেট, term ও profile আছে, opening items লোড ও RC10 শূন্য পার্থক্য | `accounting.subledger.manage` | ব্যর্থ তালিকা দেখায় | প্রযোজ্য নয় | `accounting.subledger.enable_checked` | গাড় লাল থাকলে চালু হয় না |
| E2 | Opening item load | `acc_open_items` (`origin` = `opening_load`) | `OpeningItemLoadJob` (ফাইল) | প্রতি control account-এ যোগফল = GL balance cutover তারিখে | `accounting.subledger.manage` | পার্থক্য থাকলে সব rollback | ফাইল hash | `accounting.subledger.opening_loaded` | RC10 শূন্য |
| E3 | Flag চালু | `subledger.enabled` | Platform Setting + দ্বিতীয় অনুমোদন | E1 আবার | Controller | ব্যর্থ হলে বন্ধ | স্থিতিশীল | `accounting.subledger.enabled` | চালুর পর প্রথম post-এ item |
| M1 | রাতে `Rc10Job` | `acc_reconciliation_runs` | `SubledgerReconciler::run` | প্রতি type পার্থক্য ≤ tolerance | system | ব্যর্থ হলে alert, period close checklist আটকায় (Phase 12) | run key (company, date) | `accounting.subledger.rc10_run` | মিললে পাস |
| M2 | Aging report | `acc_open_items`, `acc_open_item_events` | GET `/reports/aging` | as-of তারিখ, profile | `accounting.subledger.view` | বড় হলে async export | export key | `accounting.subledger.report_exported` | সমষ্টি = control balance |

**Exception scenario:**

| দৃশ্য | আচরণ |
| --- | --- |
| Duplicate event (একই invoice দুইবার) | Phase 4 inbox identity আটকায়; দ্বিতীয় journal নেই, তাই দ্বিতীয় item নেই। `uq (company_id, journal_line_id)` শেষ সুরক্ষা |
| Posting ব্যর্থ (hook) | Post rollback, journal Approved থাকে, কারণ error-এ, retry যায় |
| Party অজানা বা ভুল type (mapping ভুল) | Draft/Submit-এ `PTY_PARTY_REQUIRED`/`TYPE_MISMATCH`; event ops queue-এ দেখায়, mapping ঠিক করে replay |
| Period বন্ধ | Settlement date বন্ধ period-এ হলে `OPI_PERIOD_CLOSED`। Unmatch-এ reversal date খোলা period-এ |
| Reversal | Posted invoice reverse হলে R9.19; settle থাকলে আগে unmatch |
| Adjustment (ভুল due date/party) | Posted line বদলায় না। Party ভুল হলে adjustment journal দিয়ে বাদ/যোগ; due date ভুল হলে item-এ due date বদল (event সহ) |
| Concurrent settlement | দ্বিতীয়জন lock-এ অপেক্ষা, তারপর `OPI_AMOUNT_EXCEEDS_OPEN`। Deadlock হলে id ক্রমে retry ৩ বার |
| Retry (client) | একই `Idempotency-Key` পুরোনো ফল দেয়, ভিন্ন payload-এ `IDEMPOTENCY_KEY_REUSED` (P1-09) |
| Settlement journal reject | Sweep নির্দিষ্ট সময়ে বাতিল করে reservation ছাড়ে |
| Blocked বা merged party | Blocked: mode অনুযায়ী। Merged: survivor-এ রুট; line-এ merged party দিলে `PTY_PARTY_MERGED` |
| Source party মুছে গেল | `archived`, item থাকে, settle চলে |
| Rounding পার্থক্য | শেষ settlement-এ বাকি base নেয় (R9.13) |
| Cache অমিল | Report open item থেকে, rebuild job, alert |

### Dependencies

| দিক | বি঵রণ |
| --- | --- |
| Phase 9 যা চায় | **Phase 6:** `journal_mgmt.enabled` ও `dimensions.enabled` চালু; journal type `adjustment`, `event_generated`, `import` (P6-01); number series (সীড `STL` বানানোর জন্য); `acc_journal_line_dimensions` (aging by dimension)। **Phase 7:** `FxRealizedService::compute`, `FxService::convert`, txn স্তম্ভ (`txn_currency`, `txn_debit`, `txn_credit`, `exchange_rate`)। **Phase 8:** tax registration পড়ার চাহিদা Phase 8-এর, Phase 9 ডেটা দেয় (নিচে দেখো)। **Phase 1 থেকে 5:** P1-09 (Idempotency), P1-10 (trigger), P1-12 (`AccountingJob`, `AccountingAudit`), P2-03 (validator), P2-12 (`PostingService`), P3-13 (RC framework), Phase 4 (Inbox) |
| Phase 9 যা দেয় | **Phase 7:** open item read-only access revaluation-এ (OD-49a) আর `PartyService::resolve`। **Phase 8:** `acc_party_tax_registrations` ও `PartyService::taxRegistrations(party, jurisdiction, date)`। **Phase 10:** `OpenItemService::settle` (bank match থেকে) ও `AgingService`। **Phase 12:** RC10 close checklist-এ। **Phase 14:** `PartyService::resolve`, `defaultAccount`, document\_ref কনভেনশন। **Phase 15:** party-র `ic_company_id` mapping শুরুর তথ্য। **Phase 16:** privacy redact hook, import। **Phase 17:** UAT স্ক্রিপ্ট |
| Build ক্রম | Phase 6, 7, 8-এর পর (Phase 8-এর সাথে সমান্তরাল হতে পারে)। Phase 10 ও 12 Phase 9-এর আগে শুরু হয় না |

### Implementation Tasks ও Subtasks

| Workstream | Card | Task | Subtasks | নির্ভর | Deliverable | Acceptance |
| --- | --- | --- | --- | --- | --- | --- |
| Schema | P9-01 | Party mirror table | 4টা table, composite FK, status history trigger | P6 gate | Migration, model | AC9.1 |
| Schema | P9-02 | Journal line ও account expand, control rule | তিন column, index, P1-10 trigger, validator rule | P9-01 | Migration, validator | AC9.2 |
| Service | P9-03 | `PartyService` | sync job, resolve, block, merge, privacy hook | P9-01 | Service, job | AC9.1, AC9.7 |
| Schema | P9-04 | Terms, party account, credit limit | 3 table, seed `NET30`, `IMMEDIATE`, service | P9-01 | Migration, service | AC9.3 |
| Service | P9-05 | Open item schema ও `create` | 2 table, hook, due date resolver, events | P9-02, P9-04 | Service, hook | AC9.3, AC9.4 |
| Schema | P9-06 | Settlement schema | 3 table, seed series `STL`, trigger/CHECK | P9-05 | Migration | AC9.5 |
| Service | P9-07 | Settlement engine | match, partial, many-to-many, advance, credit note, refund, FX, write-off, apply | P9-06, Phase 7 | Service | AC9.5, AC9.6 |
| Service | P9-08 | Unmatch ও reversal handling | unmatch, auto-settle reversal, block rule | P9-07 | Service | AC9.6 |
| Report | P9-09 | Aging ও balance cache | 3 table, AgingService, cache job | P9-05 | Service, job | AC9.8 |
| Report | P9-10 | Party ledger ও statement | running balance, export | P9-09 | Report | AC9.9 |
| Reconciliation | P9-11 | RC10 | SubledgerReconciler, party diff, job | P9-07 | RC10 check | AC9.10 |
| Control | P9-12 | Credit limit hook | exposure, Submit hook, override | P9-04, P9-05 | Hook | AC9.11 |
| API | P9-13 | API ও permission | endpoint, permission, error, OpenAPI | P9-03 থেকে P9-12 | API | AC9.12 |
| UI | P9-14 | UI | party, open item, settlement workbench, aging, statement, RC10 | P9-13 | Screens | AC9.12 |
| Rollout | P9-15 | Enable, opening load, integration | enable-check, opening load, Phase 14/7/10/16 hook | সব | Flag, loader | AC9.13 |
| Quality | P9-16 | UAT, concurrency, golden test | script, race test, performance | সব | Test class | AC9.14 |

### Testing Requirements

| ধরন | পরীক্ষা (Test class সব MySQL-এ) |
| --- | --- |
| Unit | Due date resolver (সব ক্রম), base অংশের rounding, bucket মাপ, party status মেশিন, write-off limit |
| Integration | `PartySyncTest`, `ControlAccountRuleTest`, `OpenItemCreateTest`, `SettlementTest`, `UnmatchTest`, `AgingTest`, `PartyStatementTest`, `Rc10Test`, `CreditLimitTest` |
| Concurrency | একই item-এ ২ settlement একসাথে; Post-এর সাথে settlement; unmatch-এর সাথে নতুন settle; দুই উল্টো ক্রমে দুই multi-item settlement (চক্র নেই); idempotent retry |
| Accounting-validation | প্রতি scenario-র শেষে RC10 শূন্য; open item যোগফল = control balance; settlement journal balanced; সব settlement-এ যোগফল = `original_base` |
| Security | Permission প্রতি endpoint-এ; SoD (settle vs write-off approve, merge request vs approve); অন্য company-র id-এ অ্যাক্সেস না; PII mask |
| Performance | 1 লাখ open item aging < 3 সেকেন্ড; 50-line settlement < 1 সেকেন্ড; 10 হাজার party sync < 60 সেকেন্ড; Post-এ hook যোগ হওয়ার খরচ < 10% |

**Golden test:**

| ID | দৃশ্য | প্রত্যাশিত ফল |
| --- | --- | --- |
| G9.1 | Base currency invoice 1000, ৩ কিস্তি payment 400 + 400 + 200 | Item `open` → `partial` → `partial` → `settled`; প্রতি ধাপে RC10 শূন্য; journal লাগেনি |
| G9.2 | Invoice USD 100 @1.10 (110), payment USD 100 @1.15 (115) | Realized gain 5; settlement journal Dr control 5 / Cr FX gain 5; উভয় item `settled`; RC10 শূন্য |
| G9.3 | Invoice 100.00, payment 99.50, write-off rule limit 1.00 | Write-off 0.50 Dr expense / Cr control; invoice `settled`; limit 0.40 হলে `OPI_WRITEOFF_LIMIT` |
| G9.4 | 3 invoice (300, 200, 100) + 1 credit note 150 + 1 payment 450 | Many-to-many settlement, যোগফল মিলে, সব কটা item `settled` |
| G9.5 | G9.2-এর settlement unmatch | Reversal Draft → post → দুই item আগের অবস্থায়, RC10 শূন্য, দ্বিতীয় unmatch `OPI_UNMATCH_BLOCKED` |
| G9.6 | Advance 500 আগে, invoice পরে | Advance item credit-side থাকে, invoice-এ apply, বাকি advance থাকে |

### Security ও Audit Requirements

| বিষয় | বিবরণ |
| --- | --- |
| Permission | `accounting.party.view`, `.manage`, `.sync`, `.block`, `.merge`, `.merge_approve`, `.override_limit`; `accounting.subledger.view`, `.manage`, `.settle`, `.unmatch`, `.writeoff`, `.writeoff_approve`, `.reconcile` |
| SoD | Settlement তৈরিকারী নিজের write-off journal approve করতে পারে না। Merge অনুরোধকারী ≠ approver। Credit limit override নিজের করা journal-এ নয় (কারণ লাগে, পরে পরীক্ষা)। Party block করা ব্যক্তি merge approve করতে পারে না |
| Audit event | `accounting.party.synced`, `.source_removed`, `.blocked`, `.unblocked`, `.archived`, `.merge_requested`, `.merged`, `.unmerged`, `.credit_limit_changed`, `.credit_limit_warned`, `.credit_limit_overridden`, `.terms_changed`, `.account_changed`, `.resolve_failed`, `.pii_redacted`, `.setting_changed`; `accounting.subledger.item_created`, `.item_reserved`, `.item_due_date_changed`, `.settlement_started`, `.settlement_completed`, `.settlement_cancelled`, `.unmatch_requested`, `.settlement_unmatched`, `.writeoff_requested`, `.writeoff_rule_changed`, `.aging_profile_changed`, `.opening_loaded`, `.enable_checked`, `.enabled`, `.rc10_run`, `.report_exported`, `.setting_changed` |
| Data protection | Party-র নাম, email, phone, ঠিকানা, tax id ব্যক্তিগত তথ্য। Log-এ শুধু `party_id`, নাম নয়। Export-এ mask সুইচ। Redact (Phase 16) ledger নষ্ট করে না |

### Deliverables

১৬টা card-এর migration (১৬টা নতুন table আর দুই বিদ্যমান table-এ expand); চারটা service; তিনটা job; RC10; API; UI; seed (`NET30`, `IMMEDIATE`, `STD` aging, `STL` series); enable-check; opening load tool; UAT script; runbook (RC10 ব্যর্থ হলে কী করতে হয়, sweep, cache rebuild)।

### Acceptance Criteria

- **AC9.1** Party sync দ্বিতীয়বার চালালে কোনো বদল হয় না। Party নাম বা tax id বদলানোর চেষ্টা `PTY_SOURCE_READONLY` দেয়। Source মুছলে item থাকলে party `archived`।
- **AC9.2** Control account line-এ party না থাকলে বা type না মিললে Submit reject। Draft-এ শুধু সতর্কতা। Posted line-এ `party_id`, `due_date`, `document_ref` বদলালে DB trigger আটকায়।
- **AC9.3** Due date ক্রম R9.9 মেনে চার স্তরেই সঠিক। Term বদলালে আগের item-এর due date নড়ে না।
- **AC9.4** Post-এ control line সংখ্যা = নতুন item সংখ্যা, একই transaction-এ। Hook ব্যর্থ হলে Post হয় না। Settlement journal-এর control line নতুন item বানায় না।
- **AC9.5** Full, partial, many-to-many, cross-document, credit note, advance ও refund settlement G9.1, G9.4, G9.6 মতো চলে। এক item-এ দ্বিতীয়বার অতিরিক্ত settle অসম্ভব (race test)।
- **AC9.6** Realized FX (G9.2), write-off (G9.3) ও unmatch (G9.5) সঠিক। সব journal Draft থেকে শুরু, নিজে post হয় না।
- **AC9.7** Block, merge (দ্বিতীয় অনুমোদন), unmerge ও PII redact নিয়মমতো। Posted data অক্ষত।
- **AC9.8** Aging সমষ্টি = RC10-এর open item যোগফল। Past as-of তারিখে ফল তখনকার অবস্থার সাথে মেলে। Currency ও dimension ধরে ভাগ সঠিক।
- **AC9.9** Party ledger-এ opening + লেনদেন = closing। Statement open item তালিকার সাথে মেলে। PII mask সুইচ কাজ করে।
- **AC9.10** RC10 প্রতি party type-এ সব scenario-র শেষে পাস। সামান্য ভুল ঢুকিয়ে দিলে RC10 party ধরে পার্থক্য দেখায়।
- **AC9.11** Credit limit warn/block mode কাজ করে। Override কারণসহ ও audited।
- **AC9.12** প্রতি endpoint-এ permission ও error সঠিক। UI সব স্ক্রিনে permission অনুযায়ী লুকায়।
- **AC9.13** Enable-check ব্যর্থ হলে flag চালু হয় না। সফল হলে opening load-সহ RC10 শূন্য।
- **AC9.14** UAT script সব পাস, concurrency ও performance লক্ষ্য পূরণ, G9.1 থেকে G9.6 সব পাস।
- **AC9.15** `subledger.enabled = 0` হলে Phase 8 শেষের সব আচরণ অপরিবর্তিত, নতুন table কেউ পড়ে না, Post-এ hook ডাকা হয় না।

### Entry ও Exit Criteria

**Entry:** Phase 6 ও 7 gate পাস (Phase 8 সমান্তরাল হলে tax registration কাঠামো স্থির)। P2-12, P1-10, P2-03 স্থির। OD-47, OD-48a, OD-49a-এর উত্তর P9-05-এর আগে। Staging-এ production copy আছে। Phase 6/7 flag staging-এ চালু।

**Exit:** সব AC9.1 থেকে AC9.15 ও G9.1 থেকে G9.6 পাস। Staging-এ production copy দিয়ে enable-check সফল ও RC10 শূন্য। Race ও performance test পাস। Registry হালনাগাদ। Phase 1-6 card-এর দরকারি সংশোধন merge। Production-এ flag বন্ধ রেখে deploy, rollback সম্ভব।

### Open Decisions

প্রতি OD-এর কাঠামো: কেন জরুরি, বিকল্প (সুবিধা, সীমাবদ্ধতা, প্রভাব), প্রস্তাবিত সিদ্ধান্ত, কে নেবে, কখন। Phase 9 বাদ সব baseline ধরে লেখা।

**OD-45 Credit limit বাস্তবায়ন (warn না block).** কেন জরুরি: P9-12 hook-এর আচরণ নির্ভর করে। বিকল্প: (ক) শুধু warn: সহজ, sales আটকায় না, কিন্তু limit বাস্তবে পার হয়। (খ) block সহ override: নিয়ন্ত্রণ শক্ত, কিন্তু Billing-এর invoice Draft আটকে যায়। (গ) party অনুযায়ী আলাদা mode। প্রস্তাবিত: (ক) default `warn`, company-তে `block` চালু করা যাবে (তাই baseline-এ কেউ আটকা পড়ে না)। কে: Business Approval। কখন: P9-12-এর আগে।

**OD-45a Dunning ও statement পাঠানোর দায়.** কেন জরুরি: দায় না থাকলে চিঠি কেউ পাঠায় না। বিকল্প: (ক) Accounting শুধু statement data ও report দেয়, পাঠানো Billingর (OD-118 এর মতো সীমা)। (খ) Accounting নিজে dunning level, চিঠি ও email চালায়: বড় scope, template ও channel লাগে। প্রস্তাবিত: (ক), যুক্তি: গ্রাহক যোগাযোগ Billingর কাজ, আর Accounting-এর কাছে `hold_flag` ও overdue তথ্য আছে। কে: Business Approval। কখন: Phase 14 শুরুর আগে।

**OD-46 Party merge-এর ধরন.** কেন জরুরি: P9-03-এর কাজ ও report-এর হিসাব সেই অনুযায়ী। বিকল্প: (ক) alias: `merged_into_party_id` বসে, posted line ও item অক্ষত, report survivor ধরে। সুবিধা নিরাপদ ও ফেরতযোগ্য; সীমা ব্যাক্তি-ভিত্তিক query-এ join বাড়ে। (খ) item-এর `party_id` survivor-এ সরানো: query সহজ, কিন্তু item ও posted line-এ party মিলে না, RC10 party-diff জটিল। প্রস্তাবিত: (ক)। কে: Accounting Expertise। কখন: P9-03-এর আগে।

**OD-46a AR ও AP contra (একই মানুষ customer ও vendor).** কেন জরুরি: নেট settlement চাইলে দুই party-র মধ্যে settle লাগবে। বিকল্প: (ক) এই phase-এ না, contra journal হাতে দেওয়া (R9.10 অনুযায়ী দুই type মেলানোর অনুমতি নেই)। (খ) `acc_party_links` বানিয়ে cross-type settlement: বড় scope, দুই control account-এ journal। প্রস্তাবিত: (ক), প্রয়োজন পরিষ্কার হলে পরের phase-এ। কে: Accounting Expertise। কখন: পরে (Phase 12-এর আগে)।

**OD-47 Enable-এর আগের balance (opening items).** কেন জরুরি: পুরোনো posted line-এ party নেই, তাই RC10 না মিললে flag চালু হয় না। বিকল্প: (ক) ফাইল থেকে open item লোড (`origin` = `opening_load`), প্রতি control account-এ যোগফল = GL balance। সহজ ও নিরাপদ; সংগ্রহ সময় লাগে। (খ) পুরোনো line-এ party বসানো (backfill): posted line-এর বদল নিষিদ্ধ, তাই সম্ভব নয়। প্রস্তাবিত: (ক)। কে: Business Approval (ডেটা সরবরাহ) ও Accounting Expertise। কখন: P9-05-এর আগে।

**OD-47a Payment event-এর স্বয়ংক্রিয় settlement.** কেন জরুরি: Phase 14-এর payment কতটুকু নিজে invoice মেলাবে। বিকল্প: (ক) শুধু event-এ স্পষ্ট allocation থাকলে `OpenItemService::settle` ডাকা, বাকি unapplied থাকে। (খ) document\_ref মিলিয়ে স্বয়ংক্রিয় অনুমান: ভুল match-এর ঝুঁকি। প্রস্তাবিত: (ক); (খ) শুধু `suggest` API-এ প্রস্তাব। কে: Business Approval। কে: Phase 14 শুরুর আগে (কখন)।

**OD-48 Write-off limit ও approval.** কেন জরুরি: P9-06-এর rule seed নির্ভর করে। বিকল্প: (ক) স্থির ছোট limit (যেমন প্রতি item 1.00 base) ও সাধারণ journal approval। (খ) শতাংশ-ভিত্তিক limit ও আলাদা approver। প্রস্তাবিত: (ক) ও প্রতি company-তে Admin সংখ্যা দেবে (default নেই)। কে: Business Approval। কখন: write-off চালুর আগে (P9-07-এর আগে)।

**OD-48a Settlement journal-এর type.** কেন জরুরি: Phase 6 seed-এ settlement-কেন্দ্রিক type নেই। বিকল্প: (ক) `adjustment` type ব্যবহার, source\_type `subledger_settlement` দিয়ে চেনার উপায়। (খ) নতুন type `subledger_settlement` (17 নম্বর, Phase 6 seed ও migration বদল)। প্রস্তাবিত: (ক) কারণ Phase 6 স্থির, পরে report-এ ব্যাকলাগ বাড়লে (খ)। কে: Accounting Expertise ও Technical Review। কখন: P9-06-এর আগে।

**OD-49 Advance ও unapplied cash-এর account.** কেন জরুরি: balance sheet-এ কোথায় দেখাবে তা। বিকল্প: (ক) একই control account-এ credit-side item। (খ) আলাদা advance account (`acc_party_accounts.purpose = advance`), দুটোই `requires_party`। প্রস্তাবিত: (খ) কারণ statutory report-এ আলাদা দেখানো সহজ; RC10 দুটো account একত্রে ধরে। কে: Accounting Expertise। কখন: P9-04-এর আগে।

**OD-49a Phase 7 revaluation open item কীভাবে পড়ে ও লেখে.** কেন জরুরি: না মিললে revaluation-এর পর RC10 ভাঙে। বিকল্প: (ক) item-স্তরে: Phase 7 `acc_open_items` read-only পড়ে, reval journal line-এ `document_ref = item_ref`, hook `reval_adjust_base` বাড়ায়। (খ) party-স্তরে: adjust `acc_party_balances`-এ, কিন্তু তখন cache সত্যের উৎস হয়ে যায়। প্রস্তাবিত: (ক); Phase 7 card-এ সংশোধন লাগবে। কে: Accounting Expertise ও Technical Review। কখন: P9-05-এর আগে (Phase 7 gate-এর আগেও)।

**OD-50 Privacy: PII redact ও retention.** কেন জরুরি: মুছে ফেলার অনুরোধ আর ledger retention দ্বন্দ্ব করে। বিকল্প: (ক) শুধু PII column mask, id ও code থাকে। (খ) পুরো party row মুছা: ledger-এ foreign key ভাঙে, অসম্ভব। প্রস্তাবিত: (ক), retention OD-116 অনুযায়ী। কে: Business Approval (আইন সহ)। কখন: Phase 16-এর আগে।

**OD-50a Party sync পদ্ধতি.** কেন জরুরি: দেরিতে নতুন party-র event আটকায়। বিকল্প: (ক) শুধু event: দ্রুত, কিন্তু event হারালে মিল ভাঙে। (খ) event ও দৈনিক poll। প্রস্তাবিত: (খ)। কে: Technical Review। কখন: P9-03-এর আগে।

**OD-51 Past as-of aging কীভাবে.** কেন জরুরি: পুরোনো তারিখের aging audit-এ লাগে। বিকল্প: (ক) `acc_open_item_events` থেকে পুনর্নির্মাণ: সঠিক, কিন্তু ধীর। (খ) মাস শেষে snapshot table: দ্রুত, কিন্তু নতুন table, বাইরে তারিখে সম্ভব নয়। প্রস্তাবিত: (ক), গত মাস শেষের জন্য পরে (খ) যোগ করা যাবে। কে: Technical Review। কখন: P9-09-এর আগে।

**OD-51a Bad debt provision.** কেন জরুরি: aging-ভিত্তিক provision চাইলে পদ্ধতি লাগবে। বিকল্প: (ক) এই phase-এ না; হাতে adjustment। (খ) bucket-ভিত্তিক হারে স্বয়ংক্রিয় accrual journal (Phase 12)। প্রস্তাবিত: (ক); পরে (খ) Phase 12-এ। কে: Accounting Expertise। কখন: পরে (Phase 12-এর আগে)।

## ২০. Phase 10: Bank ও Cash Management

Phase 10 bank statement-কে ledger-এর সাথে মেলানোর পথ তৈরি করে। সব Draft journal যায় `JournalService` দিয়ে। Status বদলায় শুধু `PostingService` (P2-12)। Phase 10 নিজে কোনো journal post করে না, কোনো posted row বদলায় না। Capability flag `bank.enabled`, default বন্ধ।

### ২০.১ Objective ও Scope

**Objective:** Phase 7 (FX) ও Phase 9 (open item, settlement) চালু থাকার পর ব্যাংক ও নগদের হিসাব ledger-এর সাথে প্রমাণসহ মেলানো। Bank statement ঢোকে, ledger-এর posted line বা Phase 9 open item-এর সাথে মেলে, যা মেলে না তা Draft journal হয়ে অনুমোদনে যায়, শেষে একজন সই করে। সই করা পর্যন্ত সবকিছু ফেরানো যায়। সই করার পর lock।

**ছয়টা ফল:**

1. Bank account master: প্রতিটা account একটা control GL account-এর সাথে বাঁধা। Account নম্বর encrypted, দেখানো হয় masked।
2. Statement import: CSV, OFX, MT940, CAMT.053 configurable profile দিয়ে, আর হাতে লেখা statement। একই file বা একই line দুইবার ঢুকলে ধরা পড়ে। দুই statement-এর মাঝে balance ধারাবাহিকতা যাচাই হয়।
3. Matching engine: statement line থেকে posted journal line বা Phase 9 open item। One-to-one, one-to-many, many-to-many, tolerance সহ। Score দিয়ে suggestion, মানুষ confirm করে।
4. Rule ও auto-categorization: bank charge, interest, চিহ্নিত নয় এমন receipt-এর জন্য Draft journal। কখনো auto-post নয়।
5. Reconciliation ও lock: RC12, outstanding item, সই (maker-checker), সইয়ের পর পুরোনো তারিখে bank GL-এ লেখা বন্ধ।
6. নগদের আশপাশ: bank transfer (currency আলাদা হলেও), petty cash (imprest), cheque lifecycle, cash book ও day book।

| বিষয় | ভেতরে | বাইরে | বাইরের কেন |
| --- | --- | --- | --- |
| Statement | CSV, OFX, MT940, CAMT.053 profile, manual entry, duplicate ও continuity যাচাই | Live bank API feed | OD-53। Adapter interface থাকে, bank-ভিত্তিক credential ও অনুমোদনের দায় আলাদা |
| Matching | Journal line, Phase 9 open item, tolerance, score, manual match ও unmatch | Phase 9 invoice বা bill নিজে তৈরি | Open item-এর মালিক Phase 9 |
| Draft তৈরি | Bank rule, transfer, petty cash, count shortage, tolerance write-off | Auto-post, auto-approve | G9 ও OD-54। Client-এর লিখিত সিদ্ধান্ত ছাড়া নয় |
| Reconciliation | RC12, report, sign-off, reopen, lock | Statutory bank confirmation letter | Phase 16 audit pack |
| Payment | Payment proposal (শুধু OD-56 হ্যাঁ হলে) | Bank payment file (pain.001, NACHA) | Bank-ভিত্তিক format, সই ও security আলাদা প্রকল্প |
| Petty cash | Fund, voucher, replenishment, count | কর্মচারীর advance ও expense claim | Employee mirror ও claim workflow অন্য module-এর |
| Cheque | Register, lifecycle, stale, bounce (OD-52 হ্যাঁ হলে) | Cheque printing ও MICR | Printer ও template আলাদা বিষয় |
| Cash flow | `is_cash_equivalent` মেনে চলা, transfer-এ cash flow-এ প্রভাব শূন্য | Cash forecasting | Phase 13 forecast |
| Gateway | Gateway settlement report আলাদা profile হিসেবে import | Chargeback ও fee logic | Gateway-ভিত্তিক, আলাদা প্রকল্প |

**Baseline অনুমান:**

- Phase 10-এর capability flag `bank.enabled`। Default বন্ধ। বন্ধ থাকলে আচরণ Phase 9 শেষের মতোই। Cheque অংশের আলাদা flag `bank.cheque.enabled` (OD-52)।
- একটা company, একটা ledger, functional currency = base currency (G1, G2, G3)।
- Journal type `bank` Phase 6-এ seed হয়েছে (owner\_phase 10, `is_active = 0`, `allow_manual_entry = 0`)। Phase 10 enable পথ এটা active করে।
- Statement line-এর amount signed: পজিটিভ = account-এ জমা, নেগেটিভ = বের।
- Matching ও reconciliation ledger বদলায় না। শুধু Phase 10-এর নিজের table লেখে।
- সব system-তৈরি journal Draft (G9)।

### ২০.২ Functional Requirements

| ক্রম | Requirement | বিস্তার | Card |
| --- | --- | --- | --- |
| ক | Bank account master | Code, নাম, currency, GL link, bank-এর তথ্য, status (draft, active, suspended, closed)। GL account-এ `is_bank_account = 1` লাগে | P10-01, P10-02 |
| খ | সংবেদনশীল তথ্যের সুরক্ষা | Account নম্বর ও IBAN encrypted। List ও detail-এ masked। পূর্ণ মান দেখতে আলাদা permission ও প্রতিবার audit | P10-01, P10-02 |
| গ | Import profile | Format, delimiter, encoding, column mapping, date ও number format, sign convention, skip pattern। Profile পরীক্ষা (dry-run) | P10-03, P10-04 |
| ঘ | Statement import | File upload, async parse, validate, line সংরক্ষণ। CSV, OFX, MT940, CAMT.053। ত্রুটির line-ভিত্তিক report | P10-04 |
| ঙ | Manual statement | যাদের file নেই তাদের জন্য হাতে statement ও line, একই validation, দ্বিতীয় ব্যক্তির review | P10-04 |
| চ | Duplicate ও continuity | File hash, content hash, line hash। Opening = আগের closing। Opening + lines = closing | P10-04 |
| ছ | Book item projection | Bank GL account-এর posted line-এর পড়ার কপি, matching ও report-এর জন্য index সহ | P10-05 |
| জ | Match suggestion | Amount, reference, তারিখ, পক্ষ-নাম, uniqueness ধরে score। Tolerance। One-to-one থেকে many-to-many | P10-05 |
| ঝ | Manual match ও unmatch | মানুষ confirm, split ও merge, unmatch (কারণ সহ), sign-off-এর পর lock | P10-06 |
| ঞ | Phase 9 integration | Statement line থেকে open item settle (Draft), posted হলে match active | P10-06 |
| ট | Bank rule | Match rule ও categorize rule, priority, condition, simulate। Categorize শুধু Draft তৈরি করে | P10-07 |
| ঠ | Bank transfer | নিজের দুই account-এর মধ্যে, currency আলাদা হলে FX, in-transit, fee | P10-08 |
| ড | Reconciliation | Statement-ভিত্তিক, RC12, outstanding item, adjusted balance, submit, sign-off, reopen | P10-09 |
| ঢ | Reconciliation lock | সইয়ের পর reconciled\_through\_date, পুরোনো তারিখে bank GL-এ লেখা আটকায় | P10-09 |
| ণ | Report | Bank reconciliation statement, outstanding item aging, cash book, day book, bank balance summary | P10-10 |
| ত | Petty cash | Fund (imprest), voucher, approval, replenishment, count, shortage ও overage Draft | P10-11 |
| থ | Cheque lifecycle | Cheque book, issued, presented, cleared, stale, bounced, cancelled (OD-52) | P10-12 |
| দ | Cash Flow মান | Bank ও petty cash GL `is_cash_equivalent = 1`। Transfer cash flow-এ শূন্য প্রভাব ফেলে (P3-07) | P10-02, P10-08 |
| ধ | UI | Account, import, matching workbench, reconciliation, petty cash, cheque | P10-13 |
| ন | Enablement ও যাচাই | `bank.enabled` precondition, journal type `bank` activate, RC12, runbook, UAT | P10-14, P10-15 |

### ২০.৩ Technical Requirements

| বিষয় | Requirement |
| --- | --- |
| Service | `BankAccountService`, `BankMatchService`, `BankReconciliationService` (Master Plan §৭)। সহায়ক: `BankImportService` ও parser (`CsvStatementParser`, `OfxStatementParser`, `Mt940StatementParser`, `Camt053StatementParser`), `BankRuleEngine`, `BankTransferService`, `PettyCashService`, `ChequeService`, `BankReportService`, `BankBookSync` |
| Job ও queue | সব job `AccountingJob` (P1-12) ধরে, company context সহ। `BankStatementImportJob` (queue `bank-import`), `BankAutoMatchJob` ও `BankRuleApplyJob` (queue `bank-match`), `BankBookItemSyncJob` (posted event-এর পর), `BankBookItemSweepJob` (ঘণ্টায় একবার, ফাঁক ধরে), `BankOutstandingAlertJob` (দৈনিক), `ChequeStaleJob` (দৈনিক)। প্রতি bank account-এ একসাথে একটাই import চলে (bank account row-এর lock) |
| Journal তৈরি | সব Draft `JournalService` দিয়ে, journal type `bank`, `origin = bank`, `source_type` ও `source_id` সহ (বিদ্যমান `source_active` unique key idempotency দেয়)। Phase 10 কখনো `acc_journals.status` লেখে না, কখনো `PostingService` ডাকে না |
| Post-এর সাথে সম্পর্ক | Post transaction-এ Phase 10-এর একটাই যোগ: validator hook (P2-03) যা `acc_bank_accounts` row `FOR SHARE` পড়ে (lock ক্রমের শেষে)। Post-এর পরের কাজ (projection, match-এর অবস্থা) commit-এর পরে event দিয়ে, Post transaction-এর বাইরে |
| Lock ক্রম | Global ক্রম অপরিবর্তিত (journal, approval request, account, period, voucher counter)। Phase 10 নতুন lock রাখে শুধু নিজের table-এ, ক্রম: bank account, reconciliation, statement, statement line (id ক্রমে), book item (id ক্রমে), match। Phase 9 `settle` ডাকার আগে Phase 10-এর transaction commit হয়ে যায় (match অবস্থা `pending_post`), তাই দুই ক্রম মেশে না |
| Concurrency | সব write `version` দিয়ে optimistic। Matching-এ দুই পক্ষের সারি id ক্রমে `FOR UPDATE`, active match unique key দ্বিতীয় প্রতিরক্ষা। Deadlock হলে সর্বোচ্চ ৩ বার পুনঃচেষ্টা |
| Performance | ফাইল সর্বোচ্চ ২০ MB ও ২,০০,০০০ line (setting)। Stream parse, ১,০০০ line করে chunk insert, hash যাচাই batch `IN` query-তে। Candidate খোঁজা `acc_bank_book_items`-এর index-এ, line প্রতি N+1 query নয়। লক্ষ্য: ১,০০,০০০ line import ৫ মিনিটে, auto-match ১০ মিনিটে, reconciliation হিসাব ৫০,০০০ open item-এ ৫ সেকেন্ডে (aggregate query)। Cash book রপ্তানি cursor দিয়ে stream |
| Failure handling | Import chunk-ভিত্তিক transaction, `cursor` রেখে পুনরায় চালু করা যায়। Statement পুরো validate না হওয়া পর্যন্ত `importing` (তালিকায় লুকানো)। Parse ত্রুটি line নম্বর সহ। `bank.import.max_error_lines = 0` হলে একটা ত্রুটিতেই পুরো file বাতিল। Phase 9 `settle` ব্যর্থ হলে match `voided`, line আবার `unmatched` |
| Idempotency | সব write API-তে `Idempotency-Key` (P1-09)। Import: file hash, statement content hash, line hash ও occurrence। Rule: `source_active`। Transfer ও petty cash replenishment: নিজস্ব unique key |
| সুরক্ষা | Account নম্বর ও IBAN AES-256-GCM, `key_version` সহ। Duplicate ধরার জন্য HMAC-SHA256 hash (key আলাদা)। Original statement file encrypted, `file_hash` সহ, quarantine scan P6-08-এর মতো। Log, audit diff, export ও error `details`-এ sensitive মান কখনো নয়। Reveal endpoint আলাদা permission ও rate limit |
| Cash Flow | Bank ও petty cash GL `is_cash_equivalent = 1`। Transfer-এর in-transit account-ও তাই, নইলে transfer cash flow-এ ভুয়া নড়াচড়া দেখায় (P3-07 যাচাই) |
| Observability | Metric: import সময়, duplicate হার, auto-match হার, unmatched বয়স, projection lag। Alert `acc_alert_state`-এ: lag > ১০ মিনিট, unmatched > `bank.recon.max_unrecorded_age_days`, import ব্যর্থ |
| Flag | সব endpoint প্রথমে `bank.enabled` দেখে। বন্ধ থাকলে `BNK_DISABLED` (403)। Validator hook-এর reconciled-period lock তবুও সক্রিয় থাকে (R10.51) |

### ২০.৪ Data Model ও API

Phase 10 ২০টা নতুন table যোগ করে (সব `acc_` prefix, সবাই `uq (company_id, id)` ও composite FK মানে)। পূর্ণ column spec Pack-এ (P10-01 থেকে P10-12)।

| Table | কাজ | মূল column | Card |
| --- | --- | --- | --- |
| `acc_bank_accounts` | Bank account master | `code, name, gl_account_id, currency, bank_name, branch_name, country, account_type, account_no_enc, account_no_last4, account_no_hash, iban_enc, swift_bic, status, import_profile_id, reconciled_through_date, key_version, version` | P10-01 |
| `acc_bank_status_history` | সব Phase 10 object-এর অবস্থার ইতিহাস (append-only) | `entity_type, entity_id, from_status, to_status, reason, actor_id` | P10-01 |
| `acc_bank_import_profiles` | Format ও mapping | `code, format, bank_account_id, config, sign_convention, is_active, version` | P10-03 |
| `acc_bank_import_batches` | Import কাজের অবস্থা | `bank_account_id, profile_id, file_hash, file_ref, status, total_lines, imported_lines, duplicate_lines, error_lines, cursor` | P10-03 |
| `acc_bank_statements` | Statement header | `bank_account_id, statement_ref, period_from, period_to, opening_balance, closing_balance, currency, source, file_hash, content_hash, prev_statement_id, continuity_status, status` | P10-03 |
| `acc_bank_statement_lines` | Statement line | `statement_id, line_no, value_date, booking_date, amount, bank_ref, customer_ref, counterparty_name, description, line_hash, occurrence_no, dup_of_line_id, status` | P10-03 |
| `acc_bank_book_items` | Posted bank GL line-এর পড়ার কপি | `bank_account_id, journal_line_id, journal_id, journal_date, amount, document_ref, narration, matched_match_id, is_open` | P10-05 |
| `acc_bank_matches` | Match header | `bank_account_id, match_type, method, score, status, statement_total, book_total, difference, difference_treatment, reconciliation_id` | P10-05 |
| `acc_bank_match_items` | Match-এর দুই পক্ষ | `match_id, side, statement_line_id, book_item_id, open_item_id, settlement_id, amount` | P10-05 |
| `acc_bank_rules` | Match ও categorize rule | `code, rule_type, bank_account_id, priority, direction, conditions, counter_account_id, party_id, narration_template, dimension_defaults, stop_on_match, is_active, version` | P10-07 |
| `acc_bank_transfers` | নিজের account-এর মধ্যে transfer | `from_bank_account_id, to_bank_account_id, from_amount, to_amount, rate, fee_amount, status, out_journal_id, in_journal_id` | P10-08 |
| `acc_bank_reconciliations` | Reconciliation header | `bank_account_id, statement_id, as_of_date, statement_closing, book_balance, adjusted_bank, adjusted_book, difference, status, prepared_by, signed_off_by` | P10-09 |
| `acc_bank_reconciliation_items` | সই-করা snapshot (append-only) | `reconciliation_id, item_type, journal_line_id, statement_line_id, amount` | P10-09 |
| `acc_petty_cash_funds` | Imprest fund | `code, gl_account_id, custodian_user_id, imprest_amount, max_voucher_amount, status` | P10-11 |
| `acc_petty_cash_vouchers` | খরচের voucher | `fund_id, voucher_no, voucher_date, amount, expense_account_id, status, replenishment_id` | P10-11 |
| `acc_petty_cash_replenishments` | ভরাট | `fund_id, total_amount, source_bank_account_id, status, journal_id` | P10-11 |
| `acc_petty_cash_counts` | নগদ গণনা | `fund_id, counted_amount, expected_amount, difference, status, journal_id` | P10-11 |
| `acc_bank_cheque_books` | Cheque book (OD-52) | `bank_account_id, prefix, from_no, to_no, status` | P10-12 |
| `acc_bank_cheques` | Cheque register (OD-52) | `cheque_book_id, cheque_no, direction, amount, status, cheque_date, journal_id` | P10-12 |
| `acc_bank_cheque_events` | Cheque অবস্থার ইতিহাস (append-only) | `cheque_id, from_status, to_status, event_date, statement_line_id` | P10-12 |

**বিদ্যমান table (শুধু পড়া বা নির্ধারিত ব্যবহার):**

- `acc_accounts`: `is_bank_account`, `is_cash_equivalent`, `allow_foreign_currency`, `currency_restriction` পড়া। Phase 10 নতুন column যোগ করে না।
- `acc_journals` ও `acc_journal_lines`: Draft লেখা শুধু `JournalService` দিয়ে। Posted row পড়া।
- `acc_reconciliation_runs`: RC12 ও তার উপ-পরীক্ষার ফল (P3-13 framework)।
- Phase 9: `acc_open_items`, `acc_settlements` পড়া, লেখা শুধু `OpenItemService::settle`।
- `acc_number_series` (P6-03): journal type `bank`-এর series `BK`।

**API (prefix `/accounting`, সব write-এ `Idempotency-Key`):**

| গোষ্ঠী | Endpoint | Permission |
| --- | --- | --- |
| Bank account | `GET/POST /bank-accounts`, `GET/PATCH /bank-accounts/{id}`, `POST /bank-accounts/{id}/activate`, `/suspend`, `/close`, `POST /bank-accounts/{id}/reveal` | `bank.view`, `.manage`, `.reveal` |
| Profile | `GET/POST /bank-import-profiles`, `PATCH /bank-import-profiles/{id}`, `POST /bank-import-profiles/{id}/test` | `bank.view`, `.manage` |
| Import | `POST /bank-statements/import` (multipart, 202), `GET /bank-import-batches/{id}`, `POST /bank-statements` (manual), `GET /bank-statements`, `GET /bank-statements/{id}`, `GET /bank-statements/{id}/lines` (cursor), `POST /bank-statements/{id}/void`, `POST /bank-statements/{id}/continuity-override`, `PATCH /bank-statement-lines/{id}` | `bank.import`, `bank.view`, `bank.approve` (override) |
| Matching | `GET /bank-statements/{id}/suggestions`, `POST /bank-matches`, `POST /bank-matches/{id}/unmatch`, `GET /bank-matches`, `GET /bank-book-items`, `POST /bank-accounts/{id}/auto-match` | `bank.match`, `bank.view` |
| Rule | `GET/POST /bank-rules`, `PATCH /bank-rules/{id}`, `POST /bank-rules/{id}/simulate`, `POST /bank-rules/{id}/deactivate`, `POST /bank-statements/{id}/apply-rules` | `bank_rule.manage`, `.approve` |
| Transfer | `GET/POST /bank-transfers`, `GET /bank-transfers/{id}`, `POST /bank-transfers/{id}/submit`, `/cancel`, `/complete` | `bank_transfer.manage`, `.approve` |
| Reconciliation | `POST /bank-reconciliations`, `GET /bank-reconciliations/{id}`, `POST /bank-reconciliations/{id}/submit`, `/sign-off`, `/reopen-request`, `/void` | `bank.reconcile`, `bank.approve` |
| Report | `GET /reports/bank-reconciliation/{id}`, `/reports/outstanding-items`, `/reports/cash-book`, `/reports/day-book`, `/reports/bank-balance-summary` | `bank.view` |
| Petty cash | `GET/POST /petty-cash/funds`, `/vouchers`, `/replenishments`, `/counts` ও তাদের `submit`, `approve`, `reject`, `cancel` | `petty_cash.view`, `.manage`, `.approve` |
| Cheque | `GET/POST /cheque-books`, `GET/POST /cheques`, `POST /cheques/{id}/present`, `/clear`, `/bounce`, `/cancel` | `cheque.view`, `.manage`, `.approve` |

সব endpoint-এ error shape `{code, message, field, details, correlation_id}`, prefix `BNK_`।

### ২০.৫ Business Rules ও Configuration

**Business rule:**

| Rule | নিয়ম | Card |
| --- | --- | --- |
| R10.1 | একটা active Bank Account ঠিক একটা GL account-এর সাথে যুক্ত। একটা GL একই সময়ে একটাই active Bank Account পায়। DB generated unique key এটা ধরে | P10-01 |
| R10.2 | GL account-এ `is_bank_account = 1` ও type asset লাগে। Credit card বা overdraft liability হলে OD-52b-র সিদ্ধান্ত অনুযায়ী | P10-02 |
| R10.3 | Bank currency base না হলে GL-এ `allow_foreign_currency = 1` ও `currency_restriction` = bank currency। প্রথম statement বা posting-এর পর currency বদলায় না | P10-02 |
| R10.4 | Account নম্বর ও IBAN encrypted। List ও detail-এ শুধু শেষ ৪ অঙ্ক। পূর্ণ মান reveal endpoint-এ, কারণ ও audit সহ | P10-02 |
| R10.5 | Unreconciled statement, open outstanding item, pending transfer বা pending cheque থাকলে Bank Account close হয় না | P10-02 |
| R10.6 | Posting শুরুর পর GL link বদলায় না। নতুন GL মানে নতুন Bank Account ও তাদের মধ্যে transfer | P10-02 |
| R10.7 | Statement একটা Bank Account ও একটা currency-র। Opening + সব line = closing। না মিললে `BNK_STATEMENT_BALANCE_MISMATCH` | P10-04 |
| R10.8 | একই file hash বা content hash আবার এলে নতুন statement হয় না। `BNK_STATEMENT_DUPLICATE` এবং আগের statement-এর id ফেরে। একই `Idempotency-Key`-এ আগের উত্তরই ফেরে | P10-04 |
| R10.9 | Overlap-এ একই line (`line_hash` ও `occurrence_no`) আগে থাকলে নতুনটা `duplicate`। Matching ও total থেকে বাদ | P10-04 |
| R10.10 | নতুন statement-এর opening = একই account-এর আগের statement-এর closing। না মিললে `bank.import.continuity_policy`। Override-এ `bank.approve` ও কারণ | P10-04 |
| R10.11 | Sign convention profile-এ ঠিক থাকে। Import-এর পর বদলায় না | P10-03 |
| R10.12 | Imported line অপরিবর্তনীয়। বদলায় শুধু status, match, note, ignore-এর কারণ | P10-03 |
| R10.13 | হাতে লেখা statement-এ একই validation। যে লিখেছে সে review করতে পারে না (`bank.import.manual_review_required`) | P10-04 |
| R10.14 | Original file encrypted রাখা হয়, hash সহ। ডাউনলোডে permission ও audit | P10-04 |
| R10.15 | Book item = Bank GL account-এর posted line। Posted event-এর পর projection-এ আসে। একই journal line দুবার আসে না | P10-05 |
| R10.16 | Match-এ statement মোট ও book মোট সমান বা tolerance-এর মধ্যে। পার্থক্য থাকলে `difference_treatment` বাধ্যতামূলক | P10-05 |
| R10.17 | Statement `value_date` ও book `journal_date`-এর ফারাক date window-এর মধ্যে। বাইরে হলে manual match-এ কারণ লাগে, suggestion হয় না | P10-05 |
| R10.18 | এক statement line এক active match-এ। এক book item এক active match-এ। DB unique key এটা ধরে | P10-05 |
| R10.19 | Book item ওই Bank Account-এর GL-এর হতে হবে (`BNK_MATCH_SIDE_INVALID`)। `duplicate` বা `ignored` line match হয় না | P10-06 |
| R10.20 | Score 0 থেকে 100। Weight configurable। একাধিক সমান candidate থাকলে suggestion অস্পষ্ট চিহ্নিত, auto-confirm হয় না | P10-05 |
| R10.21 | Suggestion কিছু লেখে না। Confirm মানুষের কাজ। Auto-confirm default বন্ধ। চালু হলে শুধু exact reference, exact amount, one-to-one | P10-05, P10-06 |
| R10.22 | Tolerance পার্থক্য নিজে write-off হয় না। `write_off_draft` মানে JournalService দিয়ে Draft, approval সহ | P10-06 |
| R10.23 | Phase 9 open item match: `OpenItemService::settle` Draft তৈরি করে, match `pending_post`। Draft posted হলে `active`। Rejected বা cancelled হলে `voided` | P10-06 |
| R10.24 | Book-only net: মূল ও reversal line-এর যোগফল শূন্য হলে statement ছাড়াই match | P10-06 |
| R10.25 | Unmatch-এ কারণ বাধ্যতামূলক। Reconciled match reopen ছাড়া unmatch হয় না (`BNK_MATCH_LOCKED`) | P10-06 |
| R10.26 | Rule priority: ছোট সংখ্যা আগে। একই account ও priority-তে দুই active rule নিষিদ্ধ। `stop_on_match` প্রথম hit-এ থামায় | P10-07 |
| R10.27 | Categorize rule শুধু Draft তৈরি করে। Auto-post ও auto-approve নেই (G9, OD-54) | P10-07 |
| R10.28 | Rule-তৈরি Draft idempotent। `source_type = bank_statement_line`, `source_id` = line id | P10-07 |
| R10.29 | Rule-এর counter account bank বা cash GL হতে পারে না (সেজন্য transfer)। Control account হলে party বাধ্যতামূলক | P10-07 |
| R10.30 | Rule বদল পুরোনো Draft ও posted journal ছোঁয় না। Simulate কিছু লেখে না | P10-07 |
| R10.31 | Transfer দুটো ভিন্ন active Bank Account-এর মধ্যে | P10-08 |
| R10.32 | Currency ভিন্ন হলে rate `FxService::rate` থেকে। Manual rate Phase 7-এর নিয়মে | P10-08 |
| R10.33 | Value date ভিন্ন বা `bank.transfer.use_in_transit = true` হলে দুই journal, in-transit account দিয়ে। Fee আলাদা line | P10-08 |
| R10.34 | Transfer-এর সব journal Draft। Posted হলে cancel নয়, reversal Draft | P10-08 |
| R10.35 | Reconciliation একটা Bank Account ও একটা statement-এর (OD-57)। `as_of_date` = statement `period_to` | P10-09 |
| R10.36 | Sign-off শর্ত: RC12 পার্থক্য শূন্য, প্রতিটা statement line matched বা explained, projection lag শূন্য, প্রস্তুতকারী ≠ sign-off-কারী | P10-09 |
| R10.37 | Sign-off-এ snapshot append-only। পরের report snapshot থেকে | P10-09 |
| R10.38 | Sign-off-এর পর `reconciled_through_date` বাড়ে। তার আগের `journal_date`-সহ Bank GL line-এর journal Submit বা Post আটকায় (`BNK_RECONCILED_PERIOD`), যদি `bank.lock.reconciled_posting = block` | P10-09 |
| R10.39 | Reopen শুধু সবচেয়ে সাম্প্রতিক signed-off reconciliation-এর, Maker-Checker সহ | P10-09 |
| R10.40 | Outstanding item বয়স অনুযায়ী দেখানো হয়। Threshold পেরোলে alert | P10-10 |
| R10.41 | Report শুধু posted ledger ও projection পড়ে, Draft ধরে না। Cash book running balance account ধরে, opening carry সহ | P10-10 |
| R10.42 | Petty cash fund-এর GL-এ `is_cash_equivalent = 1`, `is_bank_account = 0`। একটা fund একটা GL | P10-11 |
| R10.43 | `max_voucher_amount` পেরোলে উচ্চতর approval ও receipt বাধ্যতামূলক | P10-11 |
| R10.44 | Voucher-এর approver ≠ requester | P10-11 |
| R10.45 | Replenishment-এ approved ও এখনো replenish না হওয়া voucher। একই voucher দুবার নয়। Draft journal: Dr expense, Cr source | P10-11 |
| R10.46 | Count-এ গণনাকারী ≠ সাক্ষী। Expected = imprest − unreplenished approved voucher। পার্থক্যে Draft journal | P10-11 |
| R10.47 | Cheque নম্বর book-এর সীমার মধ্যে ও unique | P10-12 |
| R10.48 | Cheque অবস্থা strict: issued → presented → cleared। issued → cancelled, stopped, stale। presented → bounced। Cleared ফেরে না | P10-12 |
| R10.49 | Cheque অবস্থা বদল নিজে journal post করে না। Bounce বা stale-এ পাল্টা কাজ Draft হিসেবে প্রস্তাব | P10-12 |
| R10.50 | সব write-এ `Idempotency-Key` ও `version`। ব্যর্থ হলে `BNK_CONFLICT` (409) | সব card |
| R10.51 | `bank.enabled` বন্ধ হলে সব endpoint `BNK_DISABLED`। শুধু reconciled-period lock hook সক্রিয় থাকে। বিদ্যমান তথ্য থাকে | P10-14 |
| R10.52 | Sensitive মান log, audit diff, export, error `details`-এ কখনো নেই | P10-02 |

**Configuration:** Flag `Setting` model-এ (group `accounting`)। বাকিগুলো `acc_settings`-এ `SettingRegistry` দিয়ে। Sensitive key-এর বদলে P1-04-এর change request, `ChangeApplier` prefix `bank:`। কোডে বাঁধা মান নেই।

| Key | উদ্দেশ্য ও Business Impact | Option ও Validation | Default Policy | Scope | বাধ্যতামূলক কি | কে বানায়, বদলায়, অনুমোদন করে | বদলের প্রভাব | Lock ও Audit |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `flag:bank.enabled` | Phase 10 চালু করে। ভুলে চালু হলে GL link অসম্পূর্ণ থাকলে ভুল match হয় | `true` বা `false`। চালুর শর্ত §২০.১৩ | `false` | Company | হ্যাঁ। শর্ত সবুজ না হলে চালু হয় না (`BNK_ENABLE_PRECONDITION`) | বানায় ও বদলায়: `accounting.settings.manage`, Accounting Admin। অনুমোদন: Maker-Checker, দ্বিতীয় admin | চালুর পর journal type `bank` active, নতুন import ও match চলে। আগের posted অপরিবর্তিত। বন্ধ করলে নতুন কাজ থামে, তথ্য ও sign-off-করা reconciliation-এর lock থাকে | বন্ধ করতে খোলা import, transfer, pending match না থাকা লাগে। Event `accounting.bank.flag_changed`, `acc_enablement_log`-এ আগে ও পরে ও কারণ |
| `flag:bank.cheque.enabled` | Cheque register ও lifecycle চালু করে (OD-52) | `true` বা `false`। `bank.enabled` চালু থাকা লাগে | `false` | Company | ঐচ্ছিক, OD-52 হ্যাঁ হলে | Accounting Admin। অনুমোদন: Maker-Checker | চালুর পর cheque book ও cheque তৈরি। বন্ধ করলে নতুন cheque নয়, বিদ্যমান register থাকে | Event `accounting.cheque.flag_changed`। বন্ধ করতে খোলা (issued, presented) cheque না থাকা লাগে |
| `bank.import.max_file_mb`, `bank.import.max_lines` | এক file-এর আকার সীমা। ভুল হলে memory চাপ বা বৈধ file বাতিল | পূর্ণসংখ্যা: file 1 থেকে 100, line 1,000 থেকে 500,000 | 20 ও 200,000 | System default, Company override | হ্যাঁ (default আছে) | `accounting.settings.manage`। অনুমোদন সাধারণ নিয়মে নয় | নতুন import-এ। চলমান import আগের সীমা ধরে চলে। আগের statement বদলায় না | Lock নয়। Event `accounting.bank.setting_changed` |
| `bank.import.balance_check`, `bank.import.continuity_policy` | Opening + line = closing ও আগের closing = opening যাচাই। নরম করলে ভাঙা statement ঢুকতে পারে | `block` বা `warn`; `block`, `warn` বা `allow` | `block`, `block` | Company | হ্যাঁ | বানায়: `accounting.settings.manage`। `block` থেকে নরম করা Maker-Checker (`bank.approve`) | নতুন import-এ। আগের statement-এর `continuity_status` বদলায় না। Override-করা statement-এ কারণসহ চিহ্ন | Lock নয়। Event `accounting.bank_statement.setting_changed`, আগে ও পরের মান |
| `bank.import.max_error_lines` | কয়টা line ত্রুটি থাকলেও file নেওয়া যাবে। শূন্য মানে এক ত্রুটিতেই পুরো file বাতিল | 0 থেকে 1,000 | `0` | Company | ঐচ্ছিক | `accounting.settings.manage` | নতুন import-এ। ত্রুটিযুক্ত line statement-এ ঢোকে না, batch-এর error তালিকায় থাকে | Lock নয়। Event `accounting.bank.setting_changed` |
| `bank.import.manual_review_required` | হাতে লেখা statement-এ দ্বিতীয় চোখ। বন্ধ থাকলে ভুল বা ভুয়া statement একজনেই ঢোকাতে পারে | `true` বা `false` | `true` | Company | হ্যাঁ | বানায়: `accounting.settings.manage`। `false` করতে Maker-Checker | নতুন manual statement-এ। আগের review অবস্থা বদলায় না | Event `accounting.bank_statement.setting_changed` |
| `bank.match.amount_tolerance`, `bank.match.amount_tolerance_pct` | Suggestion ও match কত পার্থক্য সহ্য করে। বড় হলে ভুল জোড়া ও লুকানো write-off ঝুঁকি | DECIMAL(20,4) ≥ 0 (currency ধরে), pct 0 থেকে 5 | `0.0000` ও `0` | Company, bank account override | হ্যাঁ | বানায়: `accounting.bank.manage`। বাড়াতে `accounting.bank.approve` | নতুন suggestion ও match-এ। আগের match-এর `policy_snapshot`-এ প্রযোজ্য tolerance সংরক্ষিত, বদলায় না | Lock নয়। Event `accounting.bank_match.setting_changed` |
| `bank.match.date_window_days` | Statement ও book তারিখের কত ফারাক সহ্য। ভুল হলে ভুল মাসের জোড়া | 0 থেকে 30 | `3` | Company, bank account override | হ্যাঁ | `accounting.bank.manage` | নতুন suggestion-এ। আগের match অপরিবর্তিত (`policy_snapshot`) | Event `accounting.bank_match.setting_changed` |
| `bank.match.suggest_min_score`, `bank.match.score_weights` | কত score-এ suggestion দেখায় ও weight ভাগ। ভুল হলে কাজের suggestion হারায় বা আবর্জনা বাড়ে | min 1 থেকে 100। Weight JSON: amount, reference, name, date, uniqueness, যোগফল 100 | `70`। 40, 30, 15, 10, 5 | Company | হ্যাঁ | `accounting.bank.manage` | নতুন suggestion-এ। Match-এ score সংরক্ষিত, পুনর্গণনা হয় না | Event `accounting.bank_match.setting_changed` |
| `bank.match.auto_confirm_enabled`, `bank.match.auto_confirm_min_score` | মানুষ ছাড়া match confirm। ভুল হলে ভুল জোড়া নিঃশব্দে confirm হয় (ledger নয়, শুধু match) | `true` বা `false`। Score 90 থেকে 100 | `false`। `100` | Company, bank account | ঐচ্ছিক | চালু করতে `accounting.bank.manage` ও Maker-Checker | চালুর পর নতুন run-এ শুধু exact reference ও exact amount। আগের match অপরিবর্তিত। Auto match `method = auto` চিহ্নিত | Event `accounting.bank_match.auto_confirmed` (প্রতি batch)। বন্ধ থাকলে job কিছু confirm করে না |
| `bank.rule.max_drafts_per_run` | এক run-এ কয়টা Draft তৈরি। ভুল rule যেন হাজার হাজার Draft না বানায় | 1 থেকে 20,000 | `5000` | Company | হ্যাঁ | `accounting.bank_rule.manage` | নতুন run-এ। তৈরি Draft অপরিবর্তিত। সীমা ছুঁলে run থামে, বাকি line `unmatched` | Event `accounting.bank_rule.applied` (সংখ্যা সহ) |
| `bank.account.charges_account_id`, `bank.account.interest_income_account_id`, `bank.account.unidentified_receipts_account_id` | Rule-এর default counter account। অচেনা receipt suspense-এ যায়। ভুল হলে খরচ বা আয় ভুল account-এ | Account id। Active, posting allowed, bank ও cash GL নয়। Unidentified receipt: liability বা suspense type | বাধ্যতামূলক, Admin দেবে | Company | হ্যাঁ, `bank.enabled` চালুর শর্ত | বানায়: `accounting.settings.manage`। অনুমোদন: Maker-Checker (mapping-এর মতো সংবেদনশীল) | নতুন Draft-এ। আগের Draft ও posted অপরিবর্তিত | যে account এখানে ব্যবহৃত তা deactivate হয় না (P1-04 ব্যবহার-বাধা)। Event `accounting.bank.setting_changed` |
| `bank.transfer.use_in_transit`, `bank.transfer.in_transit_account_id` | Transfer কি দুই ধাপে (বের, ঢোকা) যাবে। In-transit account cash-equivalent না হলে cash flow-এ ভুয়া নড়াচড়া | `true` বা `false`। Account asset, `is_cash_equivalent = 1`, bank GL নয় | `true`। বাধ্যতামূলক, Admin দেবে | Company | হ্যাঁ | `accounting.settings.manage`। অনুমোদন: Maker-Checker | নতুন transfer-এ। আগের transfer ও journal অপরিবর্তিত | যে account ব্যবহৃত তা deactivate হয় না। Event `accounting.bank_transfer.setting_changed` |
| `bank.transfer.fx_difference_account_id`, `bank.transfer.rate_type` | Currency ভিন্ন transfer-এ rate ও আসল প্রাপ্তির ফারাক কোথায় যাবে | Account active, P&L type। `rate_type`: Phase 7-এর rate type code | বাধ্যতামূলক, Admin দেবে। `spot` | Company | হ্যাঁ, বহু-currency bank account থাকলে | `accounting.settings.manage`। অনুমোদন: Maker-Checker | নতুন transfer-এ। আগের transfer অপরিবর্তিত, ব্যবহৃত rate transfer row-এ সংরক্ষিত | Event `accounting.bank_transfer.setting_changed` |
| `bank.recon.signoff_levels` | Sign-off-এ কয় ধাপ। কম হলে দুর্বল নিয়ন্ত্রণ | `1` বা `2`। উভয়ে প্রস্তুতকারী ≠ sign-off-কারী | `1` | Company, bank account override | হ্যাঁ | বানায়: `accounting.settings.manage`। `2` থেকে `1` Maker-Checker | নতুন reconciliation-এ। চলমানটি তৈরির সময়ের স্তর ধরে চলে (`level_snapshot`) | Event `accounting.bank_recon.setting_changed` |
| `bank.recon.require_unrecorded_explained` | Sign-off-এ ব্যাংকে আছে কিন্তু খাতায় নেই এমন line-এ ব্যাখ্যা লাগবে কি। বন্ধ থাকলে অব্যাখ্যাত ফারাক চাপা পড়ে | `true` বা `false` | `true` | Company | হ্যাঁ | `accounting.settings.manage`। বন্ধ করতে Maker-Checker | নতুন sign-off-এ। আগের snapshot অপরিবর্তিত | Event `accounting.bank_recon.setting_changed` |
| `bank.recon.max_unrecorded_age_days` | Unmatched বা outstanding item কত দিন পরে alert। ভুল হলে পুরোনো ফারাক চোখ এড়ায় | 1 থেকে 365 | `30` | Company | হ্যাঁ | `accounting.bank.manage` | নতুন alert-এ। আগের alert state বদলায় না | Lock নয়। Event `accounting.bank.setting_changed` |
| `bank.lock.reconciled_posting` | Reconciled তারিখে bank GL-এ নতুন journal আটকাবে কি। `warn` হলে sign-off-করা হিসাব নিঃশব্দে নড়ে | `block` বা `warn` | `block` | Company | হ্যাঁ | বানায়: `accounting.settings.manage`। `warn`-এ নামাতে Maker-Checker (`bank.approve`) | নতুন Submit ও Post-এ। আগের reconciliation ও snapshot অপরিবর্তিত। Warn-এ ঢোকা journal reopen-এর কারণ হিসেবে দেখায় | Event `accounting.bank_recon.lock_policy_changed`, আগে ও পরে। UI-তে বর্তমান ও আগের মান |
| `bank.petty.receipt_required_over`, `bank.petty.count_frequency_days`, `bank.petty.shortage_account_id`, `bank.petty.overage_account_id` | Petty cash voucher-এ receipt-সীমা, গণনার ব্যবধান, কম বা বেশির account | Amount ≥ 0। দিন 1 থেকে 90। Account active, P&L type | `0` (সব voucher-এ receipt)। `7`। বাধ্যতামূলক, Admin দেবে | Company, fund override | হ্যাঁ, petty cash চালু করলে | `accounting.petty_cash.manage`। Account বদলে Maker-Checker | নতুন voucher ও count-এ। আগের voucher ও Draft অপরিবর্তিত | Event `accounting.petty_cash.setting_changed` |
| `bank.cheque.stale_days` | Cheque তারিখের কত দিন পরে stale। ভুল হলে বৈধ cheque stale বা পুরোনোটা চালু থাকে | 30 থেকে 365 | `180` (OD-52) | Company | হ্যাঁ, cheque চালু থাকলে | `accounting.cheque.manage`। অনুমোদন: Maker-Checker | নতুন ও এখনো issued cheque-এ। আগে stale হওয়া বদলায় না | Event `accounting.cheque.setting_changed` |
| `bank.sensitive.reveal_requires_reason`, `bank.sensitive.reveal_rate_limit_per_hour` | পূর্ণ account নম্বর দেখার কারণ বাধ্যতামূলক ও সীমা। শিথিল হলে তথ্য ফাঁসের ঝুঁকি | `true` বা `false`। সীমা 1 থেকে 200 | `true`। `20` | Company | হ্যাঁ | `accounting.settings.manage`। শিথিল করতে Maker-Checker | নতুন reveal-এ। আগের reveal log অপরিবর্তিত | Event `accounting.bank.setting_changed`। প্রতিটা reveal `accounting.bank_account.sensitive_viewed` |
| `bank.report.cashbook_scope` | Cash book শুধু bank, শুধু petty নাকি দুটোই। ভুল হলে নগদ ঘাটতি চোখে পড়ে না | `bank`, `petty`, `both` | `both` | Company | হ্যাঁ | `accounting.bank.manage` | শুধু নতুন report run-এ। সংরক্ষিত export অপরিবর্তিত | Lock নয়। Event `accounting.bank.setting_changed` |

**২০.৬ থেকে ২০.১৪ আলাদা tab-এ:** মূল tab-এর block সীমা শেষ, তাই Operational Workflow, Dependencies, WBS, Testing, Security ও Audit, Deliverable, Acceptance Criteria, Entry ও Exit, Open Decisions এই tab-এ: Phase 10 ২০.৬ থেকে ২০.১৪ (Workflow থেকে Open Decisions)

## ২১. Phase 11: Fixed Asset Accounting

### ২১.১ Objective ও Scope

**Objective.** ERPFlow-এর Asset module asset-এর operational মালিক থাকে। Accounting শুধু তার একটা accounting register রাখে। এই register-এ প্রতিটা asset-এর cost, accumulated depreciation, impairment, revaluation আর carrying value থাকে। প্রতিটা বদল আসে একটা register transaction হয়ে, আর প্রতিটা transaction-এর জন্য একটা **Draft** journal তৈরি হয়। Posted হওয়ার পরেই register-এর balance বদলায়। তাই register-এর মোট সবসময় GL-এর control account-এর সমান থাকে (RC11)।

Phase 4-এর Asset write-off pilot এই register-এর একটা transaction type হয়ে যায়। Depreciation, disposal, impairment, revaluation, transfer, CWIP সবই এই একই কাঠামোতে চলে।

**Capability flag:** `fixed_assets.enabled`। Platform Setting-এ, company-ভিত্তিক, default বন্ধ। বন্ধ থাকলে Phase 4-এর write-off pilot আগের মতোই চলে।

| ভাগ | বিষয় | কেন |
| --- | --- | --- |
| ভেতরে | Accounting register (`acc_fa_assets`) যা Asset module-এর mirror, চাবি `(source_type, source_id)` (G8) | Asset-এর data Accounting বদলায় না। Accounting নিজের হিসাব রাখে |
| ভেতরে | Asset category ও account mapping (cost, accumulated depreciation, depreciation expense, gain, loss, impairment, revaluation surplus), default method, life, residual | Control account ও RC11 category ধরে মেলে |
| ভেতরে | Asset event গ্রহণ: acquisition, capitalization, cost adjustment, transfer, disposal, write-off (Phase 4 inbox ও Phase 14 rule দিয়ে) | Operational ঘটনা থেকে accounting-এর Draft |
| ভেতরে | Capitalization threshold, CWIP ও CWIP থেকে asset-এ transfer | Cost কখন asset হয় তার নিয়ম আলাদা |
| ভেতরে | `DepreciationEngine`: straight-line, declining balance; period ধরে run, preview, catch-up, change of estimate | Depreciation সবচেয়ে বেশি বার চলা, সবচেয়ে ভুলের ঝুঁকির কাজ |
| ভেতরে | Impairment ও তার reversal, revaluation (model OD-59) | Carrying value বদলের অন্য দুই কারণ |
| ভেতরে | `DisposalService`: full ও partial disposal, proceeds, gain বা loss | Derecognition-এর একটাই পথ |
| ভেতরে | Cost center ও dimension transfer, category reclassification, company-র মধ্যে transfer-এর hook (Phase 15) | Asset-এর অবস্থান ও ধরন বদলায় |
| ভেতরে | Register report: asset register, depreciation schedule, cost ও accumulated depreciation roll-forward, disposal, CWIP | Audit ও management-এর প্রথম প্রশ্ন |
| ভেতরে | Reconciliation RC11, Phase 12 close checklist-এ run-এর ক্রম, period lock আচরণ | Close-এর আগে নিশ্চয়তা |
| বাইরে | Asset data (নাম, serial, location, custodian, maintenance, status) বদলানো | G8: মালিক Asset module। Accounting কখনো Asset-এর table-এ লেখে না |
| বাইরে | Asset module-এর outbox-এ নতুন field বসানো | এটা Asset module-এর কাজ। এখানে শুধু requirement লেখা হয়েছে (২১.৪.৩) |
| বাইরে | Parallel book ও tax depreciation book | G2 অনুযায়ী একটাই ledger। OD-58 |
| বাইরে | Lease asset (right-of-use), investment property, biological asset, inventory | আলাদা standard ও আলাদা সিদ্ধান্ত। Phase 11-এর register পরে বাড়তে পারে, expand-only |
| বাইরে | Asset-এর physical verification ও maintenance cost | Operational কাজ |

### ২১.২ Functional Requirements

প্রতিটা requirement-এর শেষে R নম্বর থাকলে সেটা ২১.৫-এর Business Rule।

**ক. Category ও account mapping**

- ক১. প্রতিটা company-তে Asset category-র জন্য `acc_fa_categories`। প্রতিটা category-তে নয়টা account slot (cost, accumulated depreciation, accumulated impairment, depreciation expense, disposal gain, disposal loss, impairment loss, revaluation surplus, CWIP) আর default method, life, residual rate, threshold (R11.1, R11.2)।
- ক২. Category-র cost ও accumulated depreciation account ব্যবহার শুরু হলে lock হয়। বদলাতে হলে নতুন category ও reclassification (R11.3)।
- ক৩. Control account-এ `acc_accounts.fa_category_id` বসে। Manual journal সেখানে লেখা আটকানো যায় (R11.4)।

**খ. Register ও sync**

- খ১. Asset module-এর প্রতিটা asset Accounting-এ একটা `acc_fa_assets` row পায়, চাবি `(source_type, source_id)`। নাম, code, category-র ref mirror হয়। Accounting-এর নিজের field (cost, balance, term) Accounting-এর (R11.5)।
- খ২. Asset-এ category বা cost center বদলালে mirror আপডেট হয়, কিন্তু accounting প্রভাব আসে শুধু transfer বা reclassification transaction দিয়ে (R11.6)।
- খ৩. Cutover-এর সময় বিদ্যমান asset opening balance হিসেবে লোড হয়। GL-এর সাথে না মিললে flag চালু হয় না (R11.7)।

**গ. Event গ্রহণ**

- গ১. Asset থেকে ছয় রকম event: acquisition, capitalization, cost adjustment, transfer, disposal, write-off। প্রতিটা Phase 4 inbox দিয়ে আসে, identity `(company_id, source_type, source_id, event_type)` (R11.8)।
- গ২. প্রতিটা event থেকে ঠিক একটা register transaction আর একটা Draft journal। Event দ্বিতীয়বার এলে নতুন কিছু তৈরি হয় না (R11.9)।
- গ৩. Event-এ `effective_date`, `schema_version`, funding side, proceeds, carrying value as read থাকা লাগবে। না থাকলে event আটকে থাকে, অনুমানে চলে না (R11.10)।

**ঘ. Capitalization ও CWIP**

- ঘ১. Threshold-এর নিচের cost সরাসরি expense, register-এ `expensed` হিসেবে নথিভুক্ত (R11.11)।
- ঘ২. CWIP project-এ cost জমে। Asset in-service হলে এক বা একাধিক asset-এ ভাগ হয়ে যায়। ভাগের যোগফল CWIP-র সমান (R11.12)।

**ঙ. Depreciation**

- ঙ১. Method: straight-line, declining balance। Sum-of-years ও units-of-production OD-61। Method, life, residual `acc_fa_asset_terms`-এ version করা (R11.13)।
- ঙ২. প্রথম period convention configurable: full month, mid-month, daily, next month (R11.14)।
- ঙ৩. Period ধরে একটাই run। আগে preview, তারপর run। Re-run আটকানো। দেরিতে যোগ হওয়া asset-এর catch-up (R11.15 থেকে R11.18)।
- ঙ৪. Run `JournalService` দিয়ে journal type `depreciation`-এর **Draft** বানায়, category বা grouping ধরে। কখনো নিজে post করে না (R11.19)।
- ঙ৫. Change of estimate শুধু prospective (R11.20)।

**চ. Impairment ও revaluation**

- চ১. Impairment loss ও তার reversal, reversal-এর ceiling সহ (R11.21)।
- চ২. Revaluation চালু থাকলে (OD-59) surplus equity-তে (R11.22)।

**ছ. Disposal, transfer, retirement**

- ছ১. Full ও partial disposal। Disposal-এর তারিখ পর্যন্ত depreciation, proceeds (receivable বা bank), gain বা loss (R11.23, R11.24)।
- ছ২. Cost center ও dimension transfer, category reclassification, company-র মধ্যে transfer-এর hook (R11.25, R11.26)।
- ছ৩. পুরো depreciated asset-এর retirement (R11.27)।

**জ. Report ও control**

- জ১. Asset register, depreciation schedule, roll-forward, disposal report, CWIP report, প্রতিটা as-of date ধরে (R11.28)।
- জ২. RC11: register মোট = GL control account, category ধরে (R11.29)।
- জ৩. Phase 12 close checklist-এ FA task-এর ক্রম (R11.30)।

### ২১.৩ Technical Requirements

সব service company context সহ চলে। সব job `AccountingJob` (P1-12) ধরে। নতুন queue `accounting-fa`। সব টাকা `DECIMAL(20,4)`, rate `DECIMAL(20,10)`, PHP-তে decimal library।

| Component | কাজ | নিয়ম |
| --- | --- | --- |
| `FaCategoryService` | Category ও account slot, account type যাচাই, lock | Slot-এর account type ঠিক না হলে `FA_ACCOUNT_TYPE_INVALID`। ব্যবহার শুরু হলে cost ও accumulated depreciation slot lock |
| `AssetRegisterService` | Mirror sync, opening load, term version, estimate change | Asset-এর table শুধু পড়ে। লেখে শুধু `acc_fa_*` |
| `FaEventRouter` | Phase 4 inbox-এর asset event নিয়ে handler বাছে | Handler নির্বাচন `event_type` ধরে। Phase 14 চালু হলে rule-ও দেখে (OD-64a) |
| `FaDraftBuilder` | Register transaction থেকে journal spec | শুধু `JournalService` ডাকে। `acc_journals.status` কখনো ছোঁয় না |
| `FaApplyService` | Journal posted হলে register balance বদলায় | Posted না হলে কিছুই বদলায় না। নিজের table-এ লেখে |
| `DepreciationEngine` | `preview`, `run(company, periodId)`, `calculate(asset, period)` | Calculation pure function। Run job transaction সামলায় |
| `FaImpairmentService`, `FaRevaluationService` | Impairment, reversal, revaluation | Ceiling `DepreciationEngine::replay` থেকে |
| `DisposalService` | Full, partial disposal, write-off, retirement | Disposal-এর তারিখ পর্যন্ত depreciation এই service হিসাব করে |
| `FaTransferService` | Cost center, dimension, category, company transfer | Company transfer Phase 15 hook, না থাকলে `FA_INTERCOMPANY_NOT_ENABLED` |
| `CwipService` | CWIP project, cost, capitalization | যোগফল না মিললে `FA_CWIP_ALLOCATION_MISMATCH` |
| `FaReportService` | পাঁচটা report ও export | Read-only, replica-friendly query |
| `FaReconciler` | RC11 (P3-13 framework-এ) | `acc_reconciliation_runs`-এ ফল |
| `FaCloseTaskProvider` | Phase 12 close checklist-এর task অবস্থা | Phase 12 এটা পড়ে। Phase 11 checklist-এ লেখে না |

**Job।**

| Job | কাজ | Idempotency |
| --- | --- | --- |
| `FaMirrorSyncJob` | Asset থেকে mirror ও category ref আনে | `(company_id, source_type, source_id)` upsert, `source_hash` একই হলে skip |
| `FaEventPumpJob` | Pending asset event inbox থেকে তুলে router-এ দেয় | Inbox identity ও `uq` on transaction source |
| `FaApplyJob` | Posted journal খুঁজে register apply করে | Transaction `state` ও `applied_at` |
| `FaDepreciationRunJob` | Run-এর chunk হিসাব ও Draft তৈরি | `uq` on run (company, period) ও journal source key |
| `FaReconcileJob` | RC11 নির্ধারিত সময়ে | Run নম্বর ধরে |

**Concurrency।**

- একটা asset-এর একসাথে একটাই pending (`planned`, `pending_approval`, `draft`) balance-সম্পর্কিত transaction। DB-তে generated column `open_key` ও unique index। ফল: `FA_ASSET_BUSY`।
- Asset row lock ধরে `JournalService` ডাকা নিষেধ। তাই optimistic `version`, আর lock শুধু `FaApplyService`-এ (asset row `FOR UPDATE`, id ক্রমে, সব global lock-এর শেষে, নিজের table)।
- Depreciation run-এর ভেতরে journal তৈরি আলাদা transaction-এ। প্রতিটার source key ঠিক করা, তাই retry নিরাপদ।
- Global lock ক্রম বদলায় না: journal, approval request, account `FOR SHARE` (id ক্রমে), period `FOR SHARE`, voucher counter। তারপর শুধু Phase 11-এর নিজের row।

**Failure handling।**

- Run chunk-ভিত্তিক (default 500 asset)। কোনো chunk ব্যর্থ হলে run `failed`, আগের chunk-এর Draft থাকে, resume করা যায় (একই run, একই fingerprint)।
- Journal তৈরি ব্যর্থ হলে transaction `planned` থাকে, retry। বারবার ব্যর্থ হলে alert (P5-03 `acc_alert_state`)।
- Event ক্রমভঙ্গ হলে inbox-এ `pending`, কারণ `FA_EVENT_OUT_OF_ORDER`। আগের event আসা মাত্র আবার চেষ্টা।
- Register ও GL মিলে না গেলে RC11 `fail`, নতুন run ও disposal আটকায় না, কিন্তু Close checklist সবুজ হয় না।

**Performance।** লক্ষ্য (OD-119 ছাড়া baseline): ৫০,০০০ asset-এর run ১০ মিনিটের মধ্যে, preview ২ মিনিটের মধ্যে, roll-forward report ১০০,০০০ asset-এ ১০ সেকেন্ডের মধ্যে। Index-এর তালিকা Pack-এ (P11-02, P11-06, P11-12)। সব report keyset pagination।

**Security।** সব endpoint company scope ও permission দিয়ে। Mirror-এর `source_*` ব্যবহারকারী বদলাতে পারে না। Proceeds ও disposal-এর buyer তথ্য সংবেদনশীল, log-এ masked। Export `acc_report_exports`-এ রেকর্ড।

### ২১.৪ Data Model ও API

**২১.৪.১ Table (সব `acc_fa_` prefix, expand-only)।** পুরো column spec Pack section ১৬-এ।

| Table | মূল column | উদ্দেশ্য |
| --- | --- | --- |
| `acc_fa_categories` | code, name, cost\_account\_id, accum\_dep\_account\_id, accum\_impairment\_account\_id, dep\_expense\_account\_id, gain\_account\_id, loss\_account\_id, impairment\_loss\_account\_id, revaluation\_surplus\_account\_id, cwip\_account\_id, low\_value\_expense\_account\_id, default\_method, default\_life\_months, default\_residual\_rate, capitalization\_threshold, first\_period\_convention, measurement\_model, is\_depreciable, locked\_at | Category ও mapping |
| `acc_fa_assets` | source\_type, source\_id, asset\_code, category\_id, status, acquisition\_date, in\_service\_date, original\_cost, accumulated\_depreciation, accumulated\_impairment, revaluation\_adjustment, carrying\_value, residual\_value, cost\_center\_id, dimension\_set, parent\_asset\_id, quantity, source\_hash, version | Accounting register |
| `acc_fa_asset_terms` | asset\_id, version\_no, effective\_from, method, life\_months, residual\_value, base\_amount, remaining\_months, declining\_rate, reason | Method ও life-এর ইতিহাস (append-only) |
| `acc_fa_transactions` | asset\_id, txn\_type, effective\_date, period\_id, d\_cost, d\_accum\_dep, d\_accum\_impairment, d\_reval, state, journal\_id, source\_type, source\_id, open\_key | সব register লেনদেন |
| `acc_fa_depreciation_runs` | period\_id, status, fingerprint, active\_key, totals, started\_by | Period-ভিত্তিক run |
| `acc_fa_depreciation_lines` | run\_id, asset\_id, line\_kind, amount, basis snapshot, transaction\_id | Asset-ভিত্তিক হিসাব |
| `acc_fa_depreciation_journals` | run\_id, group\_key, journal\_id, amount, line\_count | Grouping অনুযায়ী Draft |
| `acc_fa_disposals` | asset\_id, disposal\_type, disposal\_date, fraction, proceeds, carrying\_value, gain\_loss, proceeds\_mode, journal\_id | Disposal ও gain-loss |
| `acc_fa_cwip` | code, name, status, target\_category\_id, accumulated\_cost, capitalized\_on | CWIP project |
| `acc_fa_cwip_lines` | cwip\_id, cost\_date, amount, source\_type, source\_id, transaction\_id | CWIP cost ও capitalization |

`acc_fa_books` **তৈরি হয় না**, কারণ baseline G2 (একটাই ledger)। OD-58 "একাধিক book" বললে expand migration-এ আলাদা table আসবে। Phase 1-এর `acc_journals`-এ `ledger_id` লাগার প্রশ্ন OD-109 (master)-এর।

**বিদ্যমান table-এ expand।** শুধু `acc_accounts.fa_category_id` (nullable, composite FK `(company_id, fa_category_id)` → `acc_fa_categories (company_id, id)`)। আর কিছু নয়। `acc_journals`-এ নতুন column নেই: journal type `depreciation` (Phase 6-এ seed করা, `owner_phase` 11), আর বাকিগুলো `event_generated` ও `source_type`, `source_id`, `source_event` দিয়ে।

**Journal source চাবি।**

| উৎস | `source_type` | `source_id` | `source_event` | journal\_type |
| --- | --- | --- | --- | --- |
| Asset event (acquisition, capitalization, cost\_adjustment, transfer, disposal, write\_off) | `asset_finance_event` | inbox-এর event id (Phase 4-এর মতো) | `fa_<event_type>` | `event_generated` |
| Depreciation group | `fa_depreciation_journal` | `acc_fa_depreciation_journals.id` | `depreciation` | `depreciation` |
| Impairment, reversal, revaluation, reclassification, CWIP capitalization, estimate-নির্ভর adjustment | `fa_transaction` | `acc_fa_transactions.id` | `fa_<txn_type>` | `event_generated` |

Journal number series: default `JV` (P6-03 seed)। Admin চাইলে `depreciation` type-এ আলাদা series (যেমন `DEP`) assignment resolver দিয়ে বসাতে পারে। Gapless নিয়ম Phase 6-এর মতোই।

**২১.৪.২ API।** Prefix `/api/accounting/fixed-assets`। সব write-এ `Idempotency-Key`।

| Endpoint | কাজ | Permission |
| --- | --- | --- |
| `GET/POST/PUT /categories`, `GET /categories/{id}/usage` | Category ও mapping | view, manage |
| `GET /assets`, `GET /assets/{id}`, `GET /assets/{id}/transactions`, `GET /assets/{id}/schedule` | Register ও projected schedule | view |
| `POST /assets/opening-load`, `POST /assets/opening-load/{id}/commit` | Opening balance (dry-run, তারপর commit) | manage + approve |
| `POST /assets/{id}/estimate-changes` | Change of estimate | manage, তারপর approve |
| `POST /assets/{id}/impairments`, `POST /impairments/{id}/reversal` | Impairment ও reversal | manage, approve |
| `POST /assets/{id}/revaluations` | Revaluation | manage, approve |
| `POST /assets/{id}/transfers`, `POST /assets/{id}/reclassifications` | Transfer, reclassification | manage, approve |
| `POST /assets/{id}/disposals`, `GET /disposals` | Disposal | dispose, approve |
| `POST /depreciation/preview`, `POST /depreciation/runs`, `GET /depreciation/runs`, `GET /depreciation/runs/{id}`, `POST /depreciation/runs/{id}/cancel` | Run | depreciate |
| `GET/POST /cwip`, `POST /cwip/{id}/costs`, `POST /cwip/{id}/capitalize` | CWIP | manage |
| `GET /events/pending`, `POST /events/{inboxId}/actions` | FA-সংক্রান্ত event (action Phase 4-এর মতো) | ops |
| `GET /reports/{register,depreciation-schedule,rollforward,disposals,cwip}`, `POST /reports/{name}/export` | Report | view, export |
| `POST /reconcile/rc11`, `GET /readiness` | RC11 ও enable precondition | view, manage |

**২১.৪.৩ Asset module-এর উপর requirement (এখানে কিছু বদলানো হচ্ছে না)।** নিচের field Asset-এর `asset_finance_events` outbox-এ লাগবে। কাজটা Asset module-এর। Accounting এগুলো ধরে নিয়ে অ্যাডাপ্টার লিখবে, আর না থাকলে event আটকে রাখবে।

| Field | কোন event-এ | কেন | না থাকলে |
| --- | --- | --- | --- |
| `effective_date` DATE | সব | Accounting period ও depreciation-এর তারিখ। (Phase 4 শুধু write-off-এর জন্য OD-21 (ক)-তে চেয়েছিল, এখন সব event) | `FA_EVENT_FIELD_MISSING`, event `pending` |
| `schema_version` | সব | Phase 11 `schema_version = 2` চায়। বিদ্যমান মান 1 | Version 1 হলে শুধু write-off পুরোনো পথে, বাকি `FA_EVENT_SCHEMA_UNSUPPORTED` |
| `event_seq` (asset-ভিত্তিক বাড়ন্ত নম্বর) | সব | Out-of-order ধরা | `effective_date`, তারপর `id` ধরে ক্রম। ঝুঁকি বেশি |
| `funding_side` (clearing, payable, bank, cash, cwip, equity, other) ও `funding_ref` | acquisition, capitalization, cost\_adjustment | Credit দিক কোন account | OD-62a-র default (clearing) |
| `amount`, `currency`, `quantity` | acquisition, cost\_adjustment, disposal | Cost ও partial disposal | `FA_EVENT_FIELD_MISSING` |
| `in_service_date` | capitalization | Depreciation শুরু | `effective_date` ধরা হয়, warning |
| `category_ref` | acquisition, capitalization | Category বাছা | `FA_CATEGORY_MISSING`, event `pending` |
| `cost_center_ref`; `from_cost_center_ref`, `to_cost_center_ref` | acquisition, transfer | Dimension | Mirror-এর মান |
| `from_company_id`, `to_company_id` | transfer | Company-র মধ্যে transfer (OD-64) | Cost center transfer ধরা হয় |
| `proceeds` (DECIMAL), `buyer_ref` (`source_type`, `source_id`) | disposal | Gain-loss ও receivable | Proceeds শূন্য ধরে write-off, warning নয়, **block**: বিক্রি হলে proceeds ছাড়া disposal নয় (`FA_EVENT_FIELD_MISSING`) |
| `carrying_value_as_read` | disposal, write\_off, transfer | Asset-এর নিজের দেখা book value। Accounting-এর সাথে তুলনা, তফাত `FA_CARRYING_MISMATCH` (warning, Accounting-এর মান জেতে) | তুলনা বাদ |
| `reason_code` | write\_off | Loss-এর কারণ (scrap, theft, damage) | `unspecified` |
| `supersedes_event_id` | সব | ভুল event সংশোধন। নতুন event আগেরটাকে বাতিল করে না, Accounting reversal বা adjustment চালায় | সংশোধন শুধু মানুষের হাতে |
| Acquisition event নিজে outbox-এ লেখা | acquisition | আজ production-এ acquisition লেখা হয় না | Acquisition Accounting-এ UI দিয়ে (opening/manual register entry), সীমিত |
| Acknowledger | সব | `AssetFinanceEventAcknowledger` (P4-07) সব event type-এ `accepted` বসায় | Asset-এর event `pending` দেখায় |

একটা `FaAssetEventContract` test fixture (Pack P11-03) এই field-গুলোর নমুনা JSON রাখে, Asset-এর দল যেন একই নমুনায় test লেখে।

### ২১.৫ Business Rules ও Configuration

**Business Rules।** প্রতিটা rule-এর নিশ্চিত করার জায়গা ডানে।

| # | নিয়ম | কে নিশ্চিত করে |
| --- | --- | --- |
| R11.1 | প্রতিটা category-তে cost, accumulated depreciation, depreciation expense, gain, loss account বাধ্যতামূলক। বাকি slot (impairment, revaluation surplus, CWIP, low value expense, disposal clearing) সংশ্লিষ্ট feature ব্যবহারের সময় বাধ্যতামূলক। Account type: cost, accumulated depreciation, accumulated impairment, CWIP হলো asset। Expense ও loss slot হলো expense। Gain হলো revenue। Surplus হলো equity | `FaCategoryService`, `FA_ACCOUNT_TYPE_INVALID` |
| R11.2 | Category-র default method, life, residual rate শুধু নতুন asset-এর প্রথম term-এ কপি হয়। পরে category বদলালে চলতি asset-এর term বদলায় না | Term-এ কপি, `acc_fa_asset_terms` append-only |
| R11.3 | Category-র cost ও accumulated depreciation account একবার কোনো applied transaction-এ ব্যবহার হলে lock। বদলাতে হলে নতুন category ও reclassification | `locked_at`, `FA_CATEGORY_LOCKED` |
| R11.4 | `fa_category_id` বসানো account-এ `manual` ও `opening` ধরনের journal লেখা `fixed_assets.control_account_manual_posting` অনুযায়ী (default block)। Phase 11-র নিজের journal ও `adjustment` (override সহ) ছাড়া | JournalService validator-এ নতুন নিয়ম (P2-03), `FA_CONTROL_ACCOUNT_MANUAL` |
| R11.5 | Mirror field (নাম, code, category ref, cost center ref) Asset থেকে আসে, Accounting বদলায় না। Accounting-এর field (cost, balance, term, status) Asset-এ যায় না | `AssetRegisterService`, column আলাদা, কোড review ও static test |
| R11.6 | Asset-এ category বা cost center বদলালে mirror বদলায়, কিন্তু accounting প্রভাব শুধু transfer বা reclassification transaction দিয়ে | `FA_SYNC_CONFLICT` যখন mirror ও register অমিল |
| R11.7 | Opening load একটা dry-run, তারপর commit। Commit-এর আগে category-ভিত্তিক মোট GL control account balance-এর সমান না হলে commit হয় না। Opening load journal বানায় না (GL-এ ব্যালান্স আগে থেকেই আছে) | `FA_OPENING_MISMATCH`, RC11 |
| R11.8 | Asset event-এর identity `(company_id, source_type, source_id, event_type)`। একই identity দ্বিতীয়বার এলে `EVT_DUPLICATE` (Phase 4) | Phase 4 inbox unique |
| R11.9 | এক event থেকে এক register transaction ও এক Draft journal। Transaction-এর unique key `(company_id, source_type, source_id, txn_type)`। Journal-এর `source_active` একই কাজ করে | DB unique, JournalService idempotent |
| R11.10 | Event-এ `effective_date`, `schema_version`, ধরন অনুযায়ী দরকারি field (২১.৪.৩) না থাকলে event `pending` থাকে। অনুমান করে ভরা হয় না | `FA_EVENT_FIELD_MISSING` |
| R11.11 | Cost threshold-এর নিচে হলে asset হয় না। Cost সরাসরি expense, register-এ `expensed` row (নথির জন্য)। Threshold তুলনা acquisition-এর সময় এক item-এর cost-এ (OD-60a) | `FaDraftBuilder`, transaction-এ `threshold_applied` |
| R11.12 | CWIP থেকে capitalize করার সময় ভাগের যোগফল CWIP-র মোট cost-এর সমান। CWIP-তে থাকা সময়ে depreciation হয় না | `FA_CWIP_ALLOCATION_MISMATCH` |
| R11.13 | Method, life, residual, declining rate প্রতিটা `acc_fa_asset_terms` row-এ। পুরোনো row কখনো বদলায় না, নতুন row যোগ হয় | `Schema::appendOnly()` |
| R11.14 | প্রথম period convention চারটা: full\_month, mid\_month, daily, next\_month। Term-এ convention কপি হয়, তাই setting বদলালে পুরোনো asset-এর হিসাব বদলায় না | Term snapshot |
| R11.15 | প্রতি company ও period-এ সক্রিয় run একটা। Cancelled run slot ছাড়ে | `uq (company_id, active_key)`, `FA_RUN_EXISTS` |
| R11.16 | Run শুরুর আগে preview। Run তৈরি হয় preview-এর `fingerprint` দিয়ে। এর মধ্যে asset, term বা period বদলালে `FA_RUN_PREVIEW_STALE` | Fingerprint hash |
| R11.17 | Draft অবস্থায় run বাতিল করা যায় (journal `cancelled`, সব line `void`)। কোনো journal posted হয়ে গেলে বাতিল নয়, শুধু Reversal | `FA_RUN_NOT_CANCELLABLE` |
| R11.18 | দেরিতে যোগ হওয়া asset-এর ছুটে যাওয়া period-এর depreciation চলতি run-এ `catch_up` line হয় (setting `late_addition_policy`)। বন্ধ period-এ কিছু ফেরত বদলায় না | `line_kind = 'catch_up'` |
| R11.19 | Depreciation journal সবসময় Draft, journal type `depreciation`। কোনো auto-post নেই (G9) | `JournalService` ছাড়া পথ নেই |
| R11.20 | Change of estimate শুধু prospective: নতুন term row, বাকি depreciable amount বাকি life-এ। অতীত period পুনর্লিখন নয় | `FA_ESTIMATE_RETROACTIVE` |
| R11.21 | Impairment loss বর্তমান carrying value-এর বেশি হতে পারে না। Reversal-এর উর্ধ্বসীমা: impairment না হলে যে carrying value থাকত তার থেকে বর্তমান carrying value বাদ, আর মোট জমা impairment-এর ছোটটা | `FA_IMPAIRMENT_EXCEEDS_CV`, `FA_REVERSAL_EXCEEDS_CAP` |
| R11.22 | Revaluation শুধু `measurement_model = revaluation` category-তে, আর setting চালু থাকলে। বৃদ্ধি surplus-এ, আগের ক্ষতির পুনরুদ্ধার ছাড়া। হ্রাস আগে surplus-এ, বাকিটা expense-এ | `FA_REVALUATION_DISABLED` |
| R11.23 | Disposal বা write-off-এর তারিখ পর্যন্ত depreciation (stub) disposal-এর অংশ। ওই asset সেই period-এর নিয়মিত run থেকে বাদ | `DisposalService`, run exclusion |
| R11.24 | Gain বা loss = proceeds − selling cost − carrying value। Partial disposal-এ cost ও accumulated depreciation আনুপাতিক, শেষ অংশ বাকিটা শুষে নেয় (rounding) | `acc_fa_disposals` |
| R11.25 | Cost center বা dimension transfer-এ cost ও accumulated depreciation-এর মোট বদলায় না। বদলায় শুধু dimension। Depreciation expense-এর dimension transfer-এর তারিখ থেকে | `FaTransferService`, `DimensionService::validateLines` |
| R11.26 | Category reclassification-এ cost ও accumulated depreciation পুরোনো account থেকে নতুনে যায় (journal)। Company-র মধ্যে transfer শুধু Phase 15 চালু থাকলে | `FA_RECLASS_INVALID`, `FA_INTERCOMPANY_NOT_ENABLED` |
| R11.27 | Fully depreciated asset স্বয়ংক্রিয় `fully_depreciated` status পায় কিন্তু register-এ থাকে। Retirement আলাদা কাজ, যেখানে loss শূন্য (residual শূন্য হলে) | `retire_fully_depreciated` |
| R11.28 | প্রতিটা report as-of date ধরে, শুধু applied transaction থেকে। Draft transaction আলাদা "অপেক্ষমাণ" কলামে | `FaReportService` |
| R11.29 | RC11: category ধরে register মোট (cost, accumulated depreciation, accumulated impairment, CWIP, surplus) = GL control account balance (posted)। অমিল হলে `fail` | `FaReconciler` |
| R11.30 | Phase 12 close checklist-এ FA task-এর ক্রম: (১) Asset event সব প্রক্রিয়াজাত, (২) CWIP capitalization, disposal, transfer, (৩) impairment ও revaluation, (৪) depreciation run ও তার Draft approve ও post, (৫) RC11। Allocation ও recurring-এর আগে | `FaCloseTaskProvider` |
| R11.31 | Register balance বদলায় শুধু journal posted হওয়ার পর (`FaApplyService`)। Draft-এ শুধু transaction `draft` | `state` যন্ত্র |
| R11.32 | একটা asset-এ একসাথে একটা pending (planned, pending\_approval, draft) balance-সম্পর্কিত transaction | `uq (company_id, open_key)`, `FA_ASSET_BUSY` |
| R11.33 | Event-এর তারিখ বন্ধ period-এ হলে `closed_period_policy` অনুযায়ী: roll\_forward হলে journal\_date প্রথম খোলা period, `document_date` আসল তারিখ। Register-এ `effective_date` আসল তারিখই থাকে | Phase 6 document date, `FA_PERIOD_CLOSED` |
| R11.34 | সব amount base currency-তে (G3)। বিদেশি currency-র event Phase 7 `FxService::convert` দিয়ে `effective_date`-র rate-এ | Phase 7 hook |
| R11.35 | Asset-এর `carrying_value_as_read` Accounting-এর সাথে মিলিয়ে দেখা হয়। সহনসীমার বেশি তফাত warning। Accounting-এর মান জেতে | `FA_CARRYING_MISMATCH` |
| R11.36 | Maker-checker: impairment, revaluation, estimate change, disposal, reclassification, opening load যে বানায় সে approve করতে পারে না | SoD guard, `accounting.fixed_assets.approve` |
| R11.37 | পোস্টেড journal-এর সাথে যুক্ত register transaction-এর amount ও delta column কখনো বদলায় না। ভুল হলে উল্টো transaction (reversal) | DB trigger |
| R11.38 | Reversal হলে (posted journal-এর) transaction `reversed`, বিপরীত delta apply হয় | `FaApplyService` |
| R11.39 | Rounding প্রতি asset line-এ ৪ দশমিক, setting অনুযায়ী। Journal-এর মোট = line-এর যোগফল, আবার rounding নয়। শেষ period-এ বাকি residual-এর ফারাক শোষণ | Engine |
| R11.40 | Depreciable নয় এমন category (জমি) depreciation পায় না, কিন্তু impairment, disposal পায় | `is_depreciable` |

**Configuration।** ভূমিকা কলামে **ম** = তৈরি ও বদল, **অ** = অনুমোদন। Setting `acc_settings`-এ (P1-03), সংবেদনশীল key-এ দ্বিতীয় অনুমোদন (`acc_setting_change_requests`)। প্রতিটা বদল `acc_settings_history`-তে আগে ও পরের মান সহ।

| Key | উদ্দেশ্য ও Business Impact | Option ও Validation | Default Policy | Scope | বাধ্যতামূলক কি | কে বানায়, বদলায়, অনুমোদন করে | বদলের প্রভাব (পুরোনো বনাম নতুন) | Lock ও Audit |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `fixed_assets.enabled` | Phase 11 চালু। ভুল সময়ে চালালে register ও GL আলাদা হয় | on বা off। চালুর শর্ত ৮টা (২১.১৩)। শর্ত না মিললে `FA_ENABLE_PRECONDITION` | off | Company | হ্যাঁ (মূল switch) | ম: `accounting.enable.manage`। অ: `accounting.settings.approve` (`flag:` prefix, দ্বিতীয় অনুমোদন) | পুরোনো: Phase 4 পথে হওয়া write-off Draft ও Posted অপরিবর্তিত। নতুন: event register পথে | বন্ধ করা যায় শুধু pending Draft না থাকলে। `accounting.fixed_assets.enabled` বা `.disabled`, flag ও precondition snapshot সহ |
| `fixed_assets.capitalization_threshold_default` | এর নিচের cost সরাসরি expense। ভুল হলে ছোট asset ফুলে ওঠে বা বড় asset expense হয়ে যায় | DECIMAL(20,4) ≥ 0। শূন্য = সব capitalize। Category-তে override | বাধ্যতামূলক, Admin দেবে | Company | হ্যাঁ (enable-এর আগে) | ম: `accounting.settings.manage`। অ: `accounting.settings.approve` | পুরোনো asset-এর সিদ্ধান্ত বদলায় না (`threshold_applied` snapshot)। নতুন acquisition নতুন মানে | Acquisition applied হলে ওই সিদ্ধান্ত lock। `accounting.settings.changed` |
| `fixed_assets.first_period_convention` | প্রথম ও শেষ month-এর depreciation কত। সরাসরি expense-এর পরিমাণ বদলায় | full\_month, mid\_month, daily, next\_month। Category-তে override | full\_month (OD-58a-তে নিশ্চিত হবে) | Company, category | হ্যাঁ | ম: `settings.manage`। অ: `settings.approve` | পুরোনো asset-এর term-এ convention কপি, তাই হিসাব বদলায় না। নতুন term-এ নতুন মান | প্রথম run-এর পর বদলালে audit-এ সতর্কতা। `accounting.settings.changed` |
| `fixed_assets.day_count_basis` | Daily convention-এ দিনের হিসাব | actual, 30\_360 | actual | Company | ঐচ্ছিক (শুধু daily-তে) | ম: `settings.manage`। অ: `settings.approve` | শুধু নতুন term-এ। পুরোনো term-এ snapshot | Term-এ lock। `accounting.settings.changed` |
| `fixed_assets.enabled_methods` | কোন method বেছে নেওয়া যায়। ভুল method-এ হিসাব ভুল | straight\_line, declining\_balance। sum\_of\_years ও units\_of\_production শুধু OD-61 অনুমোদিত হলে (নইলে `FA_METHOD_DISABLED`) | straight\_line, declining\_balance | Company | হ্যাঁ | ম: `settings.manage`। অ: `settings.approve` | বন্ধ করা method-এ থাকা পুরোনো term চলে। নতুন term-এ বাছা যায় না | Term-এ method শব্দ lock। `accounting.settings.changed` |
| `fixed_assets.rounding_mode` | Asset line-এর rounding। ভুল হলে journal-এর যোগফল নড়ে | half\_up, half\_even | half\_up | Company | হ্যাঁ | ম: `settings.manage`। অ: `settings.approve` (সংবেদনশীল) | তৈরি হওয়া run line বদলায় না। নতুন run নতুন মানে | প্রথম run-এর পর lock (শুধু override permission-এ)। `accounting.settings.changed` |
| `fixed_assets.run_journal_grouping` | Run-এ কয়টা Draft journal, কীভাবে ভাগ। Review ও approval সহজ করে | per\_category, per\_category\_dimension, per\_asset। Dimension বন্ধ থাকলে per\_category | per\_category\_dimension | Company | হ্যাঁ | ম: `settings.manage`। অ: `settings.approve` | পুরোনো run-এর journal বদলায় না, run-এ `grouping` snapshot। নতুন run নতুন মানে | Run তৈরির পর ওই run-এর জন্য lock। `accounting.settings.changed` |
| `fixed_assets.require_prior_run_posted` | আগের period-এর run posted না হলে পরেরটা আটকানো। বন্ধ করলে ভুল ভিত্তির ওপর হিসাব | true, false | true | Company | হ্যাঁ | ম: `settings.manage`। অ: `settings.approve` (সংবেদনশীল) | নতুন run-এ। পুরোনো run অপরিবর্তিত | `accounting.settings.changed`, বন্ধ করলে কারণ বাধ্যতামূলক |
| `fixed_assets.late_addition_policy` | দেরিতে যোগ হওয়া asset-এর ছুটে যাওয়া depreciation কীভাবে। ভুল নীতিতে financial statement তুলনার অযোগ্য | catch\_up\_current, start\_current | catch\_up\_current (OD-58a) | Company | হ্যাঁ | ম: `settings.manage`। অ: `settings.approve` | যে asset আগে যোগ হয়েছে তার run হিসাব বদলায় না। নতুন যোগ-হওয়াগুলোতে নতুন নীতি | Run line-এ নীতি snapshot। `accounting.settings.changed` |
| `fixed_assets.disposal_month_convention` | Disposal-এর মাসে কত depreciation। Gain বা loss-এর পরিমাণ বদলায় | same\_as\_first\_period, full\_month, none, daily | same\_as\_first\_period | Company | হ্যাঁ | ম: `settings.manage`। অ: `settings.approve` | পুরোনো disposal অপরিবর্তিত (disposal row-এ snapshot)। নতুন disposal নতুন মানে | `accounting.settings.changed` |
| `fixed_assets.closed_period_policy` | বন্ধ period-এর event কী হবে | roll\_forward, reject | roll\_forward | Company | হ্যাঁ | ম: `settings.manage`। অ: `settings.approve` | নতুন event-এ। pending event পরের retry-তে নতুন নীতিতে | `accounting.settings.changed` |
| `fixed_assets.control_account_manual_posting` | Control account-এ হাতে লেখা। অনুমতি দিলে register ও GL ভাঙে | block, warn, allow | block | Company | হ্যাঁ | ম: `settings.manage`। অ: `settings.approve` (সংবেদনশীল) | নতুন journal-এ। পুরোনো posted অপরিবর্তিত | `accounting.settings.changed`। প্রতিটা ব্লক `accounting.fixed_assets.control_posting_blocked` |
| `fixed_assets.auto_draft_from_events` | Event এলে নিজে Draft হবে কি না | true, false | true | Company | ঐচ্ছিক | ম: `settings.manage`। অ: `settings.approve` | নতুন event-এ। `planned` থাকা transaction পরে হাতে Draft করা যায় | `accounting.settings.changed` |
| `fixed_assets.proceeds_target_default` | Disposal-এর proceeds কোথায় যাবে | receivable (Phase 9 লাগে), bank (Phase 10 লাগে), clearing | clearing (OD-63 নিশ্চিত হলে বদলাবে) | Company | হ্যাঁ | ম: `settings.manage`। অ: `settings.approve` | নতুন disposal-এ। Disposal row-এ `proceeds_mode` বসানো থাকে | `accounting.settings.changed` |
| `fixed_assets.revaluation_enabled` | Revaluation model চালু | true, false। Category-তে `measurement_model` | false (OD-59) | Company | ঐচ্ছিক | ম: `settings.manage`। অ: `settings.approve` | নতুন revaluation-এ। বন্ধ করলে আগের surplus থাকে | চালুর পর আর বন্ধ নয় যদি surplus ≠ 0। `accounting.settings.changed` |
| `fixed_assets.component_accounting_enabled` | Asset ভেঙে component হিসাব | true, false | false (OD-60) | Company | ঐচ্ছিক | ম: `settings.manage`। অ: `settings.approve` | নতুন capitalization-এ। পুরোনো asset সরল থাকে | `accounting.settings.changed` |
| `fixed_assets.retire_fully_depreciated` | Fully depreciated asset নিজে retire হবে কি না | manual, auto\_flag | manual (OD-61a) | Company | ঐচ্ছিক | ম: `settings.manage`। অ: `settings.approve` | নতুন run-এর পর থেকে | `accounting.settings.changed` |
| `fixed_assets.event_max_age_days` | Pending asset event-এর alert-এর সীমা | পূর্ণসংখ্যা ১ থেকে ৩০ | 3 | Company | ঐচ্ছিক | ম: `settings.manage`। অ: দরকার নেই | শুধু alert | `accounting.settings.changed` |
| `fixed_assets.carrying_mismatch_tolerance` | Asset-এর book value ও Accounting-এর মধ্যে সহনসীমা | DECIMAL(20,4) ≥ 0 | 0.0100 | Company | ঐচ্ছিক | ম: `settings.manage`। অ: দরকার নেই | শুধু warning | `accounting.settings.changed` |
| `fixed_assets.run_chunk_size` | Run-এর chunk। ছোট হলে ধীর, বড় হলে transaction ভারী | ১০০ থেকে ২০০০ | 500 | System-wide | ঐচ্ছিক | ম: platform admin। অ: দরকার নেই | শুধু চলমান নয়, পরের run-এ | `accounting.settings.changed` |

**২১.৬ থেকে ২১.১৪** (Operational Workflow, Dependencies, Implementation Tasks, Testing, Security ও Audit, Deliverables, Acceptance Criteria, Entry ও Exit, Open Decisions) block সীমার কারণে আলাদা tab-এ: Phase 11 ২১.৬ থেকে ২১.১৪ (Fixed Asset Accounting)

## ২২. Phase 12: Period-End, Recurring ও Year-End

### ২২.১ Objective ও Scope

**উদ্দেশ্য:** মাস ও বছরের শেষের কাজ হাতে মনে রাখার বদলে system-এর নিয়ন্ত্রিত ধাপে আনা। Recurring entry, accrual reversal, deferral release, allocation আর year-end closing সব **Draft** হিসেবে তৈরি হয় এবং সাধারণ approval ও posting পথ দিয়ে যায়। Close checklist দেখায় কী বাকি আছে, আর required কাজ বাকি থাকলে period বন্ধ হয় না। Year-end-এ নতুন বছরের opening balance গণনা করে মেলানো হয়, হাতে লেখা হয় না।

**Scope-এর ভেতরে:**

- Recurring journal template: fixed বা formula amount, frequency, start ও end, next-run তারিখ, pause, skip, missed run-এর catch-up।
- Auto-reversing accrual: `auto_reverse_on` তারিখে (সাধারণত পরের period-এর প্রথম দিনে) P2-13-এর পথে reversal Draft।
- Deferral schedule: prepaid expense, deferred revenue, accrued expense, accrued revenue, amortization। মাসিক release Draft, schedule বদল ও বাতিল, RC16।
- Allocation rule: percentage, driver-ভিত্তিক (Phase 6-এর dimension), fixed amount। Allocation run থেকে Draft journal।
- Close checklist engine: per-company template, task (system check, run job, manual), ক্রমিক dependency, evidence, P3-11-এর সাথে যুক্ত gate।
- Year-end closing journal। OD-20 (dynamic retained earnings নাকি closing entry) সমাধানের প্রস্তাব সহ দুটো পথই specify করা।
- Fiscal year rollover (পরের বছরের period ও number series), opening carry-forward গণনা ও যাচাই, late entry ও prior-year adjustment নিয়ম।
- Capability flag `period_end.enabled`, permission, audit, report, monitoring, UAT script।

**Scope-এর বাইরে (কেন):**

- Depreciation হিসাব: Phase 11-এর `DepreciationEngine`-এর কাজ। এখানে শুধু close task হিসেবে ডাকা হয়।
- FX revaluation হিসাব: Phase 7-এর `FxRevaluationService`। এখানে শুধু task।
- Tax return তৈরি (Phase 8), bank reconciliation (Phase 10), subledger reconciliation (Phase 9): এখানে শুধু তাদের অবস্থা পড়ে task সবুজ করা হয়।
- Weekly বা daily recurring এবং Billing-এর recurring invoice: আর্থিক calendar period-এর গুণিতকের বাইরে। Invoice Billing module-এর দায়িত্ব (OD-118-এর সাথে সংগতি)।
- Auto-post: G9 ও OD-81 বন্ধ না হওয়া পর্যন্ত সব Draft।
- Consolidation-এর year-end ও elimination: Phase 15।
- Statutory financial statement: Phase 13 ও 16।
- Payroll accrual-এর পরিমাণ হিসাব: Payroll module Financial Event পাঠায়। এখানে শুধু accrual journal-এর reversal।

**Baseline অনুমান (এই Phase যা ধরে লেখা):** G2 (একটাই ledger), G4 (১২ মাসের period), G7 (ledger = posted lines), G9 (সব Draft)। এছাড়া: **S1** আর্থিক বছর Phase 1-এর `fiscal_year`। **S2** Retained Earnings account আছে (`system_key = retained_earnings`, type equity)। **S3** company-র time zone Phase 1-এ রাখা, scheduler সেটা ধরে। **S4** `acc_journals`-এর generated `source_active` unique key `source_event` সহ (P1-10-এ নিশ্চিত করতে হবে, §২২.৭)। **S5** Phase 12 যে journal বানায় তার `created_by` সেই company-র system actor (P4-এর intake যেমন), manual run-এ অনুরোধকারী।

### ২২.২ Functional Requirements

**ক. Recurring journal template**

- Template-এ code, নাম, frequency (`interval_months` 1 থেকে 24), দিনের নিয়ম (নির্দিষ্ট তারিখ, মাসের প্রথম, মাসের শেষ), start ও end তারিখ বা সর্বোচ্চ run সংখ্যা, journal type (default `recurring`), line (account, Debit বা Credit, fixed বা formula amount, dimension)।
- Amount fixed হতে পারে অথবা সীমিত formula (constant, template parameter, আগের run-এর amount, period-এর দিন সংখ্যা)। Ledger থেকে পড়া formula এই version-এ নেই (OD-65b)।
- Scheduler job প্রতিটা (template, period) জোড়ার জন্য ঠিক একটা Draft বানায়। আবার চালালে বা দুজন একসাথে চালালে নতুন Draft হয় না।
- Pause, resume, নির্দিষ্ট period skip (কারণ সহ), শেষ করা, retire। Missed run-এর catch-up setting অনুযায়ী (OD-65a)।
- Generated Draft সাধারণ নিয়মে Submit, approve, post হয়। Template activation ও সংবেদনশীল বদলে দ্বিতীয় অনুমোদন।

**খ. Auto-reversing accrual**

- যে journal type-এ `auto_reverse_allowed` চালু, তার Draft-এ `auto_reverse_on` তারিখ বসানো যায়।
- মূল journal post হওয়ার পর `auto_reverse_on` তারিখে job reversal Draft বানায় (P2-13-এর পথ, `reverses_journal_id`, কারণ `auto_reverse`)। একবারই।
- লক্ষ্য period বন্ধ থাকলে setting অনুযায়ী প্রথম open period-এ গড়ানো বা alert (R12.17)।

**গ. Deferral schedule**

- Post হওয়া journal line থেকে (বা go-live opening balance থেকে) schedule: total amount, শুরু ও শেষ, method (straight-line মাসিক, straight-line দৈনিক, custom), deferral account, recognition account, dimension।
- প্রতি period-এর release line আগেই গণনা হয়, release job সেই period-এর জন্য Draft বানায়।
- Schedule বদল ও বাতিল নতুন version হিসেবে, posted release অপরিবর্তিত। দ্বিতীয় অনুমোদন।
- RC16: schedule-এর অবশিষ্ট যোগফল = deferral control account-এর GL balance।

**ঘ. Allocation**

- Rule: source pool (account, ঐচ্ছিক dimension value), basis (period net activity বা ending balance), target তালিকা (account, dimension value), পদ্ধতি percentage, driver বা fixed।
- Driver হতে পারে Phase 6-এর dimension-ভিত্তিক ledger balance অথবা হাতে দেওয়া statistical মান (headcount, বর্গফুট)। Run-এর সময় driver snapshot রাখা হয়।
- Preview কিছু লেখে না। Run Draft journal বানায়। Sequential ধাপ রাখা যায়, reciprocal নেই (OD-67)।

**ঙ. Close checklist engine**

- Company-র নিজস্ব template (task, ক্রম, dependency, required কি না, assignee, due offset)। Period-এর জন্য template থেকে checklist instance।
- Task তিন ধরনের: system check (স্বয়ংক্রিয় যাচাই), run job (recurring run, deferral release, allocation, depreciation run, FX revaluation), manual task (assignee ও evidence)।
- Bank reconciliation sign-off (Phase 10), subledger reconciliation (Phase 9), tax return (Phase 8) task হিসেবে অবস্থা দেখায়।
- Required task অসম্পূর্ণ থাকলে P3-11-এর period close আটকায়।

**চ. Year-end closing**

- `year_end.mode`: `dynamic` (default, closing journal নেই, Equity-তে চলতি বছরের লাভ গণনা করে) অথবা `closing_entry` (Revenue ও Expense balance Retained Earnings-এ সরানো)।
- Closing journal total হিসেবে বা dimension ধরে, type `closing`, Draft, approval ও post সাধারণ পথে। Preview আগে।
- নিয়ন্ত্রিত reverse এবং supplementary closing (বন্ধ করার পর ভুল ধরা পড়লে বাকি অংশ সরানো)।

**ছ. Fiscal year rollover ও opening carry-forward**

- পরের বছরের ১২ period তৈরি, number series ও voucher counter প্রস্তুত বা যাচাই, recurring template-এর `next_run_date` যাচাই।
- Balance sheet account-এর নতুন বছরের opening গণনা করা হয় (আগের বছরের closing), কোথাও হাতে লেখা হয় না। RC16b মেলায়।

**জ. Late entry ও prior-year adjustment**

- বন্ধ বছরে সরাসরি backdate নেই। Policy: `reopen_only` অথবা `current_year_adjustment` (OD-69b)। কোনো পথেই posted journal বদলায় না।

**ঝ. Report ও monitoring**

- Pending recurring run, auto-reversal তালিকা, deferral roll-forward, allocation run ইতিহাস, close status dashboard, year-end status, ব্যর্থ job-এর alert।

**ঞ. Capability flag**

- `period_end.enabled` (default বন্ধ)। বন্ধ থাকলে সব job no-op, API `PER_DISABLED`, P3-11 আগের মতো। চালুর শর্ত §২২.৫-এ।

### ২২.৩ Technical Requirements

**Service (Master Plan §৭-এর নাম):**

| Service | মূল method | কাজ |
| --- | --- | --- |
| `RecurringEngine` | `dueRuns(company, asOf)`, `generate(company, templateId, periodId, trigger)`, `preview(templateId, periodId)`, `autoReverseDue(company, asOf)` | Template থেকে Draft, missed run ধরা, auto-reversal Draft |
| `DeferralEngine` | `buildSchedule(spec)`, `release(company, periodId)`, `change(scheduleId, spec)`, `cancel(scheduleId, disposition)`, `reconcile(company, periodId)` | Schedule তৈরি, মাসিক release Draft, বদল, RC16 |
| `AllocationEngine` | `preview(ruleId, periodId)`, `run(ruleId, periodId, trigger)`, `cancel(runId)` | Pool গণনা, বণ্টন, Draft journal |
| `YearEndService` | `preview(company, fy)`, `createClosingDrafts(company, fy, previewHash)`, `reverse(runId, reason)`, `rollover(company, fy)`, `verifyCarryForward(company, fy)` | Closing, rollover, carry-forward যাচাই |
| `CloseChecklistService` | `createInstance(company, periodId)`, `refresh(checklistId)`, `runTask(taskId)`, `completeTask(taskId, evidence)`, `waiveTask(taskId, reason)`, `isCloseAllowed(company, periodId)` | Checklist ও task, P3-11-এর gate |

`CloseTaskHandlerRegistry` Phase 12-এর ভেতরের অংশ। প্রতিটা handler key একটা adapter, আর adapter শুধু অন্য phase-এর §৭ interface ডাকে: `FxRevaluationService::run`, `DepreciationEngine::run`, `SubledgerReconciler`, `BankReconciliationService`, `TaxReturnService`। অন্য phase-কে Phase 12-এর কিছু implement করতে হয় না, শুধু তাদের interface-এ একটা read-only status query ও idempotent run লাগে (§২২.৭)।

**Job (সব `AccountingJob` base, P1-12; queue `accounting-period-end`, company context সহ):**

| Job | কখন | Idempotency একক |
| --- | --- | --- |
| `RecurringRunJob` | প্রতিদিন company-র run window-এ (OD-71) | (template, period) |
| `AutoReversalJob` | প্রতিদিন | মূল journal (DB `reversal_active` unique) |
| `DeferralReleaseJob` | প্রতিদিন ও চাহিদায় | schedule line |
| `AllocationRunJob` | চাহিদায় ও close task থেকে | (rule, period) |
| `CloseChecklistCreateJob` | প্রতিদিন | period (instance unique) |
| `CloseTaskRunJob` | চাহিদায় | (task, attempt) |
| `YearEndPreviewJob`, `YearEndClosingJob`, `RolloverJob` | চাহিদায় | (fiscal\_year, run\_type) |
| `PeriodEndHealthJob` | ঘণ্টায় | কিছু লেখে না, আটকে থাকা claim ছাড়ে, alert দেয় |

**Idempotent একক ও resume:** প্রতিটা কাজের একক নিজেই একটা সারি (run বা line) যার unique key আছে। Job মাঝপথে মরলে আবার চালালে যে সারির state `generated`/`posted` নয় শুধু সেগুলো ধরে। আলাদা cursor লাগে না। একই job দুবার চললে একই ফল।

**Concurrency (দুজন একই job চালালে):** Claim ও কাজ দুটো আলাদা transaction।

1. **Claim tx (ছোট):** `UPDATE ... SET status='generating', claimed_by=?, claim_expires_at=NOW()+lease WHERE id=? AND (status IN ('pending','failed') OR claim_expires_at < NOW())`, affected row ১ হলে claim সফল, commit।
2. **Work tx:** `JournalService` Draft তৈরি করে (সাধারণ lock ক্রমে: account `FOR SHARE`, voucher counter ইত্যাদি, নতুন journal row নতুন বলে অন্য কেউ ধরে না)। সবার শেষে নিজের run row `UPDATE` (status `generated`, `journal_id`)।
3. Claim হারালে (affected ০) caller বিদ্যমান run ফেরত পায় (API-তে idempotent replay) অথবা manual run-এ `PER_JOB_RUNNING` (409)।

Phase 12-এর কোনো job বা API কখনো `acc_journals.status` লেখে না। Post, approve, reject, cancel সব P2-এর বিদ্যমান পথে (লেখক `PostingService`)। Phase 12 শুধু `JournalService::createDraft(DraftSpec)` ডাকে।

**Lock ক্রম:** global ক্রম (journal, approval request, account, period, voucher counter) অপরিবর্তিত। Phase 12-এর নিজের row (run, schedule, line, task) ক্রমের শেষে বা আলাদা claim tx-এ। Post-এর পথ Phase 12-এর কোনো table ছোঁয় না, তাই চক্র হয় না। Schedule change/cancel schedule row `FOR UPDATE` নিয়ে তারপর Draft cancel পথ ডাকে (Draft journal-এর lock), আর এই সময় কোনো line `generating` থাকলে `PER_JOB_RUNNING`।

**Failure handling:**

| ব্যর্থতা | আচরণ |
| --- | --- |
| Draft validation ব্যর্থ (account inactive, dimension rule, unbalanced) | run `failed`, `error_code` ও `error_detail` সংরক্ষিত, Draft তৈরি হয় না, alert। সংশোধনের পর retry |
| Period বন্ধ | নীতি অনুযায়ী (roll forward, skip with alert, block) |
| Worker মরে গেল | lease শেষে `PeriodEndHealthJob` claim ছাড়ে, পরের run তুলে নেয় |
| Deadlock | Work tx সর্বোচ্চ ৩ বার আবার, তারপর `failed` |
| Handler (অন্য phase) অনুপলব্ধ | task `failed`, `PER_HANDLER_UNAVAILABLE`, বাকি task অক্ষত |
| Flag বন্ধ | job no-op, কিছু লেখে না, health-এ "disabled" |

**Performance (লক্ষ্য, P12-14-এ মাপা হয়):** Recurring ১,০০০ template একটা company-র এক run window-এ ১০ মিনিটের মধ্যে (chunk `period_end.job_chunk_size`)। Deferral release ১০,০০০ সক্রিয় schedule ২০ মিনিটে। Year-end preview ৫০ লাখ posted line থেকে ৬০ সেকেন্ডে (একটা aggregate query, `(company_id, period_id, account_id)` index)। Long query-র জন্য read replica নয়, কারণ ledger সঠিকতা আগে; তাই lock ছাড়া `SELECT` ও ছোট chunk। কোনো transaction ৩০ সেকেন্ডের বেশি নয়।

**Security:** Scheduler system actor হিসেবে চলে, কিন্তু শুধু flag চালু company-তে। Manual run-এ ব্যবহারকারীর permission (§২২.১০) ও company scope (`CompanyScope`) যাচাই। Job payload-এ ব্যবহারকারীর id থাকে (audit-এর জন্য), token নয়। Formula evaluator sandboxed (`eval` নেই), শুধু whitelist-করা token।

### ২২.৪ Data Model ও API

**Table (সব `acc_` prefix, পুরো column spec Pack-এ):**

| গোষ্ঠী | Table | মূল column (সংক্ষিপ্ত) | Card |
| --- | --- | --- | --- |
| Recurring | `acc_recurring_templates` | `code`, `status`, `interval_months`, `day_rule`, `start_date`, `end_date`, `next_run_date`, `amount_mode`, `params`, `auto_reverse_offset`, `version` | P12-02 |
| Recurring | `acc_recurring_template_lines` | `template_id`, `line_no`, `account_id`, `side`, `amount_fixed`, `amount_formula`, `is_balancing_line` | P12-02 |
| Recurring | `acc_recurring_line_dimensions` | `template_line_id`, `dimension_id`, `value_id` | P12-02 |
| Recurring | `acc_recurring_runs` | `template_id`, `period_id`, `status`, `journal_id`, `template_snapshot`, `trigger`, `claim_expires_at` | P12-03 |
| Deferral | `acc_deferral_schedules` | `schedule_type`, `status`, `source_journal_id`, `source_line_id`, `total_amount`, `method`, `deferral_account_id`, `recognition_account_id`, `schedule_version` | P12-05 |
| Deferral | `acc_deferral_schedule_dimensions` | `schedule_id`, `dimension_id`, `value_id` | P12-05 |
| Deferral | `acc_deferral_schedule_lines` | `schedule_id`, `schedule_version`, `period_id`, `release_date`, `amount`, `state`, `journal_id` | P12-05 |
| Deferral | `acc_deferral_schedule_changes` (append-only) | `schedule_id`, `change_type`, `from_version`, `to_version`, `reason`, `snapshot` | P12-05 |
| Allocation | `acc_allocation_rules` | `code`, `method`, `status`, `source_account_id`, `basis`, `sequence_no`, `version` | P12-07 |
| Allocation | `acc_allocation_rule_targets` | `rule_id`, `target_account_id`, `target_dimension_id`, `target_value_id`, `percentage`, `fixed_amount`, `driver_kind`, `is_remainder` | P12-07 |
| Allocation | `acc_allocation_driver_values` | `rule_id`, `period_id`, `target_id`, `driver_value` | P12-07 |
| Allocation | `acc_allocation_runs` | `rule_id`, `period_id`, `status`, `source_amount`, `journal_id`, `rule_snapshot`, `driver_snapshot` | P12-08 |
| Close | `acc_close_checklists` | `is_template`, `code`, `period_id`, `template_id`, `status` | P12-09 |
| Close | `acc_close_tasks` | `checklist_id`, `task_key`, `task_type`, `handler_key`, `required`, `status`, `assignee_user_id` | P12-09 |
| Close | `acc_close_task_dependencies` | `task_id`, `depends_on_task_id` | P12-09 |
| Close | `acc_close_task_runs` (append-only) | `task_id`, `attempt_no`, `status`, `result`, `evidence_ref` | P12-10 |
| Year-end | `acc_year_end_runs` | `fiscal_year`, `run_type`, `status`, `mode`, `closing_basis`, `preview_hash`, `net_result` | P12-12 |

**বিদ্যমান table-এ expand:** `acc_journals`-এ `auto_reverse_on`, `recurring_run_id`, `deferral_schedule_id`, `year_end_run_id` (সব nullable, composite FK `(company_id, x_id)`), `acc_accounts`-এ `is_deferral_control` (default 0)। P1-10-এর trigger-এ চারটা journal column "অপরিবর্তনীয়" তালিকায় (P12-01)।

**API (prefix `/accounting`, সব write `Idempotency-Key` সহ, লেখার সময় flag যাচাই):**

| Resource | Endpoint | Permission |
| --- | --- | --- |
| Template | `GET/POST /recurring-templates`, `GET/PATCH /recurring-templates/{id}` (body-তে `version`), `POST .../activate`, `.../pause`, `.../resume`, `.../end`, `.../retire`, `POST .../preview?period=` | view, manage, activation-এ approve |
| Recurring run | `GET /recurring-runs`, `POST /recurring-templates/{id}/runs`, `POST /recurring-runs/{id}/skip`, `.../retry`, `.../cancel`, `POST /recurring/run-due` | view, run |
| Auto-reversal | `GET /auto-reversals/pending`, `POST /auto-reversals/run` | view, run |
| Deferral | `GET/POST /deferral-schedules`, `GET/PATCH /deferral-schedules/{id}` (draft), `POST .../activate`, `.../changes`, `.../cancel`, `.../suspend`, `.../resume`, `GET .../lines`, `POST /deferral-releases/run`, `GET /deferral-schedules/reconciliation?period=` | view, manage, release, approve |
| Allocation | `GET/POST /allocation-rules`, `GET/PATCH .../{id}`, `PUT .../{id}/targets`, `POST .../activate`, `.../retire`, `PUT /allocation-drivers/{rule}/{period}`, `POST /allocation-runs/preview`, `POST /allocation-runs`, `GET /allocation-runs`, `POST /allocation-runs/{id}/cancel` | view, manage, run, approve |
| Close template | `GET/POST /close-templates`, `GET/PATCH .../{id}`, `PUT .../{id}/tasks` | close.view, close.manage |
| Close checklist | `POST /close-checklists` (period), `GET /close-checklists/{id}`, `POST .../refresh`, `POST /close-tasks/{id}/start`, `.../run`, `.../complete`, `.../reopen`, `.../assign`, `.../waive`, `GET .../runs` | close.view, close.execute, close.approve |
| Year-end | `GET /year-end/{fy}/status`, `POST .../preview`, `POST .../closing-drafts`, `POST .../rollover`, `POST .../carry-forward-check`, `POST /year-end/runs/{id}/reverse`, `GET /year-end/runs` | year\_end.view, run, approve, reverse |
| Flag | `POST /period-end/enable-check` (dry run), Setting বদল `flag:period_end` | period\_end.manage, period\_end.approve |

**Error আকার:** `AccountingError` enum, `{code, message, field, details, correlation_id}`। Prefix `PER_`, `REC_`, `DEF_`, `ALC_`, `YER_`; পুরো তালিকা Pack-এর Registry যোগ-এ।

**Phase 12-এর বাকি অংশ (২২.৫ থেকে ২২.১৪):** এই tab-এর block সীমা (20,000) প্রায় পূর্ণ, তাই বাকিটা আলাদা tab-এ: Phase 12 (বিস্তারিত)

## ২৩. Phase 13: Budgeting, Forecast ও Management Reporting

Blueprint tab-এর আকারের সীমা (20,000 block) শেষ হওয়ায় এই phase-এর সব ২৩.১ থেকে ২৩.১৪ অংশ আলাদা tab-এ লেখা: Phase 13 (২৩.১ থেকে ২৩.১৪): Budgeting, Forecast, Management Reporting

## ২৪. Phase 14: Posting Rules Engine ও Source Integrations

Phase 4 একটা source (Asset write-off) আর একটা হাতে-বাঁধা line-নিয়ম দিয়ে শুরু করেছিল। এই Phase সেটাকে সাধারণ করে। যেকোনো ERPFlow module একই চুক্তিতে Financial Event পাঠাবে। Accounting versioned Posting Rule দিয়ে তা balanced Draft Journal বানাবে। নিজে কখনো post করবে না (G9)।

### ২৪.১ Objective ও Scope

**উদ্দেশ্য:** Event থেকে Draft পর্যন্ত পুরো পথ এক জায়গায় নিয়ম-চালিত, পরীক্ষিত, অনুমোদিত আর পুনরায় চালানো যায় এমন করা। নিয়ম বদলাতে code deploy লাগবে না, কিন্তু প্রতিটা বদল simulation, দ্বিতীয় অনুমোদন ও version ছাড়া চালু হবে না। প্রতিটা Draft জানবে সে কোন rule-এর কোন version থেকে এসেছে (`posting_rule_id`, `posting_rule_version`)।

**Phase 4 থেকে কী বাড়ে:**

| বিষয় | Phase 4 | Phase 14 |
| --- | --- | --- |
| Source | একটা (`asset_finance_event`, write\_off) | প্রতি module একটা, একই Outbox Contract v1 |
| Intake channel | শুধু DB-reader | DB-reader ও authenticated inbound API বা webhook |
| Line-নিয়ম | কোডে দুই line, `acc_account_mappings` | versioned rule, বহু line, শর্ত, expression |
| Payload যাচাই | `schema_version` সংখ্যা | versioned JSON schema, compatibility mode |
| নিয়ম বদল | mapping change request | simulation + maker-checker + effective date + version |
| আটকে থাকা অবস্থা | `held_mapping`, `held_currency` | আরও `held_rule`, `held_tax`, `held_party`, `held_dimension`, `held_period` |
| পুনরায় চালানো | মানুষ এক এক করে release | replay ও backfill, cutover সুরক্ষা সহ |
| Reconciliation | RC8.1 থেকে RC8.6 | RC8.7 থেকে RC8.13, source ও event type ধরে |

**Scope-এর ভেতরে:**

- Event catalog (`acc_event_types`) ও schema registry (`acc_event_schemas`), version, deprecation, compatibility mode।
- Outbox Contract v1: বাধ্যতামূলক field, ordering, delivery, idempotency identity, replay, tombstone, correlation id।
- দুই intake channel: P4-04 ধাঁচের DB-reader adapter আর authenticated inbound API ও webhook (`acc_integration_endpoints`)। Source-প্রতি rate limit ও backpressure।
- Rule engine: সীমিত, নিরাপদ expression language। শর্ত, priority, effective date, first-match বা all-match, বহু line template, rounding allocation।
- Tax (Phase 8), FX (Phase 7), Party (Phase 9), Dimension (Phase 6) এর interface দিয়ে line তৈরি।
- Rule governance: draft, simulation, দ্বিতীয় অনুমোদন, effective date, version, retire, impact preview, mapping editor-এর সাথে SoD।
- Phase 4 `acc_account_mappings` থেকে rule-এ migration (shadow, তারপর live), চলমান event না ভেঙে।
- Held state, replay, backfill, dead-letter queue আর ops screen-এর সম্প্রসারণ।
- RC8-এর per-source sub-check ও subledger মিল।
- Source-ভিত্তিক integration spec: Billing ও Project, Procurement ও AP, Payroll, Inventory, Commissioning, Asset (Phase 11), bank feed (Phase 10, ভেতরের)।
- নিয়ন্ত্রিত auto-post নীতির নকশা (OD-81), default বন্ধ, কোনো code path নেই।

**Scope-এর বাইরে (আর কেন):**

| বাইরে | কেন |
| --- | --- |
| Source module-এর ভেতরের কাজ: outbox table, event emit, ack hook | মালিক সেই module (G8, A1)। Accounting শুধু চুক্তি, test kit ও পরিবর্তনের তালিকা দেয়, কখনো সেই module-এর code বা table বদলায় না |
| Auto-post | G9। OD-81 বন্ধ না হলে কোনো path নেই |
| Tax হিসাব, FX rate, open item, depreciation, bank matching | Phase 7, 8, 9, 10, 11 এর মালিকানা। এই Phase শুধু interface ডাকে |
| Source-এর business হিসাব (payroll গণনা, inventory costing, commission গণনা) | Event-এ চূড়ান্ত amount আসে। Accounting আবার হিসাব করে না, শুধু যাচাই করে |
| যেকোনো code বা script rule | নিরাপত্তা ও audit। শুধু সীমিত expression language |
| E-invoicing ও statutory submission | OD-118, দায় Billing-এর |
| Intercompany event | Phase 15 |
| Event payload-এর দীর্ঘ archival ও retention | Phase 16। এই Phase শুধু OD-86-এ প্রশ্ন তোলে |

### ২৪.২ Functional Requirements

**ক. Event catalog ও schema registry**

- প্রতিটা event type `acc_event_types`-এ নিবন্ধিত: source\_type, event\_type, নাম, `posting_disposition` (journal, reversal, tombstone, none), sensitive কি না, কোন event-কে উল্টায়। `EventCatalog` এর একমাত্র পড়ার দরজা।
- প্রতিটা event type-এর এক বা একাধিক schema version `acc_event_schemas`-এ। Version অবস্থা: draft, active, deprecated, retired। Active schema বদলানো যায় না, নতুন version বানাতে হয়।
- Compatibility mode: `backward` (নতুন schema পুরোনো payload পড়ে), `forward`, `full`, `none`। Default `backward`। নতুন version সংরক্ষণে `SchemaRegistry::checkCompatibility` চলে, ভাঙলে EVC\_COMPAT\_BREAK।
- Deprecation: `deprecated` version-এর event আসে, গ্রহণ হয়, সতর্কতা (warning) চিহ্নিত। `sunset_on` পার হলে `retired`, তখন নতুন event EVC\_VERSION\_RETIRED দিয়ে ফেরত।
- Catalog নেই এমন event\_type বা schema\_version EVC\_TYPE\_UNKNOWN বা EVC\_SCHEMA\_UNKNOWN, চুপচাপ গ্রহণ নয়।

**খ. Outbox Contract v1 (প্রতিটা source module মানবে)**

- Blueprint ৪.১-এর এগারোটা field বাধ্যতামূলক থাকে। যোগ হয় `contract_version` ও `meta` (correlation\_id, aggregate\_key, sequence\_no)।
- Source-এর outbox row আর business পরিবর্তন একই DB transaction-এ লেখা।
- Delivery at-least-once। Accounting-এ প্রভাব exactly-once, identity `(company_id, source_type, source_id, event_type)` আর `payload_hash` দিয়ে।
- Ordering শুধু এক `aggregate_key`-র ভেতরে, `sequence_no` বাড়তি ক্রমে। দুই aggregate-এর মধ্যে ক্রম নেই।
- Tombstone আলাদা event class: source বলে "এই event বাতিল, ঘটেইনি"। এটা ব্যবসায়িক cancel থেকে আলাদা (cancel একটা সত্যিকারের event, reversal journal বানায়)।
- Replay: source একই identity ও একই payload আবার পাঠাতে পারে (no-op)। Accounting নিজের inbox থেকেও replay করতে পারে (ক্ষেত্র ঞ)।
- Correlation id: এক ব্যবসায়িক ঘটনার সব event-এ এক মান (যেমন invoice থেকে payment)। Audit ও trace-এ ব্যবহার।

**গ. দুই intake channel**

- **DB-reader adapter:** P4-04-এর `FinanceEventSource` বাড়ে। Accounting source-এর outbox table শুধু পড়ে (SELECT)।
- **Inbound API ও webhook:** যে module নিজে পাঠায়। HMAC স্বাক্ষর, timestamp, nonce (delivery id), key rotation, rate limit, size limit। দুই channel-ই শেষে `InboxService::receive` ডাকে। পরের পথ একই।
- প্রতি source-এর আলাদা rate ও backpressure সীমা। inbox ভরে গেলে API 503 (EVC\_BACKPRESSURE), reader নতুন batch তোলা থামায়।

**ঘ. Posting rule model ও expression language**

- Rule set = এক `(source_type, event_type)`-এর নিয়মের সংগ্রহ। Version = rule set-এর অপরিবর্তনীয় snapshot।
- Rule = শর্ত (condition), priority, effective তারিখ, status (draft, active, retired), line template।
- Line = side (debit, credit), account source (`fixed`, `mapping`, `payload_field`, `party_control`, `tax_result`), amount expression, dimension mapping, narration template।
- Expression language `RuleExpr v1`: JSON AST, শুধু whitelisted function। কোনো loop (শুধু array-র ওপর `repeat_over`), I/O, regex, বর্তমান সময়, random নেই। সীমা: ২০০ node, গভীরতা ৮, ৫০০ array element, ৫০ ms।

**ঙ. Rule engine আচরণ**

- `PostingRuleEngine::build(inboxRow): DraftSpec` একটা pure function: কিছু লেখে না। Draft লেখে P4-06-এর সাধারণ রূপ `DraftBuilder`, `JournalService::createDraft` দিয়ে।
- Match policy: `first_match` (default) বা `all_match`। Version-এর সঙ্গে অপরিবর্তনীয়।
- Multi-line template: একটা rule থেকে অনেক line, array-র প্রতি element-এ একটা করে line (`repeat_over`)।
- Rounding allocation: debit = credit জোর করে নয়, গণনা করে। বড় অবশিষ্ট (largest remainder) পদ্ধতি, সীমা পার হলে hold।
- Tax: `TaxService::calculate` আর `TaxLineBuilder`। FX: `FxService::rate` ও `::convert`। Party: `PartyService`। Dimension: `DimensionService::validateLines`।
- Version নির্বাচন event-এর `effective_date` ধরে, processing-এর দিন ধরে নয় (OD-80b)।

**চ. Held state**

- বিদ্যমান: `held_mapping`, `held_currency`। নতুন: `held_rule` (rule নেই বা অস্পষ্ট), `held_tax`, `held_party`, `held_dimension`, `held_period`। কারণ `hold_reason` column-এ কোড সহ।
- Held event আপনা-আপনি ছাড়া পায় না। মানুষ release করে (P4-09), bulk release `hold_reason` ধরে।

**ছ. Simulation**

- `RuleSimulator`: sample payload বা ঐতিহাসিক event (inbox থেকে) নতুন version-এর বিরুদ্ধে চালায়। কোনো journal লেখে না। ফল: matched, unmatched, held, imbalanced, আর বিদ্যমান journal-এর সঙ্গে পার্থক্য।
- Activation-এর জন্য ঠিক ওই snapshot\_hash-এর ওপর সম্পূর্ণ, তাজা simulation বাধ্যতামূলক।

**জ. Governance**

- Maker draft বানায় ও simulate করে, Checker (অন্য ব্যক্তি) activate অনুমোদন করে। Mapping entry যে বদলেছে সে সেই mapping ব্যবহারকারী version approve করতে পারে না।
- Activation-এর আগে impact preview: কত চলমান ও আটকে থাকা event প্রভাবিত, আর শেষ ৩০ দিনের event নতুন rule-এ কী হতো।
- Retire: কারণ বাধ্যতামূলক। শেষ active version retire করলে ওই event type-এর নতুন event `held_rule`-এ যাবে, তাই সতর্কতা ও স্বীকৃতি লাগে।

**ঝ. Phase 4 migration**

- `acc_account_mappings`-এর প্রতিটা সারি থেকে সিস্টেম rule set ও `acc_posting_mapping_entries` সারি তৈরি। মোড: `legacy`, `shadow`, `live` (source ধরে)। `shadow`-এ পুরোনো পথ Draft বানায়, engine শুধু তুলনা করে। পার্থক্য শূন্য প্রমাণের পর `live`। বিদ্যমান Draft ও posted journal অপরিবর্তিত।

**ঞ. Replay ও backfill**

- `ReplayService`: inbox থেকে নির্বাচিত event (ID, তারিখ-পরিসর, source, hold\_reason ধরে) আবার প্রক্রিয়া। Backfill: source-এর পুরোনো outbox row নতুন করে তোলা।
- Duplicate সুরক্ষা: identity unique, `uq_acc_journals_source_active`, আর manual journal-এর সঙ্গে overlap পরীক্ষা (`OverlapChecker`)।
- Cutover rule: source-এর `cutover_date`-এর আগের `effective_date`-এর event Draft হয় না (status `ignored`, কারণ `before_cutover`), কারণ ওই সময়ের হিসাব আগে হাতে বা পুরোনো system-এ বুক হয়েছে।

**ট. Dead-letter ও ops**

- যে event বা delivery বারবার ব্যর্থ বা অবৈধ (schema ভুল, স্বাক্ষর ভুল নয় বরং বিষয়বস্তু ভুল), সেটা `acc_event_dead_letters`-এ। মানুষ দেখে: replay, discard (কারণ সহ), বা source-কে ঠিক করতে বলা।
- P4-09-এর ops screen-এ source-ভিত্তিক filter, DLQ ট্যাব, endpoint-এর স্বাস্থ্য, sequence gap-এর তালিকা।

**ঠ. Reconciliation ও subledger**

- RC8.7 থেকে RC8.13: source-এর outbox মোট বনাম ledger (event type ধরে), coverage, sequence gap, rule version অখণ্ডতা, dead-letter বয়স, subledger মিল (Phase 9-এর RC10-এর সঙ্গে সংযোগ)।

**ড. Monitoring**

- `acc_alert_state`-এ source-ভিত্তিক alert key। Source-প্রতি lag, held সংখ্যা, rule-no-match, sequence gap, endpoint ব্যর্থতা, backpressure।

**ঢ. Controlled auto-post (OD-81)**

- Default বন্ধ, Phase 14-এ কোনো code path নেই (G9)। শুধু নকশা ও সীমা-তালিকা নথিভুক্ত, setting `autopost.enabled` লক করা।

**ণ. Source-ভিত্তিক integration spec**

প্রতিটা source একই Outbox Contract v1 মানে। `source_type` নাম `<module>_finance_event`, `source_id` = source-এর outbox row id (P4-এর `asset_finance_event`-এর মতো), `event_type` = ক্রিয়া। Rule template নিচে "default", প্রতিটা company নিজের মতো বদলাবে (simulation ও অনুমোদন সহ)। সব amount `DECIMAL(20,4)`, সব আসে source থেকে চূড়ান্ত হিসেবে, Accounting আবার হিসাব করে না, শুধু যাচাই করে।

**সব source-এর জন্য reversal নিয়ম।** Reversal event type catalog-এ `posting_disposition = reversal` আর `reverses_event_type` দিয়ে মূল event-এর সঙ্গে জোড়া। Payload-এ `meta.reverses = {source_id, event_type}` বাধ্যতামূলক।

| মূল event-এর অবস্থা | Engine কী করে | কারণ |
| --- | --- | --- |
| মূল event এখনো Draft journal (posted নয়) | মূল Draft বাতিল (Phase 2-এর বিদ্যমান cancel পথ, status লেখে PostingService)। মূল inbox row `ignored`, কারণ `voided_by_source`। Reversal event `ignored`, কারণ `target_not_posted` | Posted নয় এমন journal-এর reversal লাগে না |
| মূল journal posted | নতুন Draft, `journal_type = reversal`, `reverses_journal_id` = মূল journal, line মূল line-এর হুবহু উল্টো (mirror)। `journal_date` = reversal event-এর `effective_date`, মূল তারিখ নয়। Period বন্ধ থাকলে `held_period` | Posted কখনো বদলায় না (A1-A10)। Reversal নতুন তারিখে নতুন journal |
| মূল event Accounting-এ আসেইনি বা `held_*`/`failed` | মূল row `ignored` (`voided_by_source`), reversal row `ignored` | Ledger-এ যা নেই তা উল্টানো যায় না |
| মূল journal আগেই reversed | Reversal event `ignored`, কারণ `already_reversed`, কোনো error নয় | Idempotent, `reversal_active` unique |
| Rule-ভিত্তিক reversal দরকার (যেমন credit note-এ ভিন্ন tax) | Catalog-এ `reversal_mode = rule`: reversal event-এর নিজের rule চলে, mirror নয় | Credit note আলাদা ব্যবসায়িক কাগজ |

**Tombstone** আলাদা: source বলে মূল event কখনো ঘটেনি (ভুল emit)। মূল Draft থাকলে বাতিল, না থাকলে ignore, কিন্তু মূল journal **posted** হলে tombstone `failed` (`RUL_TOMBSTONE_POSTED`) হয়ে মানুষের সিদ্ধান্তের জন্য অপেক্ষা করে। চুপচাপ posted journal বদলানো বা উল্টানো হয় না।

---

**ণ.১ Project ও Billing** (`billing_finance_event`, flag `event_intake.sources.billing`, channel DB-reader বা API, OD-78)

| event\_type | মানে | disposition | amount | Party | মূল dimension |
| --- | --- | --- | --- | --- | --- |
| `invoice_issued` | Invoice issue | journal | invoice total | customer | project, cost\_center |
| `time_invoice_issued` | Logged hour থেকে invoice | journal | total | customer | project (বাধ্যতামূলক) |
| `credit_note_issued` | Credit note | journal (reversal\_mode = rule) | total | customer | project |
| `invoice_cancelled` | Issue-র পর বাতিল | reversal (mirror) | total | customer | মূল |
| `payment_received` | গ্রাহকের পেমেন্ট | journal | পেমেন্ট amount | customer | bank account |
| `payment_reversed` | Bounce বা refund | reversal (mirror) | amount | customer | মূল |

Payload মূল field (schema v1): `invoice_no`, `invoice_date`, `due_date`, `customer_ref {source_type, source_id}`, `currency`, `fx {rate, rate_type}` (ঐচ্ছিক), `lines[] {line_no, item_category, net_amount, discount_amount, project_id, cost_center, tax_inputs {tax_code, rate, tax_amount}}`, `subtotal`, `tax_total`, `total`, `payment_terms_code`। Payment: `payment_no`, `payment_date`, `method`, `bank_account_ref`, `allocations[] {invoice_no, amount}`, `unallocated_amount`, `discount_taken`, `withholding_amount`।

Default rule template:

| Rule | শর্ত | Line |
| --- | --- | --- |
| BIL-1 invoice\_issued | সব | (১) Debit, `party_control` (customer), `total`, due\_date, document\_ref = invoice\_no। (২) Credit, `mapping` `revenue_by_category` (key = `line.item_category`), `repeat_over lines`, net - discount, dimension project/cost\_center। (৩) Credit, `tax_result` output tax (`TaxService::calculate`)। শর্ত: Σ(১)=Σ(২)+Σ(৩) |
| BIL-2 credit\_note | সব | (১) Debit `mapping` `sales_returns_by_category`। (২) Debit `tax_result` (output tax ফেরত)। (৩) Credit `party_control` |
| BIL-3 payment\_received | `unallocated_amount = 0` | (১) Debit `mapping` `bank_by_method`। (২) Credit `party_control`, document\_ref = invoice\_no। (৩) `discount_taken > 0` হলে Debit `mapping` `sales_discount`। (৪) `withholding_amount > 0` হলে Debit `tax_result` (withholding receivable) |
| BIL-4 payment\_received | `unallocated_amount > 0` | BIL-3 + Credit `mapping` `customer_advance` (বাকি অংশ, party সহ) |

Tax-এর মালিক: Invoice আইনী কাগজ, তাই tax Billing হিসাব করে। Accounting `TaxService::calculate` চালিয়ে মেলায়, `posting_rules.tax_tolerance`-এর বেশি অমিল হলে `held_tax` (OD-80c)।

Edge case: আংশিক payment, অতিরিক্ত payment (advance), বিদেশি currency invoice (`FxService::rate` invoice তারিখে, realized gain-loss Phase 7 settlement-এ), early-payment discount, grাহকের withholding, একাধিক tax code এক line-এ, zero-rated line, বন্ধ period-এ back-dated invoice (`held_period`), একই invoice-এ payment পরে cancel (cancel-এর আগে payment reversal ক্রমে আসতে হবে, নইলে sequence নিয়মে `blocked`)।

Source-এর (Billing) যা করতে হবে, Accounting করবে না:

- Contract v1 অনুযায়ী `billing_finance_events` outbox table বানানো (migration Billing-এর)।
- `InvoiceLifecycleParticipantRegistry`-তে একটা participant নিবন্ধন, যে invoice issue, cancel, credit note transition-এর **একই transaction-এ** outbox row লেখে।
- Payment হুক: payment রেকর্ড, বাতিল, bounce service-এ একই transaction-এ outbox row।
- Line-প্রতি `tax_inputs`, `item_category`, `project_id` পাঠানো। `aggregate_key = invoice:{id}`, `sequence_no` বাড়ন্ত।
- Customer-এর স্থিতিশীল `source_id` (party mirror, G8)।
- DB-reader channel হলে ack service (P4-07-এর ধাঁচে), API channel হলে signing key সংরক্ষণ ও retry।
- Cutover তারিখ থেকে পুরোনো invoice-এর backfill (Accounting চালায় না, Billing বা ops চালায়)।

**ণ.২ Procurement ও AP** (`procurement_finance_event`, flag `event_intake.sources.procurement`, OD-82: module নাও থাকতে পারে)

| event\_type | disposition | Party | টীকা |
| --- | --- | --- | --- |
| `bill_approved` | journal | vendor | Approved vendor bill |
| `vendor_credit_issued` | journal (rule) | vendor | Debit note |
| `bill_cancelled` | reversal | vendor | Posted না হলে Draft বাতিল |
| `payment_made` | journal | vendor | Withholding সহ |
| `payment_voided` | reversal | vendor | Cheque বাতিল বা ফেরত |
| `expense_claim_approved` | journal | employee | কর্মীর খরচ |
| `expense_claim_paid` | journal | employee | Reimbursement |

Default template: **PRO-1 bill\_approved**: Debit `mapping` `expense_by_category` (`repeat_over lines`, dimension cost\_center/project); শর্ত `payload.matched_receipt = true` হলে Debit `mapping` `grni`। Debit `tax_result` (input tax, recoverable অংশ আলাদা, non-recoverable cost-এ)। Credit `party_control` (vendor)। **PRO-2 payment\_made**: Debit `party_control` (gross), Credit `mapping` `bank_by_method`, Credit `tax_result` (withholding payable)। **PRO-3 expense\_claim\_approved**: Debit expense by category, Debit input tax, Credit `party_control` (employee payable)। **PRO-4 expense\_claim\_paid**: Debit `party_control`, Credit bank।

Edge case: three-way match ও price variance (variance line আলাদা account), vendor advance (prepayment), landed cost যা inventory-তে যায়, বিদেশি currency bill, early-payment discount, duplicate vendor invoice number (warning, block নয়), claim-এ per-diem সীমা (source-এর নিয়ম)।

Source-এর যা করতে হবে: `procurement_finance_events` outbox, bill approve ও payment service-এ একই transaction-এ emit, vendor ও employee স্থিতিশীল `source_id`, tax\_inputs (withholding সহ), `matched_receipt` চিহ্ন, aggregate `bill:{id}`। Module না থাকলে এই source flag বন্ধ থাকে, বাকি Phase অটুট (OD-82)।

**ণ.৩ Payroll** (`payroll_finance_event`, flag `event_intake.sources.payroll`, sensitive, OD-78a)

| event\_type | disposition | টীকা |
| --- | --- | --- |
| `run_approved` | journal | Payroll run অনুমোদিত, component-ভিত্তিক সারাংশ |
| `run_cancelled` | reversal | পেমেন্টের আগে |
| `payslip_paid` | journal | Pay batch পরিশোধ, ব্যাংক ধরে |
| `salary_advance_paid` | journal | Party = employee |
| `payroll_tax_remitted` | journal | Payroll tax জমা |

Payload: `run_id`, `period`, `components[] {cost_center, department, component_code, kind (earning, employer_contribution, deduction, tax), amount}`, `net_pay_total`, `pay_batch {bank_account_ref, amount}`। ডিফল্ট সারাংশ স্তরের: কর্মীর নাম বা ব্যক্তিগত বেতন Accounting-এ আসে না (OD-78a)।

Default template **PAY-1 run\_approved**: Debit `mapping` `payroll_expense_by_component` (`repeat_over components`, kind = earning, dimension cost\_center/department)। Debit `mapping` `employer_contribution_expense`। Credit `mapping` `salary_payable` (net\_pay\_total)। Credit `mapping` `payroll_tax_payable` (kind = tax)। Credit `mapping` `statutory_deduction_payable` (code ধরে)। Credit `mapping` `employer_contribution_payable`। Credit `mapping` `employee_advance_recovery` (advance কাটা)। শর্ত: gross + employer অংশ = net + কর্তন + employer payable। **PAY-2 payslip\_paid**: Debit `salary_payable`, Credit bank। **PAY-3 salary\_advance\_paid**: Debit `party_control` (employee advance), Credit bank। **PAY-4 payroll\_tax\_remitted**: Debit `payroll_tax_payable`, Credit bank।

Edge case: retro-pay সমন্বয় পরের run-এ, চাকরি ছাড়ার final settlement, negative net, ভিন্ন currency-তে বেতন, benefit in kind, একই run-এ একাধিক legal cost center। Leave encashment accrual event নয়, Phase 12-এর accrual।

Source-এর যা করতে হবে: `payroll_finance_events` outbox, approve ও pay batch service-এ একই transaction-এ emit, payload minimization (ব্যক্তি-স্তরের তথ্য নয়), `sensitive` চিহ্ন, aggregate `run:{id}`, employee `source_id` শুধু advance event-এ।

**ণ.৪ Inventory** (`inventory_finance_event`, flag `event_intake.sources.inventory`, OD-83: module নাও থাকতে পারে)

| event\_type | disposition | টীকা |
| --- | --- | --- |
| `goods_received` | journal | Receipt, value inventory-র হিসাব |
| `goods_issued` | journal | COGS, value inventory-র valuation |
| `stock_adjusted` | journal | `delta_value` ধনাত্মক বা ঋণাত্মক |
| `valuation_changed` | journal | Revaluation বা write-down |
| `receipt_reversed`, `issue_reversed` | reversal | Return ও সংশোধন |

Template: **INV-1 goods\_received**: Debit `mapping` `inventory_by_category` (dimension warehouse), Credit `mapping` `grni`। **INV-2 goods\_issued**: Debit `mapping` `cogs_by_category`, Credit `inventory_by_category`। **INV-3/4 stock\_adjusted**: `delta_value > 0` হলে Debit inventory, Credit `inventory_adjustment_gain`; `< 0` হলে উল্টো। **INV-5 valuation\_changed**: Debit/Credit `inventory_revaluation`, বিপরীতে reserve বা expense।

Edge case: negative stock-এ cost, landed cost পরে adjustment হিসেবে, standard cost variance, একই entity-র দুই warehouse transfer (inventory account এক হলে journal লাগে না, আলাদা হলে দুই line), consignment, vendor return AP-র সঙ্গে মিলানো। Cost কখনো Accounting বের করে না (OD-83: perpetual না periodic)।

Source-এর যা করতে হবে: `inventory_finance_events` outbox, valuation চূড়ান্ত হওয়ার পর emit, `category` ও `warehouse` চিহ্ন, aggregate `item_move:{id}`।

**ণ.৫ Commissioning** (`commissioning_finance_event`, flag `event_intake.sources.commissioning`)

| event\_type | disposition |
| --- | --- |
| `commission_posted` | journal |
| `payment_cleared` | **none**: গ্রহণ হয়, audit-এর জন্য রাখা হয়, status `ignored` (কারণ `notification_only`), কখনো journal নয় |

Template **COM-1**: Debit `mapping` `commission_expense` (dimension project, cost\_center), Credit `party_control` (agent বা partner) বা `commission_payable`। `payment_cleared` কখনো cash entry বানায় না, কারণ নগদ ব্যাংক ও payment source (Billing, Procurement, bank feed) থেকে একবারই আসে। Rule editor `posting_disposition = none` event type-এ rule যোগ করতে দেয় না (`RUL_DISPOSITION_NONE`)। Clawback: source শুধু tombstone (কখনো post না হলে) বা Accounting-এ হাতে adjustment (posted হলে), নতুন `commission_reversed` type পরে catalog-এ যোগ হতে পারে code ছাড়া (OD-82a)।

Source-এর যা করতে হবে: `commissioning_finance_events` outbox, `commission_posted` একই transaction-এ, `payment_cleared`-কে `notification` হিসেবে চিহ্নিত পাঠানো (অথবা একেবারেই না পাঠানো), agent `source_id`।

**ণ.৬ Asset** (`asset_finance_event`, Phase 11-এর মাধ্যমে, flag `event_intake.sources.asset_write_off` বিদ্যমান, নতুন `asset_acquisition`, `asset_disposal`, `asset_transfer`)

| event\_type | disposition | টীকা |
| --- | --- | --- |
| `write_off` | journal | P4 থেকে, rule-এ migrate (P14-12) |
| `acquisition` | journal | Capitalization, `AssetRegisterService`-এর ফল |
| `disposal` | journal | `DisposalService`-এর হিসাব করা component |
| `transfer` | journal | Category বা cost center বদল |

Template: **AST-1 acquisition**: Debit `mapping` `asset_cost_by_category`, Credit `party_control` (vendor) বা `acquisition_clearing` বা bank (`payload.funding`)। **AST-2 disposal**: Debit bank বা receivable, Debit `accumulated_depreciation_by_category`, Credit `asset_cost_by_category`, gain হলে Credit `disposal_gain`, loss হলে Debit `disposal_loss`। সব component payload-এ Phase 11 দেয়, Accounting NBV হিসাব করে না। **AST-3 transfer**: category বদলালে দুই category-র cost ও accumulated depreciation উল্টে-পাল্টে, শুধু cost center বদলালে একই account-এ dimension-বদল line জোড়া। **AST-4 write\_off**: Phase 4-এর Debit `debit_account`, Credit `credit_account`। Depreciation event নয়, `DepreciationEngine::run` সরাসরি journal বানায়।

Edge case: আংশিক disposal, revaluation (Phase 11), CWIP থেকে capitalization, বিদেশি currency-তে কেনা asset, period বন্ধে transfer। Asset module-এর যা করতে হবে: P4-07-এর ack বজায়, `effective_date` ও `schema_version` (OD-21 অনুসারে), নতুন event type-এর emit Phase 11-এর card অনুসারে।

**ণ.৭ Bank feed (Phase 10, ভেতরের source)** (`bank_feed_event`, flag `event_intake.sources.bank`, channel `internal`)

External module নয়। Phase 10-এর `BankMatchService` যে statement line কোনো বিদ্যমান journal বা payment-এর সঙ্গে মেলেনি, সেটার জন্য ভেতরের outbox row লেখে: `statement_line_categorized`, `bank_fee_detected`, `bank_transfer_matched`। একই engine, একই catalog, কিন্তু HTTP বা বাইরের DB নেই। যে line আগেই Billing বা Procurement payment-এর সঙ্গে matched, তার জন্য event হয় না (ডবল নগদ এড়াতে)। Template **BNK-1**: Debit/Credit bank GL account (`is_bank_account`), বিপরীতে `mapping` `bank_category` বা Phase 10 category-র account। OD-84a: Phase 10-এর categorization rule আর Phase 14-এর rule কীভাবে এক হবে।

### ২৪.৩ Technical Requirements

**Service ও interface:**

| Service | কাজ | কে ডাকে |
| --- | --- | --- |
| `EventCatalog` | Event type পড়ার একমাত্র দরজা। `get(company, sourceType, eventType)`, `isActive`, `disposition` | Gateway, Engine, UI |
| `SchemaRegistry` | `validate(payload, type, version)`, `checkCompatibility(old, new, mode)`, `activate`, `deprecate`, `retire` | Gateway, `InboxService`, Admin API |
| `OutboxContractValidator` | Contract v1 field, ordering, tombstone, correlation যাচাই। Test kit-এও একই class | `InboxService::receive`, source-এর contract test |
| `IntegrationGateway` | Inbound API/webhook: auth, signature, nonce, rate limit, size limit, schema যাচাই, তারপর `InboxService::receive` | HTTP controller |
| `FinanceEventSource` (P4-04) | বাড়ে: `fetchPending`, `fetchAcceptedIds`, নতুন `fetchTotals(period)`, `contractVersion()`। Source-প্রতি একটা implementation, শুধু SELECT | `PullSourceEventsJob`, RC8 |
| `InboxService`, `InboxTransitionService` (P4-02) | বাড়ে: নতুন status, `hold_reason`, `blocked_by_inbox_id`, tombstone ও aggregate ক্রম | সব path |
| `PostingRuleEngine::build(inboxRow): DraftSpec` | Pure function। Version নির্বাচন, condition, line template, allocation, tax/FX/party/dimension। কিছু লেখে না | `DraftBuilder`, `RuleSimulator`, shadow |
| `RuleCompiler`, `ExpressionEvaluator` | JSON AST যাচাই (type, path, সীমা) ও চালানো। Deterministic | Engine, Governance |
| `MappingResolver` | `acc_posting_mapping_entries` থেকে `(map_code, lookup_key, date)` ধরে account | Engine |
| `AllocationService` | Largest-remainder rounding allocation ও balance পরীক্ষা | Engine |
| `DraftBuilder` (P4-06 সাধারণ রূপ) | `DraftSpec` নিয়ে `JournalService::createDraft` ডাকে, link লেখে, transition করে | Process job |
| `ReversalCoordinator` | Reversal ও tombstone event-এর পথ (২৪.২ ণ) | `DraftBuilder` |
| `RuleGovernanceService` | Draft, submit, approve, activate, retire, SoD, impact preview | Admin API |
| `RuleSimulator` | Simulation চালানো, ফল সংরক্ষণ | Admin API, job |
| `ReplayService`, `OverlapChecker` | Replay ও backfill পরিকল্পনা, cutover গার্ড, manual journal-এর সাথে মিল | Admin API, job |
| `DeadLetterService` | DLQ-তে ঢোকানো, replay, discard | Gateway, process job, UI |
| `SourceReconciler` | RC8.7 থেকে RC8.13 | P3-13 framework |
| `AccountingFeatureService` (P4-11) | বাড়ে: `postingRulesEnabled`, `sourceEnabled(company, source)`, `channelEnabled` | সব job |

Journal তৈরি শুধু `JournalService::createDraft` (P2-02) দিয়ে, আর status লেখে শুধু `PostingService`। কোনো Phase 14 class `acc_journals.status` বা `acc_journal_lines` সরাসরি লেখে না।

**Job ও queue topology:**

| Job | Queue | কখন | Concurrency ও lock |
| --- | --- | --- | --- |
| `PullSourceEventsJob(company, source)` | `acc-intake` | প্রতি মিনিটে, flag চালু প্রতি (company, source)-এর জন্য | `GET_LOCK('acc_intake_{company}_{source}', 0)`। একসাথে একটাই। Inbox pending সীমা ছাড়ালে batch তোলে না |
| `ProcessEventsJob(company, source)` | `acc-process` | Pull-এর পর ও প্রতি মিনিটে | `FOR UPDATE SKIP LOCKED` batch। একাধিক worker চলে। প্রতি row আলাদা transaction |
| `AcknowledgeEventJob(inboxId)` | `acc-ack` | Draft হওয়ার পর (P4-07 ধাঁচ) | Row `FOR UPDATE`, idempotent |
| `SequenceGapSweepJob` | `acc-intake` | প্রতি মিনিটে | Gap টাইমআউট পেরোলে alert ও `held_rule` নয়, `failed` (`EVC_SEQUENCE_GAP`) |
| `SimulateRuleJob(simId)` | `acc-simulate` (নিম্ন অগ্রাধিকার) | Simulation অনুরোধে | Company-প্রতি একসাথে সর্বোচ্চ ২টা |
| `ReplayJob(replayId)` | `acc-replay` (নিম্ন, throttle) | Replay অনুমোদনের পর | Replay-প্রতি একটা, cursor ধরে, ১০০ row batch |
| `SourceReconJob` | `acc-recon` | রাতে (RC8 এর সাথে) | Company-প্রতি একটা |
| `IntegrationHousekeepingJob` | `acc-intake` | দৈনিক | পুরোনো nonce ও delivery মোছা (শুধু কাজ শেষ হওয়া) |

Process job শুধু ব্যবসায়িক ক্রম মেনে row নেয়: যে row-এর `blocked_by_inbox_id` এখনো `draft_created` বা `ignored` নয়, সেটা বাদ। Queue আলাদা রাখার কারণ: replay বা simulation যেন সরাসরি intake আটকে না দেয়।

**Performance লক্ষ্য (benchmark দিয়ে প্রমাণ, P14-22):**

| বিষয় | লক্ষ্য |
| --- | --- |
| `PostingRuleEngine::build` (প্রতি event, ১০ line) | p95 ≤ ২০ ms, p99 ≤ ৬০ ms (`JournalService` বাদে) |
| এক company-তে end-to-end Draft | ≥ ১০০ event/সেকেন্ড, ৪ worker-এ |
| Claim query (১০ লক্ষ row inbox) | p95 ≤ ২০ ms (`idx_acc_fei_claim`) |
| Inbound API accept | p95 ≤ ১৫০ ms, ৫০ concurrent request-এ |
| Simulation | ১০,০০০ event ≤ ১২০ সেকেন্ড |
| Replay throttle | সর্বোচ্চ ৫০ event/সেকেন্ড, intake-কে বঞ্চিত করে না |

**Concurrency:**

- একই event দুই worker-এ: `SKIP LOCKED` ও P4-06-এর `uq_acc_journals_source_active` পথ। কারো নতুন journal তৈরি হয় না।
- Activation চলাকালীন event: worker claim-এর সময় version resolve করে, journal-এ সেই version লেখে। Version অপরিবর্তনীয় তাই cache নিরাপদ, শুধু `active_version_id` pointer বদলায়।
- দুই admin একসাথে activate: `acc_posting_rule_sets` row `FOR UPDATE` ও `version` optimistic check, একজন পায়, অন্যজন `RUL_VERSION_CONFLICT`।
- Lock ক্রম (global): journal, approval request, account, period, voucher counter। Phase 14 নিজের table-এর lock (inbox row, rule set row) এই ক্রমের **আগে নেয় না যদি journal transaction একই হয়**: Draft তৈরির transaction-এ প্রথমে inbox row (P4-এর বিদ্যমান ক্রম), তারপর `JournalService::createDraft` নিজের ক্রমে। Governance transaction কোনো ledger table লেখে না তাই account lock নেয় না, শুধু snapshot SELECT।

**Failure handling:**

| ব্যর্থতা | অবস্থা | পুনঃচেষ্টা |
| --- | --- | --- |
| Schema অবৈধ, signature ভুল | Inbox-এ ঢোকে না। API 4xx। বৈধ কিন্তু বিষয়বস্তু ভুল হলে DLQ | Source ঠিক করে আবার পাঠায় |
| Rule নেই বা অস্পষ্ট | `held_rule` | মানুষ rule ঠিক করে release |
| Mapping, currency, tax, party, dimension, period সমস্যা | যথাক্রমে `held_*` | মানুষ ঠিক করে release (bulk) |
| অসামঞ্জস্য (debit ≠ credit) | `failed`, `RUL_UNBALANCED` | Rule ঠিক করে retry |
| অস্থায়ী DB বা deadlock | `received` থাকে, `attempts + 1`, backoff | স্বয়ংক্রিয়, `event_intake.max_attempts`-এর পর `failed` ও DLQ |
| Expression সময় বা সীমা পেরোলো | `failed`, `RUL_EXPR_LIMIT` | Rule সরল করে |
| Worker মাঝপথে মরল | Transaction rollback, row `received` | পরের run-এ |
| Source ack ব্যর্থ | Journal/inbox অপরিবর্তিত (R4.13) | ack backoff |

**Security (প্রযুক্তিগত):** payload JSON গভীরতা ≤ ৩২, size ≤ `event_intake.payload.max_kb`; কোনো `eval`, dynamic class load বা SQL fragment নেই (expression শুধু AST, SQL সব binding); engine কোনো network বা file ডাকে না; secret শুধু secret store-এ, DB-তে reference; signature তুলনা constant-time; endpoint-প্রতি আলাদা key; sensitive payload column-level encrypted ও কেবল `accounting.intake.sensitive.view`-ওয়ালা দেখে।

**২৪.৪ থেকে ২৪.১৪** (Data Model ও API, Business Rules ও Configuration, Operational Workflow, Dependencies, Implementation Tasks, Testing, Security ও Audit, Deliverables, Acceptance Criteria, Entry ও Exit, Open Decisions) এই section-এর বাকি অংশ আলাদা tab-এ: Phase 14 ২৪.৪ থেকে ২৪.১৪ (Posting Rules, Source Integrations)

## ২৫. Phase 15: Multi-Entity, Intercompany ও Consolidation

একাধিক company (legal entity) মিলিয়ে একটা group-এর একীভূত আর্থিক চিত্র বানানো। Entity ledger কখনো ছোঁয়া হয় না, company isolation অক্ষত থাকে, আর intercompany লেনদেন দুই পক্ষে আলাদা Draft হয়ে মেলে। Error prefix `GRP_`, `ICO_`, `CON_`। Capability flag `consolidation.enabled` (Platform Setting, Default বন্ধ)। Reconciliation `RC15`, `RC18`। OD পরিসর OD-87 থেকে OD-94।

### ২৫.১ Objective ও Scope

**Objective:** একটা group-এর সব সদস্য company-র posted ledger পড়ে, group chart of accounts-এ map করে, দরকারে reporting currency-তে translate করে, intercompany অংশ eliminate করে, একীভূত Trial Balance, P&L, Balance Sheet, Cash Flow (indirect) ও equity roll-forward দেওয়া। সবকিছু আলাদা consolidation ledger-এ থাকে, তাই A7 (entity ledger-এ অন্য কেউ লেখে না) বহাল।

**Baseline অনুমান:** G1 (একটা company = একটা legal entity, OD-111), G3 (functional currency = company base currency), G4 (১২ মাসের calendar period, OD-110), G9 (system-তৈরি journal সবসময় Draft)। Reporting currency প্রতি group-এ একটা, consolidation-এর জন্য (OD-113 বৈশ্বিক)।

**Scope-এর ভেতরে:**

- Entity group, সদস্য company, মালিকানার গাছ (parent ও subsidiary), মালিকানা শতাংশ, effective তারিখ।
- Group-level permission: `acc_group_grants` দিয়ে স্পষ্ট grant, `GroupService` ছাড়া entity data পড়া নয়।
- Group chart of accounts mapping (entity account থেকে group account)।
- Intercompany transaction: initiator নিজের Draft journal বানায়, counterparty তার নিজের Draft পায় event দিয়ে, pair id দিয়ে মেলানো।
- Intercompany balance confirmation, mismatch handling, `RC15`।
- Elimination engine: intercompany receivable ও payable, revenue ও expense, investment বনাম equity।
- Currency translation: current rate, average rate, historical rate, cumulative translation adjustment (CTA)।
- Non-controlling interest (NCI) ও goodwill (baseline হিসাব, বাকিটা OD-88)।
- Consolidation run: preview, final run, lock, rerun (versioning), run-এর মধ্যে তুলনা।
- Consolidation adjustment journal (manual, approved) আলাদা consolidation ledger-এ।
- Consolidated report ও drill-down (group, entity, journal), `RC18`।
- Group audit trail, security, performance, UAT script।

**Scope-এর বাইরে (কেন):**

- Statutory consolidated report-এর নির্দিষ্ট রূপ: jurisdiction ও standard নির্ভর (OD-112)।
- Equity method ও proportional method-এর হিসাব: schema-তে জায়গা রাখা হয়েছে, কিন্তু কোন method লাগবে তা OD-87। তাই baseline-এ শুধু `full`।
- Unrealized profit-এর স্বয়ংক্রিয় হিসাব: ledger-এ inventory-র কোন অংশ এখনো buyer-এর হাতে তা নেই (OD-89)। baseline-এ manual adjustment।
- Hyperinflation translation, segment reporting, deconsolidation ও disposal accounting: client-এর চাহিদা নেই। লাগলে manual adjustment, নয়তো নতুন OD।
- Parallel ledger বা একাধিক book-এর consolidation: OD-109, G2।
- Tax consolidation (Phase 8), consolidated budget (Phase 13): আলাদা phase-এর কাজ।
- Real-time consolidation: প্রতি period শেষে চলে, কারণ posted ও closed ledger-ই একমাত্র নির্ভরযোগ্য উৎস।
- Entity ledger বা entity table-এ কোনো লেখা: A7 লঙ্ঘন হবে।

### ২৫.২ Functional Requirements

**ক. Group model।**

- একটা company একাধিক group-এর সদস্য হতে পারে (যেমন statutory group, management group)। একই group-এ একই সময়ে একবার।
- Group-এ একটা root (ultimate parent) থাকে। প্রতিটা সদস্যের একটাই `parent_member_id`। Indirect মালিকানা পথের শতাংশের গুণফল।
- মালিকানা শতাংশ, voting শতাংশ, method (`full`, `proportional`, `equity`), `effective_from`, `effective_to` প্রতি সদস্যে।
- Group সক্রিয় করার আগে `draft`। সক্রিয় করতে precondition (২৫.৫-এ)।

**খ. Company isolation ও group grant।**

- Group user entity data পড়ে শুধু `GroupService` দিয়ে, আর শুধু `acc_group_grants`-এর স্পষ্ট grant থাকলে। Grant কোনো entity-তে লেখার অধিকার দেয় না।
- Cross-company লেখা একটাই পথে: intercompany pair তৈরি (`acc_intercompany_txns` সারি, group-scoped)। অন্য company-র journal, inbox বা ledger-এ কেউ সরাসরি লেখে না।
- R1.13 ও AC1.1 অক্ষত: company X-এর user group-এর অন্য company দেখে না, যদি সে group grant না পায়।

**গ. Group CoA mapping।**

- প্রতিটা entity account একটা group account-এ map হয় (`acc_group_account_map`)। Group account হলো `coa_company_id` company-র account (baseline, OD-92)।
- Coverage report: কোন account-এ balance আছে কিন্তু map নেই।
- প্রতি account-এ translation rate class (current, average, historical)। না দিলে account type থেকে।

**ঘ. Intercompany transaction।**

- Initiator company নিজের context-এ Draft journal বানায় (`journal_type = intercompany`, `intercompany_pair_id`, line-এ `ic_company_id`)।
- Counterparty একটা event পায়। তার নিজের intake job Phase 4 inbox ও Phase 14 rule দিয়ে নিজের Draft বানায় (mirror)। Mirror auto-post হয় না।
- দুই পক্ষ নিজেদের approval ও posting সম্পন্ন করে। Pair id দিয়ে মেলে।
- ভিন্ন period ও ভিন্ন currency সমর্থিত (Phase 7)।

**ঙ. Matching, mismatch, confirmation।**

- Matching job pair-এর দুই journal পড়ে `matched` বা `mismatched` করে।
- Mismatch-এর কারণ (fx, amount, period, missing) আর সমাধানের পথ (Draft সংশোধন, adjustment, approve-সহ write-off)।
- প্রতি period শেষে দুই পক্ষের balance confirmation। `RC15` দুই দিক মেলায়।

**চ. Elimination।**

- Rule-ভিত্তিক, ক্রমানুসারে, deterministic। Intercompany balance, intercompany P&L, investment বনাম equity।
- অমিল (difference) আলাদা account-এ দেখা যায়, চুপচাপ লুকানো যায় না।
- সব elimination journal consolidation ledger-এ, Draft হয়ে, approve ও post হয়ে।

**ছ. Currency translation।**

- Asset ও liability current rate-এ, P&L average rate-এ, equity historical rate-এ। Rate শুধু Phase 7 `FxService::rate` থেকে। Rate না থাকলে run থামে, অনুমান নয়।
- CTA এক equity account-এ, plug হিসেবে।

**জ. NCI ও goodwill।**

- `full` method-এ ১০০% line যোগ, NCI আলাদা equity line। Baseline: NCI = অনুপাতিক নিট সম্পদ, goodwill = cost বিয়োগ অনুপাতিক acquired equity (OD-88)।

**ঝ. Consolidation run।**

- Preview (post হয় না), final run, lock। Lock-এর পর বদল নেই। Rerun নতুন `version_no`, reason সহ, approve সহ। দুই run-এর তুলনা।
- Final run শুধু entity period `soft_closed` বা `closed` হলে (setting)।

**ঞ. Consolidation adjustment।**

- Manual adjustment journal (reason, attachment, maker-checker) consolidation ledger-এ। Entity ledger-এ নয়।

**ট. Report ও drill-down।**

- Consolidated TB, P&L, BS, Cash Flow (indirect), equity roll-forward। Drill-down: group account, তারপর entity, তারপর entity account, তারপর journal (read-only, grant সহ)।
- Official report শুধু locked run থেকে।

**ঠ. Reconciliation ও audit।**

- `RC15` (intercompany দুই দিক মেলা) ও `RC18` (consolidated = entity-র যোগফল ± translation ± elimination)।
- প্রতিটা group-level কাজ ও প্রতিটা entity data পাঠ audit-এ।

২৫.৩ থেকে ২৫.১৪ অংশ (Technical Requirements থেকে Open Decisions) আলাদা tab-এ, কারণ এই tab-এর block সীমা প্রায় শেষ: Phase 15 ২৫.৩ থেকে ২৫.১৪ (Multi-Entity, Consolidation)

## ২৬. Phase 16: Compliance, Audit, Data Lifecycle, Migration ও API

Phase 16 enterprise accounting-কে নিরীক্ষা-যোগ্য, প্রমাণযোগ্য ও বাইরের সাথে যুক্ত করে। এই Phase ledger-এ কোনো row লেখে না। Migration-এর journal তৈরি হয় শুধু `JournalService` দিয়ে, Draft হিসেবে। `acc_journals.status` বদলায় শুধু `PostingService` (P2-12)। Public API-ও একই পথ মানে: Draft তৈরি করে, Post করে না। দুটো নতুন flag: `compliance.enabled` ও `public_api.enabled`, দুটোই default বন্ধ, Platform Setting।

### ২৬.১ Objective ও Scope

**Objective:** একজন external auditor যেন একটা সময়-সীমাবদ্ধ, শুধু-পড়া অনুমতিতে পুরো প্রমাণ (trial balance, GL, journal, attachment, approval trail, control evidence, reconciliation ইতিহাস, period close প্রমাণ, user access তালিকা) নিজে পেতে পারে। Audit log যেন বদলালে ধরা পড়ে। অনুমতির পর্যালোচনা ও SoD দ্বন্দ্ব নিয়মিত দেখা যায়। পুরোনো system-এর CoA, party, rate, opening balance ও ঐতিহাসিক journal maker-checker নিয়ন্ত্রণে আসে। Data কখনো মোছা হয় না, বন্ধ বছর cold storage-এ যায় ও আবার query করা যায়। বাইরের system নিরাপদ API ও signed webhook দিয়ে যুক্ত হয়। Backup, restore, DR ও observability প্রমাণসহ চলে।

**ছয়টা ফল:**

1. **Auditor access ও audit pack:** `acc_auditor_grants`, `acc_audit_requests`, audit pack export job।
2. **Tamper evidence:** `acc_audit_chain`, verification job, alert।
3. **Governance:** `acc_access_reviews` (ও items), `acc_sod_conflicts`, configuration change report।
4. **Migration:** `acc_migration_batches`, `_mappings`, `_rows`, dry-run, validation report, reconciliation, rollback।
5. **Data lifecycle:** `acc_retention_policies`, `acc_legal_holds`, `acc_archive_batches`, `acc_statutory_export_defs`, privacy ও encryption।
6. **Integration ও resilience:** `acc_api_clients`, `acc_api_scopes`, `acc_webhook_endpoints`, `acc_webhook_deliveries`, backup, DR, observability, performance, security test।

**Scope ও সীমা:**

| এলাকা | ভেতরে | বাইরে | বাইরের কেন |
| --- | --- | --- | --- |
| External auditor | Time-bound read-only grant, session log, PBC তালিকা, audit pack | Auditor-এর নিজের workpaper সংরক্ষণ, auditor-কে কোনো লেখার অধিকার | Audit স্বাধীনতা। Auditor ledger বদলাতে পারলে নিরীক্ষা অর্থহীন |
| Tamper evidence | `activity_logs`-এর `accounting.*` event-এর hash chain, দৈনিক anchor, verification | Hardware WORM, blockchain | খরচ বেশি, দরকার হলে OD-96-এ external anchor |
| Access review ও SoD | Campaign, reviewer, evidence, SoD matrix, exception, config change report | RBAC-এ নিজে role বদলানো, HR joiner-mover-leaver | RBAC মালিক ERPFlow Platform (G8-এর মতো, Accounting শুধু পড়ে)। OD-98a |
| Migration | CoA, party, rate, opening balance, ঐতিহাসিক journal। Dry-run, rollback, reconciliation | Live দুই-মুখী sync, source system-এর business logic পুনর্গণনা (যেমন inventory valuation) | Cutover একবারের কাজ। Sync Phase 14 integration-এর কাজ |
| Statutory export | Generic exporter, versioned field mapping, SAF-T-ধাঁচের উদাহরণ | কোনো দেশের চূড়ান্ত ফাইল সনদ, e-invoicing জমা | Jurisdiction অনির্ধারিত (OD-99, OD-112)। Submission দায় Billing (OD-118) |
| Retention, archive, hold | Policy, legal hold, বন্ধ বছর cold storage, restore, partition কৌশল | Ledger-এর physical delete, tape ও storage hardware | Posted data কখনো মোছা নয় (A1-A10) |
| Privacy ও encryption | Masking, PII access log, subject request, field encryption, key rotation, secret | অন্য module-এর PII, disk ও DB TDE সেটআপ, consent management | Platform ও infra দায়। Accounting নিজের field ও secret সামলায় |
| Public API ও webhook | `/api/v1`, scope, rate limit, idempotency, OpenAPI, signed webhook, replay | GraphQL, SDK, marketplace, API থেকে Post | Baseline-এ Post শুধু UI-তে (G9)। OD-103 |
| DR, observability, performance | RPO ও RTO প্রমাণ, restore verification, drill, SLO, dashboard, queue topology, read replica | Cloud vendor বাছাই, infra provisioning | OD-115, OD-114 |
| Security test ও compliance mapping | Test plan, compliance mapping table, UAT script, runbook | বাইরের সনদ (SOC 2, ISO 27001) পাওয়া | সনদ প্রতিষ্ঠানের কাজ। এই Phase শুধু প্রমাণ জোগায় |

**Baseline অনুমান (এগুলো বদলালে OD-তে বলা আছে কী ভাঙবে):**

- Auditor ERPFlow user হিসেবে আসে, আলাদা portal নয় (OD-95)।
- Audit chain DB-তে, external anchor ছাড়া শুরু (OD-96)।
- ঐতিহাসিক journal full আকারে আসতে পারে, তবে default opening balance আর শেষ খোলা বছরের journal (OD-97)।
- Retention 7 বছর (OD-116), RPO 15 মিনিট ও RTO 4 ঘণ্টা (OD-114), গণনা: partition দরকার কি না OD-119 বলবে (OD-100a)।
- API baseline: পড়া ও Draft তৈরি। Post নয় (OD-103)।

### ২৬.২ Functional Requirements

**ক. External auditor access**

- Auditor-এর grant কোম্পানি, engagement, scope (fiscal year, period, area) আর `valid_from` থেকে `valid_to` ধরে। সর্বোচ্চ মেয়াদ `auditor.max_grant_days`।
- Grant-এর অনুরোধকারী ও অনুমোদনকারী আলাদা। Auditor নিজে grant অনুমোদন করতে পারে না।
- Grant active থাকলে auditor শুধু GET চালাতে পারে। কোনো Draft, Submit, Approve, Post, setting বদল নেই।
- Party নাম, tax id, bank তথ্য default মুখোশ পরা। Unmask শুধু `allow_pii = 1` ও দ্বিতীয় অনুমোদনে, প্রতিটা unmask পড়া log হয়।
- Export default বন্ধ। চালু হলেও scope-এর মধ্যে, PII column বাদ, watermark সহ।
- প্রতিটা session শুরু, report দেখা, download audit event। ১৫ মিনিট idle হলে session শেষ। মেয়াদ গেলে বা revoke হলে পরের request-এই `AUD_GRANT_EXPIRED`।

**খ. Audit request (PBC) তালিকা**

- Auditor বা Controller engagement ধরে PBC item তৈরি করে (title, area, due date)।
- Assignee উত্তর দেয়: লেখা, বা export/pack সংযুক্ত। Auditor গ্রহণ বা ফেরত দেয়। বন্ধ item বদলায় না।
- Dashboard-এ খোলা, দেরি, উত্তর দেওয়া item গণনা।

**গ. Audit pack generation**

- একটা export job (P3-10-এর কাঠামো) চালায়। ভেতরে: trial balance, GL, journal ও attachment, approval trail (`acc_approval_actions`), control evidence (reconciliation `acc_reconciliation_runs`, period close `acc_period_events`), user access list, config change report, SoD report।
- Pack-এ `manifest.json`: প্রতিটা ফাইলের নাম, row সংখ্যা, SHA-256, `as_of` সময়, filter, কে চাইল।
- Pack তৈরির শুরুতে RC1 থেকে RC9 snapshot নেয়। ব্যর্থ check থাকলে pack আটকায় না, manifest-এ পরিষ্কার লেখা থাকে।
- Open period থাকলে ওই অংশ `DRAFT` watermark পায়।

**ঘ. Tamper-evident audit chain**

- `activity_logs`-এর `accounting.*` event নির্দিষ্ট ব্যবধানে block করে digest নেওয়া হয়। প্রতিটা block আগের block-এর hash জুড়ে `chain_hash` বানায়।
- Verification job পুরো chain বা একটা range আবার হিসাব করে মেলায়। অমিল হলে critical alert, কোন block ও কোন id range তা সহ।
- Chain নিজে append-only। ভেতরের বদল ধরা পড়ে, বদল ঠেকানো যায় না। এই সীমা Compliance mapping-এ লেখা।

**ঙ. Access review campaign**

- Campaign খোলার সময় Accounting-সংক্রান্ত permission-ধারী সব user-এর snapshot জমে (অপরিবর্তনীয়)।
- প্রতিটা (user, role) item-এ reviewer সিদ্ধান্ত দেয়: keep, revoke, modify, কারণ সহ। Reviewer নিজের item দেখে না।
- Revoke সিদ্ধান্ত RBAC owner-এর জন্য কাজের তালিকা হয়। Campaign বন্ধের আগে নতুন snapshot মিলিয়ে দেখা হয় revoke সত্যিই হয়েছে কি না।
- Campaign-এর evidence (সিদ্ধান্ত, সময়, snapshot hash) audit pack-এ যায়।

**চ. SoD matrix, report ও configuration change report**

- Matrix হলো permission জোড়ার তালিকা যা একজনের হাতে থাকা উচিত নয় (যেমন journal তৈরি ও post)। Version সহ।
- Detection job প্রতিদিন কে কোন জোড়া ধরে আছে তা finding হিসেবে রাখে।
- Exception: কারণ, compensating control, সর্বোচ্চ মেয়াদ, আলাদা অনুমোদন। মেয়াদ গেলে finding আবার খোলা।
- Configuration change report: কোনো সময়ের মধ্যে `acc_settings_history`, setting change request, mapping বদল, approval rule বদল একসাথে, কে কী থেকে কী করল।

**ছ. Data import ও migration**

- পাঁচ ধরনের batch: `coa`, `parties`, `rates`, `opening_balance`, `historical_journals`। প্রতিটা staging থেকে dry-run, validation report, mapping নিশ্চিতকরণ, তারপর maker-checker অনুমোদন।
- Opening balance bulk import এই Phase-এ scope-এর ভেতরে (আগের release-এ বাইরে ছিল)।
- Commit সবসময় সংশ্লিষ্ট service দিয়ে: CoA `AccountService`, party `PartyService`, rate `FxService`-এর rate API, journal `JournalService`।
- Source total বাধ্যতামূলক। Imported total মিলতে হবে (RC1i)।
- Rollback শুধু unposted batch-এর। Posted import অপরিবর্তনীয় ও batch-এর সাথে স্থায়ী লিংক।
- Cutover guidance: freeze সময়, শেষ যাচাই, parallel সময়ের নিয়ম।

**জ. Statutory export framework**

- Generic exporter: dataset (GL line, trial balance, party, tax line), field mapping, transform, format (csv, xml, json, xlsx)।
- Definition versioned, Maker-Checker-এ সক্রিয়। Run একটা version-এ আটকানো। SAF-T-ধাঁচের definition শুধু নমুনা হিসেবে seed।

**ঝ. Retention, archive, legal hold**

- Data class ধরে retention policy। Ledger ও audit log কখনো purge হয় না।
- বন্ধ fiscal year cold storage-এ copy, integrity check, তারপর অনুমোদিত হলে hot থেকে detach (delete নয়)। Restore queryable।
- Legal hold কোনো archive, detach বা anonymize আটকায়।
- Partitioning পরিকল্পনা `acc_journal_lines` ও `acc_journals`-এর জন্য, company ও বছর ধরে। OD-119-এর সংখ্যা এলে সিদ্ধান্ত।

**ঞ. Privacy, encryption, secrets**

- Party PII masking, PII access log, subject request কর্মধারা। নিয়ম: ledger record retained।
- Bank details ও API/webhook secret-এ field-level encryption। Key rotation job। Secret কখনো log-এ বা response-এ plaintext নয়।

**ট. Public REST API**

- `/api/v1`, OAuth2 client credentials-ধাঁচের token (OD-103), scope = permission-এর উপসেট, প্রতি company।
- Rate limit, `Idempotency-Key`, cursor pagination, একরকম error contract, OpenAPI spec deliverable।

**ঠ. Webhook**

- Endpoint নিবন্ধন (HTTPS), event বাছাই, HMAC signature, backoff সহ retry, delivery log, replay।

**ড. Backup, restore, DR**

- RPO ও RTO লক্ষ্য (OD-114)। Restore verification job: আলাদা পরিবেশে restore, তারপর reconciliation চালিয়ে আগের snapshot-এর সাথে মেলানো। DR drill ও failover runbook।

**ঢ. Observability, performance, scalability**

- SLO, dashboard, structured log, `correlation_id`, capacity plan। Report-এর জন্য read replica, queue topology, index review, বড় table-এর maintenance।

**ণ. Security testing, compliance mapping, UAT**

- Security test plan (API, auditor isolation, IDOR, injection, secret leak)।
- Compliance mapping table: control objective, feature, evidence। UAT script ও runbook।

### ২৬.৩ Technical Requirements

| বিষয় | প্রয়োজন |
| --- | --- |
| Service | `AuditorAccessService` (grant জীবন, request-এ `authorize`), `AuditorGuard` middleware, `AuditRequestService`, `AuditPackBuilder`, `AuditChainService` (`anchor`, `verify`), `AccessReviewService`, `SodService` (detect, exception), `ConfigChangeReport`, `ImportService` (batch জীবন, loader প্রতি kind-এ একটা), `MigrationReconciler`, `StatutoryExporter`, `RetentionService`, `LegalHoldService`, `ArchiveService`, `PrivacyService`, `KeyService` (envelope encryption), `ApiGateway` (middleware ধারা), `WebhookService`, `RestoreVerifier`, `MetricsEmitter`। সব service company context সহ, `AccountingAudit::log` ব্যবহার করে |
| Ledger সীমা | কোনো class `acc_journal_lines` বা `acc_journals`-এ লেখে না। Migration ও API Draft তৈরি করে শুধু `JournalService` দিয়ে। Status বদল শুধু `PostingService`। Static test এটা প্রমাণ করে |
| Job ও queue | `accounting-audit` (pack, chain anchor ও verify), `accounting-migration` (parse, validate, commit; P6-07-এর `accounting-import` থেকে আলাদা যাতে বড় migration সাধারণ import আটকায় না), `accounting-archive`, `accounting-webhook`, `accounting-compliance` (SoD detect, access review, privacy, retention scan), `accounting-ops` (restore verify, key rotation, capacity snapshot)। সব job `AccountingJob` base (P1-12), company প্রতি একটা চলমান job যেখানে লিখে |
| Concurrency | একই ধরনের সক্রিয় job-এর জন্য DB unique (generated `active_key`) যেমন P3-10। Archive ও legal hold একই company advisory lock `acc_hold_<company_id>` নেয়। নতুন lock সব বিদ্যমান ledger lock ক্রমের (journal, approval request, account, period, voucher counter) শেষে, এই Phase-এর table-এ। Ledger lock এই Phase-এ কখনো মাঝখানে ঢোকে না |
| Performance লক্ষ্য | Audit pack: ১ লাখ line ১০ মিনিটের মধ্যে। Chain anchor: ১০,০০০ log row ১০ সেকেন্ড। Chain full verify: ১০ লাখ row ১৫ মিনিট। Migration validate: ৫০,০০০ row ৫ মিনিট। Migration commit: ২০,০০০ row ৩০ মিনিট। Archive: এক বছর, ৫০ লাখ line, ২ ঘণ্টা। API read p95 300 ms, write p95 600 ms। Webhook প্রথম চেষ্টা p95 30 সেকেন্ডের মধ্যে। SoD detect ২ মিনিট। (সংখ্যা P16-18 benchmark-এ নিশ্চিত হবে) |
| Failure handling | প্রতিটা job idempotent ও checkpoint সহ। ব্যর্থ হলে backoff সহ retry, শেষে `failed` ও `acc_alert_state` alert (P5-03)। কোনো half-written ফল দেখা যায় না: output ফাইল temp নামে লেখা, checksum যাচাই, তারপর rename |
| Read replica | Audit pack, statutory export, SoD ও config report read replica থেকে পড়তে পারে, কিন্তু একই transaction snapshot-এর `as_of`, আর replica lag `reporting.max_replica_lag_seconds`-এর বেশি হলে primary-তে ফিরে যায় |
| Storage | Pack, archive, export ফাইল webroot-এর বাইরে, encrypted object storage, signed short-lived link। Archive manifest আলাদা checksum |
| Security | Auditor, API client ও webhook endpoint প্রতিটা আলাদা authentication path। সব ক্ষেত্রে company boundary, rate limit ও audit। SSRF সুরক্ষা webhook-এ। Secret শুধু encrypted, response-এ একবার |
| Observability | প্রতিটা request ও job-এ `correlation_id`। Structured JSON log, PII ও secret ছাড়া। Metrics: job duration, queue depth, error rate, chain lag |
| Rollback | Flag বন্ধ করলে নতুন grant, API, webhook, archive, migration থামে। ডেটা থাকে। Expand-only migration, `down()` নিরাপদ না হলে মন্তব্যে লেখা |

### ২৬.৪ Data Model ও API

Phase 16 ১৭টা নতুন table যোগ করে (সব `acc_` prefix, সব কোম্পানি-ভিত্তিক table-এ `uq (company_id, id)` ও composite FK)। পুরো column spec Pack ২১-এ।

| Table | মূল column | নোট |
| --- | --- | --- |
| `acc_auditor_grants` | `auditor_user_id`, `firm_name`, `engagement_code`, `scope` (JSON), `valid_from`, `valid_to`, `status`, `allow_pii`, `allow_export`, `requested_by`, `approved_by`, `last_access_at`, `access_count` | Scope approve-এর পর অপরিবর্তনীয় (trigger)। বদলাতে নতুন grant |
| `acc_audit_requests` | `engagement_code`, `request_kind` (`pbc`, `privacy_subject`), `request_no`, `title`, `area`, `due_date`, `status`, `assigned_to`, `response_note`, `response_export_id` | PBC তালিকা ও উত্তর। Privacy subject request-ও এখানে (R16.34) |
| `acc_audit_chain` | `seq`, `from_log_id`, `to_log_id`, `log_count`, `digest`, `prev_hash`, `chain_hash`, `anchored_at`, `anchor_ref`, `key_id` | **Append-only** (`Schema::appendOnly()`)। `activity_logs`-এ কিছু লেখে না |
| `acc_access_reviews` | `review_no`, `title`, `scope`, `status`, `opened_at`, `due_date`, `completed_at`, `snapshot_hash`, `items_total`, `items_decided` | Campaign |
| `acc_access_review_items` | `review_id`, `user_id`, `role_code`, `reviewer_id`, `decision`, `decided_at`, `reason`, `snapshot` (JSON), `revoke_confirmed` | প্রতি (user, role) একটা item |
| `acc_sod_conflicts` | `record_kind` (`rule`, `finding`, `exception`), `rule_code`, `perm_a`, `perm_b`, `severity`, `user_id`, `status`, `exception_reason`, `compensating_control`, `exception_expires` | Matrix, finding ও exception এক table-এ, `record_kind` দিয়ে আলাদা |
| `acc_migration_batches` | `batch_no`, `kind`, `source_system`, `file_sha256`, `source_total_debit`, `source_total_credit`, `source_row_count`, `cutover_date`, `mode`, `status`, `validation_summary`, `journal_import_batch_id` | `opening_balance` kind-এ একটা active batch (generated unique) |
| `acc_migration_mappings` | `batch_id`, `map_type`, `source_code`, `target_code`, `target_id`, `status` | Source code থেকে ERPFlow code-এ mapping |
| `acc_migration_rows` | `batch_id`, `row_no`, `group_ref`, `payload` (JSON), `payload_hash`, `status`, `error_codes`, `target_type`, `target_id` | Staging, কখনো ledger নয় |
| `acc_archive_batches` | `fiscal_year`, `status`, `hot_state`, `storage_uri`, `manifest`, `journals_count`, `lines_count`, `total_debit`, `total_credit`, `checksum_sha256`, `encryption_key_id` | Manifest ও checksum archived হওয়ার পর অপরিবর্তনীয় (trigger) |
| `acc_retention_policies` | `data_class`, `retention_years`, `action_after`, `effective_from`, `status`, `version` | Ledger ও audit log-এ purge action নিষিদ্ধ (CHECK) |
| `acc_legal_holds` | `hold_no`, `title`, `scope_type`, `scope_ref` (JSON), `status`, `case_ref`, `placed_by`, `released_by` | Hold থাকলে archive, detach, anonymize নেই |
| `acc_statutory_export_defs` | `code`, `jurisdiction`, `version`, `dataset`, `format`, `field_map` (JSON), `filters`, `status`, `effective_from`, `checksum` | Active version অপরিবর্তনীয়, বদলাতে নতুন version |
| `acc_api_clients` | `client_name`, `public_id`, `secret_hash`, `status`, `owner_user_id`, `ip_allowlist`, `rate_limit_per_min`, `token_ttl_minutes`, `expires_at`, `rotated_at` | Secret শুধু hash, plaintext একবার দেখানো |
| `acc_api_scopes` | `client_id`, `scope`, `permission_slug`, `access`, `granted_by`, `revoked_at` | Scope = permission-এর উপসেট |
| `acc_webhook_endpoints` | `client_id`, `url`, `event_types`, `secret_enc`, `secret_key_version`, `status`, `consecutive_failures` | Signing secret field-level encrypted |
| `acc_webhook_deliveries` | `endpoint_id`, `event_id`, `event_type`, `replay_seq`, `payload_hash`, `attempt`, `status`, `http_status`, `next_attempt_at`, `delivered_at` | Delivery log, replay নতুন `replay_seq` |

**বিদ্যমান table-এ expand (শুধু যোগ):**

- `acc_journal_import_batches` (P6-07): নতুন nullable `migration_batch_id` ও composite FK। `origin = migration` মান। Migration-এর historical journal এই pipeline দিয়ে Draft হয়।
- `acc_report_exports` (P3-10): নতুন `report` মান `audit-pack` ও `statutory:<code>`। Column বদল নেই।
- `acc_reconciliation_runs` (P3-13): `check_code` নতুন মান `RC1i`, `RC1a`, `RC1r`, `RC9c`। Column বদল নেই।
- `activity_logs` বাইরের table, শুধু পড়া। Platform-কে `(event, id)` index দিতে বলতে হবে (নির্ভরতা অংশ)।

**Admin API (prefix `/accounting`, সব write-এ `Idempotency-Key`):**

| Endpoint | কাজ | Permission |
| --- | --- | --- |
| `POST/GET /auditor-grants`, `POST /auditor-grants/{id}/approve`, `/revoke` | Grant জীবন | `accounting.auditor_grant.manage`, `.approve` |
| `GET /auditor/session` | Auditor নিজের grant ও বাকি সময় | `accounting.auditor.view` |
| `POST/GET /audit-requests`, `POST /{id}/respond`, `/accept`, `/close` | PBC | `accounting.audit_request.manage`, `.respond` |
| `POST /audit-packs`, `GET /audit-packs/{id}`, `GET .../download` | Pack | `accounting.audit_pack.generate`, `.download` |
| `GET /audit-chain`, `GET /audit-chain/status`, `POST /audit-chain/verify` | Chain | `accounting.audit_chain.view`, `.verify` |
| `POST/GET /access-reviews`, `POST /{id}/items/{item}/decide`, `POST /{id}/complete`, `GET /{id}/evidence` | Review | `accounting.access_review.manage`, `.attest` |
| `GET/POST /sod/rules`, `GET /sod/findings`, `POST /sod/exceptions`, `POST /sod/exceptions/{id}/approve` | SoD | `accounting.sod.view`, `.manage`, `.approve` |
| `GET /reports/config-changes?from=&to=` | Config change report | `accounting.config_report.view` |
| `POST /migrations` (multipart), `GET /migrations/{id}`, `PUT .../mappings`, `POST .../validate`, `GET .../report`, `POST .../submit`, `.../approve`, `.../commit`, `.../rollback`, `GET .../rows` | Migration | `accounting.migration.manage`, `.approve`, `.commit` |
| `GET/POST /statutory-defs`, `POST /statutory-defs/{id}/activate`, `POST /statutory-exports` | Statutory | `accounting.statutory_export.manage`, `.run` |
| `GET/PUT /retention-policies`, `POST/GET /legal-holds`, `POST /legal-holds/{id}/release` | Retention, hold | `accounting.retention.manage`, `.approve`, `accounting.legal_hold.manage` |
| `POST /archive-batches`, `POST /{id}/detach`, `POST /{id}/restore`, `GET /archive-batches` | Archive | `accounting.archive.run`, `.approve`, `.restore` |
| `POST /privacy/requests`, `GET /privacy/requests` | Subject request | `accounting.privacy.manage` |
| `POST/GET /api-clients`, `POST /{id}/rotate-secret`, `/suspend`, `PUT /{id}/scopes` | API client | `accounting.api_client.manage`, `.approve` |
| `POST/GET /webhook-endpoints`, `GET /webhook-deliveries`, `POST /webhook-deliveries/{id}/replay` | Webhook | `accounting.webhook.manage`, `.replay` |
| `POST /ops/restore-verification`, `GET /ops/slo` | DR, SLO | `accounting.ops.run`, `.view` |

**Public API (prefix `/api/v1`):**

| Endpoint | কাজ | Scope |
| --- | --- | --- |
| `POST /oauth/token` | Client credentials থেকে short-lived token | - |
| `GET /accounts`, `GET /periods` | CoA ও period পড়া | `coa:read`, `periods:read` |
| `GET /journals`, `GET /journals/{id}` | Posted ও Draft journal পড়া, cursor | `journals:read` |
| `POST /journals` | **শুধু Draft** (`JournalService`), `Idempotency-Key` বাধ্যতামূলক | `journals:draft:write` |
| `GET /reports/trial-balance`, `GET /reports/general-ledger` | Report | `reports:read` |
| `GET /parties` | Party (মুখোশ পরা) | `parties:read` |
| `GET /events?after=` | Event poll (webhook-এর বিকল্প) | `events:read` |
| `POST/GET/DELETE /webhook-subscriptions` | নিজের endpoint নিয়ন্ত্রণ | `webhooks:manage` |

Post, approve, setting বদল, period বন্ধ: Public API-এ নেই (R16.36, OD-103)।

### ২৬.৫ Business Rules ও Configuration

**Business rule:**

| # | নিয়ম | কীভাবে নিশ্চিত |
| --- | --- | --- |
| R16.1 | Phase 16 ledger-এ লেখে না। Journal তৈরি শুধু `JournalService` (Draft), status বদল শুধু `PostingService` | Static test, DB grant |
| R16.2 | Auditor grant সবসময় time-bound। `valid_to - valid_from <= auditor.max_grant_days` | Service ও CHECK, expiry job |
| R16.3 | Auditor read-only: `AuditorGuard` শুধু GET-কে ছাড়ে। Auditor role কখনো create, submit, approve, post, manage permission পায় না | Route allowlist test, permission matrix test |
| R16.4 | Grant-এর অনুরোধকারী, অনুমোদনকারী ও auditor তিনজন আলাদা | CHECK `approved_by <> requested_by`, service |
| R16.5 | Auditor PII মুখোশ পরা। `allow_pii = 1` লাগলে দ্বিতীয় অনুমোদন, প্রতি unmask পড়া log | `PrivacyService::mask`, audit event |
| R16.6 | Auditor export default বন্ধ। চালু হলেও scope-এর বাইরের ডেটা ও PII column নেই, watermark সহ | Export filter test |
| R16.7 | প্রতি auditor session শুরু, report দেখা, download log। ১৫ মিনিট idle-এ session শেষ | Middleware, audit event |
| R16.8 | Audit pack একটা snapshot (`as_of`)। Manifest-এ প্রতি ফাইলের SHA-256। Open period `DRAFT` watermark | Builder test |
| R16.9 | Pack-এ RC1 থেকে RC9 snapshot থাকে। ব্যর্থ check pack আটকায় না, manifest-এ লেখা (R3.9-এর মতো) | Test |
| R16.10 | Audit chain append-only। একই log id range দুইবার anchor নয় | `uq (company_id, to_log_id)`, trigger |
| R16.11 | Anchor শুধু `anchor_lag` (৫ মিনিট) আগের log ধরে। পরে ঢোকা পুরোনো id হলে `late_arrival` warning, break নয় | Verifier test |
| R16.12 | Chain break = critical alert। Posting থামে না, কিন্তু incident runbook শুরু হয় | `acc_alert_state` (P5-03) |
| R16.13 | Access review snapshot অপরিবর্তনীয়। Reviewer নিজের item দেখে না | `ck reviewer_id <> user_id` |
| R16.14 | Undecided item থাকলে campaign complete হয় না। Overdue হলে escalation | Service, job |
| R16.15 | Revoke সিদ্ধান্ত Accounting নিজে প্রয়োগ করে না (RBAC বাইরের)। Complete-এর আগে re-snapshot-এ revoke দেখা যায় কি না, না হলে `revoke_pending` | `revoke_confirmed` যাচাই (OD-98a) |
| R16.16 | SoD matrix versioned। High severity অ-excepted finding থাকলে `compliance.enabled` চালু হয় না | Precondition |
| R16.17 | SoD exception: কারণ, compensating control, সর্বোচ্চ `sod.exception_max_days`, requester, approver ও user তিনজন আলাদা। মেয়াদ গেলে finding আবার খোলা | CHECK, job |
| R16.18 | Configuration change report শুধু পড়া। উৎস: settings history, change request, mapping, approval rule বদল | Read-only query |
| R16.19 | Migration commit-এর আগে source total বাধ্যতামূলক। Imported total বনাম source total মিলতে হবে `migration.total_tolerance` (default 0.0000) পর্যন্ত | `IMP_TOTALS_MISMATCH`, RC1i |
| R16.20 | Migration লেখা প্রতি kind-এর নিজস্ব service দিয়ে: CoA `AccountService`, party `PartyService`, rate `FxService`, journal `JournalService`। সরাসরি INSERT নয় | Static test |
| R16.21 | Opening balance: প্রতি company ও fiscal year-এ একটা active batch। Journal type `opening`, Debit = Credit, অনুমোদন লাগে | Generated unique, `opening_active` (P1-09) |
| R16.22 | Maker-checker: uploader, approver ও committer তিনজনই আলাদা (production-এ) | Service, CHECK |
| R16.23 | Validate ও dry-run ছাড়া commit নয়। File বা mapping বদলালে `validation_hash` বাতিল, আবার validate | `IMP_NOT_VALIDATED` |
| R16.24 | Partial accept default বন্ধ। `opening_balance` ও `historical_journals`-এ সবসময় all\_or\_nothing। CoA ও party-তে `migration.partial_accept` | Service |
| R16.25 | Rollback শুধু unposted: Draft journal `JournalService::cancel`, নতুন account অব্যবহৃত হলে deactivate। Posted হলে Reversal বা Adjustment (পুরোনো নিয়ম) | `IMP_ROLLBACK_POSTED` |
| R16.26 | Posted imported journal-এ `source_type = migration_batch` থাকে, batch বদলালেও লিংক মুছে না (নতুন সারিতে `status`)। Original নম্বর `reference_no`-তে | FK, trigger |
| R16.27 | Historical journal-এর voucher নম্বর আলাদা series `MIG` (gapless নয়, P6-03)। আগের system-এর নম্বর `reference_no` | OD-117 সম্পর্কিত |
| R16.28 | Cutover\_date-এর পরের লেনদেন migration নয়। Cutover-এর আগে freeze checklist সবুজ হতে হবে | Checklist `CP05` |
| R16.29 | Statutory definition versioned। Active version বদলায় না। Run একটা version-এ আটকানো। একই version ও একই ledger state-এ একই ফাইল hash | Determinism test |
| R16.30 | Statutory export শুধু posted data। Period closed না হলে manifest-এ সতর্কতা | Exporter |
| R16.31 | Ledger, journal attachment (posted সংলগ্ন) ও audit log purge নয়। Retention floor কমানো যায় না, শুধু বাড়ানো | CHECK, `ARC_POLICY_VIOLATION` |
| R16.32 | Archive শুধু সম্পূর্ণ বন্ধ fiscal year (সব period closed, year-end run সম্পন্ন)। ধারা: copy, verify, approve, detach। DELETE কখনো নয় | State machine, grant |
| R16.33 | Legal hold archive, detach, anonymize, purge সব আটকায়। Release-এ দ্বিতীয় অনুমোদন | Advisory lock, `ARC_HOLD_ACTIVE` |
| R16.34 | Subject request-এ ledger record ও journal narration retained। শুধু party mirror-এর non-ledger personal field anonymize। Journal line-এ `party_id` থাকে, নাম নয় | `PrivacyService`, test |
| R16.35 | Bank details, API secret, webhook secret field-level encrypted। Plaintext log, error ও export-এ নয়। Rotation এ envelope পুনঃencrypt | `KeyService`, secret-scan test |
| R16.36 | API clientকে কখনো owner user-এর permission-এর বেশি দেওয়া যায় না (effective = scope ∩ owner permission)। Baseline-এ Post, Approve, Setting scope-ই নেই | Gateway test |
| R16.37 | API write-এ `Idempotency-Key` বাধ্যতামূলক। Journal শুধু Draft | `acc_idempotency_keys` (P1-09) |
| R16.38 | Cursor pagination, `api.max_page_size`। Rate limit client ও company দুই স্তরে। 429-এ `Retry-After` | Gateway |
| R16.39 | API breaking change নতুন major version-এ। পুরোনো version-এ deprecation নোটিস `Sunset` header সহ (OD-103a) | Contract test |
| R16.40 | Webhook HMAC-SHA256 signed (timestamp + body)। At-least-once। Event id stable। Payload-এ PII নেই (id ও type) | Signature test |
| R16.41 | Webhook URL HTTPS। Private/loopback IP নিষিদ্ধ, প্রতি চেষ্টায় DNS আবার resolve, redirect অনুসরণ নয় | SSRF test |
| R16.42 | Backup ও restore verification প্রতি সপ্তাহে। Restore-র পর RC1 থেকে RC9, RC9c চালানো। DR drill প্রতি `dr.drill_interval_months` | Job, runbook |
| R16.43 | প্রতি request-এ `correlation_id`। Log-এ PII ও secret নেই | Log scan test |
| R16.44 | Compliance mapping-এ evidence ছাড়া control থাকলে তা gap হিসেবে দেখানো | Mapping review |

**Configuration (প্রথম অর্ধ)।** Flag `Setting` model-এ (Platform Setting, সীমা `accounting`)। বাকিগুলো `acc_settings`-এ `SettingRegistry` দিয়ে। সবাই company scope, যেখানে আলাদা বলা নেই। কার্যকর হওয়ার নিয়ম প্রতি সারিতে: পুরোনো লেনদেন কখনো বদলায় না (posted row স্পর্শ করা হয় না)।

**Phase 16-এর বাকি অংশ** (২৬.৫ Configuration সারণী থেকে ২৬.১৪ Open Decisions) আলাদা tab-এ: Phase 16 (২৬.৫ বাকি থেকে ২৬.১৪): Compliance, Audit, Migration, API

## ২৭. Phase 17: Enterprise UAT, Parallel Run ও Full Release

Phase 17 নতুন কোনো feature বানায় না। Phase 6 থেকে 16-এর সব capability একসাথে প্রমাণ করে, তারপর company ধরে ও capability ধরে ধাপে ধাপে production-এ চালু করে। কোনো ধাপে সব capability একসাথে চালু হবে না। এই phase-এ নতুন table নেই, ledger-এ লেখার নতুন পথও নেই।

### ১. Objective ও Scope

**Objective:** সাতটা কাজ।

1. প্রতিটা capability-র acceptance প্রমাণ করা। Scripted scenario, expected ledger result ও reconciliation check RC1 থেকে RC18 দিয়ে।
2. Capability গুলো একসাথে চললে ঠিক চলে কি না দেখা। Cross-module end-to-end scenario দিয়ে।
3. পুরোনো পদ্ধতি আর নতুন system পাশাপাশি চালিয়ে ফল মেলানো (parallel run)।
4. Scale, failover, security, accessibility যাচাই করা।
5. মানুষ প্রস্তুত করা। Training ও support model।
6. Go or no-go সিদ্ধান্ত নিয়ে wave ধরে চালু, hypercare, আর প্রতিটা capability-র ফেরার পথ।
7. চালুর পরেও নিয়ন্ত্রণ রাখা। Governance, post-implementation review, handover।

**Scope:**

| এলাকা | ভেতরে | বাইরে | বাইরের কেন |
| --- | --- | --- | --- |
| UAT | Phase 6 থেকে 16-এর সব capability-র scripted scenario, cross-module scenario, negative ও security scenario | নতুন feature, design বদল | প্রতিটা phase-এর scope বন্ধ। বদল হলে change control-এ যায় |
| Data migration | Production-copy ও বাস্তব volume-এ মহড়া, cutover সিদ্ধান্ত, company-ভিত্তিক চূড়ান্ত cutover | Phase 16 import tool-এর নতুন কাজ | Tool Phase 16-এর মালিকানা |
| Parallel run | Legacy ও নতুন system-এর দৈনিক ও period-end তুলনা, tolerance, discrepancy log, exit criteria | Legacy system ঠিক করা বা বন্ধ করা | Legacy-র মালিক client |
| Scale ও DR | OD-119 ও OD-114-এর সংখ্যায় load test, failover drill | নতুন partitioning বা infrastructure design | Phase 16-এর কাজ |
| Security | Accounting API, UI, webhook, auditor access-এর penetration test ও সংশোধন | ERPFlow-এর অন্য module-এর test | আলাদা security program |
| মানুষ | Training, train-the-trainer, support tier, SLA, playbook | Accounting standard শেখানো | Client-এর নিজস্ব প্রশিক্ষণ |
| Release | Go or no-go, wave plan, hypercare, rollback, governance, handover | Legacy বন্ধের তারিখ ঠিক করা | Business সিদ্ধান্ত |
| Ledger | বিদ্যমান PostingService দিয়ে test লেনদেন | নতুন table, নতুন ledger লেখার পথ | Master Plan-এর অপরিবর্তনীয় নিয়ম |

Baseline অনুমান G1 থেকে G9 ধরে লেখা। বিশেষ করে একটাই ledger (G2)। OD-109 হ্যাঁ হলে UAT scope আবার খুলবে, কারণ প্রতিটা scenario-র expected result প্রতি ledger-এ আলাদা হবে।

### ২. Functional Requirements

সব প্রয়োজন একটা সারণিতে। কাজ তিন ভাগে: প্রমাণ (ক থেকে ঞ), মানুষ (ট, ঠ), চালু ও নিয়ন্ত্রণ (ড থেকে ধ)।

| # | প্রয়োজন | বিস্তারিত |
| --- | --- | --- |
| ক | UAT strategy | Capability ধরে scripted scenario। প্রতিটা scenario-র precondition, ধাপ, expected ledger result, RC check, persona। Journal management ও dimensions, multi-currency, tax, subledger, bank, fixed asset, period-end ও year-end, budgeting ও reporting, posting rules ও source integration, consolidation, compliance ও migration, এই এগারোটা ভাগে। চার cycle: Cycle 1 scripted, Cycle 2 defect সংশোধনের পর পুনরায়, Cycle 3 cross-module ও negative, Cycle 4 final regression |
| খ | Expected ledger result | Scenario শেষে debit ও credit লাইন হিসাব করে আগে লেখা থাকে। Run-এর পর ledger-এর সাথে মেলানো হয়। মিল না হলে scenario ব্যর্থ, ledger ঠিক করা হয় না, defect লেখা হয় |
| গ | Reconciliation check | প্রতিটা scenario-র সাথে প্রযোজ্য RC লেখা (RC1 থেকে RC18)। Cycle শেষে সব RC সবুজ হতে হবে, না হলে sign-off নেই |
| ঘ | Cross-module end-to-end | চারটা পথ: (১) Project থেকে invoice, receivable, bank receipt, bank reconciliation, tax return, period close, year-end, consolidation। (২) Payroll run থেকে payable, bank payment। (৩) Asset acquisition, depreciation, disposal। (৪) বিদেশি currency invoice, revaluation, realized gain |
| ঙ | Negative ও security scenario | অনুমতি ছাড়া চেষ্টা, company isolation, duplicate, locked period, ভুল mapping, SoD লঙ্ঘন, malformed webhook, tampered audit chain, আর রোলব্যাক-পরবর্তী আচরণ |
| চ | Data migration মহড়া | বাস্তব volume-এ কমপক্ষে তিনবার। Dry-run, copy, rollback, প্রতিবার সময়, মিল, ত্রুটির হার মাপা। শেষ মহড়া production cutover-এর মতো ঘড়ি ধরে |
| ছ | Parallel run | নির্ধারিত সংখ্যক period পুরোনো ও নতুন পদ্ধতি একসাথে। দৈনিক ও period-end তুলনা, tolerance নিয়ম, discrepancy log, সমাধান workflow, exit criteria |
| জ | Scale ও performance | OD-119 ও OD-105b-র সংখ্যায় load: posting, report, import, depreciation, revaluation, consolidation, bulk export। সীমা পেরোলে tune করে আবার |
| ঝ | Failover ও DR drill | OD-114 (RPO ও RTO) যাচাই। Database failover, application failover, backup থেকে restore, queue ও outbox পুনরুদ্ধার, drill-এর পর RC সবুজ |
| ঞ | Penetration test, accessibility ও usability | Pentest ও সংশোধন, retest। Accessibility (keyboard, screen reader, contrast) ও usability (accountant-র কাজের সময় ও ভুলের হার) |
| ট | Training | ভূমিকা ধরে (accountant, approver, AP ও AR clerk, treasury, auditor, admin, support)। Train-the-trainer। শেষে কাজ-ভিত্তিক assessment, পাস না করলে ওই ভূমিকায় চালু নয় |
| ঠ | Support model | তিন tier, SLA, severity ধরে playbook, escalation পথ, on-call রোস্টার, চালুর প্রথম দিনের war room |
| ড | Release readiness ও go or no-go | Checklist, আর পাঁচ জনের sign-off: finance owner, accountant, tech lead, security, audit। যেকোনো একটা না হলে no-go। সিদ্ধান্ত ও কারণ লেখা, audit-এ |
| ঢ | Wave rollout | Company ধরে ও capability ধরে wave। Flag default বন্ধ। প্রতিটা চালুর আগে precondition। একসাথে সব capability নয়। প্রতিটা ধাপের মাঝে soak সময় |
| ণ | Hypercare | চালুর পর নির্ধারিত দিন প্রতিদিন reconciliation review ও defect triage। Exit-এর শর্ত মিললে তবে শেষ |
| ত | Rollback ও fallback | প্রতিটা capability-র জন্য আলাদা পথ: flag বন্ধ, code rollback, migration rollback নেই (শুধু এগিয়ে ঠিক করা)। Posted journal কখনো মোছা হয় না |
| থ | Post-implementation review | চালুর পর ধাপে ধাপে পর্যালোচনা: লক্ষ্য বনাম ফল, defect, ঘটনা, শেখা পাঠ, সংশোধনী কাজ |
| দ | Ongoing governance | Change control, policy review calendar, access review cadence, প্রতি ত্রৈমাসিকে DR drill |
| ধ | Handover | Runbook, architecture, data dictionary, compliance mapping, traceability table। Operations দলের গ্রহণ-সই |

**প্রতিটা অংশের চালু-শর্ত:** ক থেকে ঞ শেষ না হলে ড-তে যাওয়া যাবে না। ড সবুজ না হলে ঢ শুরু হবে না। ঢ-এর প্রতিটা ধাপে ণ চলে। ত সবসময় প্রস্তুত থাকে, চালুর আগেই পরীক্ষা করা।

### ৩. Technical Requirements

নতুন service বা job বানানো হয় না। যা আছে তা ব্যবহার হয়, আর যতটুকু নতুন লাগে তা বিদ্যমান gate (P5-06), flag ও setting বাড়িয়ে।

| বিষয় | প্রয়োজন |
| --- | --- |
| Component | `ReleaseGateService` (P5-06-এর বাড়ানো রূপ: gate নাম এখন তালিকা-ভিত্তিক, তিনটায় বাঁধা নয়)। `SettingRegistry` ও `ChangeApplier` (বিদ্যমান): `flag:` ও `release.` key। Parallel run তুলনার জন্য read-only comparison script বা API (OD-105a)। P5-03 monitoring ও alert বাড়ানো: capability ধরে dashboard |
| Job ও queue | কোনো নতুন queue নয়। Hypercare-এর দৈনিক RC1 থেকে RC18 চালানো বিদ্যমান reconciliation job (P3-13) দিয়ে, `AccountingJob` base ধরে, company context সহ। Load test-এ বিদ্যমান queue-ই ব্যবহার হয়, আলাদা worker pool নয় |
| Performance লক্ষ্য | নিচের সারণি। সংখ্যা প্রস্তাব, OD-105b ও OD-119-এ নিশ্চিত হবে। পরীক্ষায় p95 মাপা হয়, গড় নয় |
| Concurrency | Wave চালুর সময় flag বদল একটা setting row-এ। লেনদেন চলাকালীন flag বদলালে চলতি transaction আগের মান দেখে শেষ করে, পরের request নতুন মান দেখে। এক company-তে একসময়ে একটাই capability চালু বা বন্ধের কাজ চলে (setting row lock)। দুজন একসাথে চাইলে দ্বিতীয়জন `REL_PRECONDITION_FAILED` পায় |
| Failure handling | Precondition ব্যর্থ হলে flag বদলায় না। Flag চালুর পর alert সবুজ না হলে auto-পরামর্শ, কিন্তু auto-বন্ধ নয় (সিদ্ধান্ত মানুষের, গুরুতর হলে জরুরি বন্ধ পথ)। DR drill ব্যর্থ হলে gate সবুজ হয় না |
| Security | UAT ও rehearsal পরিবেশে production-এর ব্যক্তিগত তথ্য মাস্ক। Production-copy শুধু অনুমোদিত লোক, ব্যবহারের পর মোছা। Pentest-এর পরিবেশ UAT-র মতো, production-এ নয়। Sign-off-এ SoD: যে defect সারিয়েছে সে সেই scenario-র pass দেয় না |
| Observability | প্রতিটা capability-র জন্য: posting সফল ও ব্যর্থ হার, Draft-এর বয়স, RC ফল, queue দৈর্ঘ্য, dead letter সংখ্যা, report সময়। Hypercare সারাংশ একটা endpoint-এ |
| UAT পরিবেশ | Production-সমান configuration, আলাদা database, মাস্ক করা production-copy, সমান index ও সমান MySQL সংস্করণ। Scale test আলাদা পরিবেশে, UAT-র সাথে মিশবে না |

**Performance লক্ষ্য (প্রস্তাব):**

| মাপ | লক্ষ্য | পরীক্ষা |
| --- | --- | --- |
| Journal post (API) p95 | 500 ms-এর নিচে, 10 জন একসাথে প্রতি company-তে | PF-1 |
| Trial balance, এক বছর, cache সহ p95 | 3 s-এর নিচে | PF-2 |
| Comparative report, 12 মাস, dimension filter সহ p95 | 10 s-এর নিচে | PF-2 |
| Bulk journal import, 50,000 line | 10 মিনিটের নিচে | PF-3 |
| Depreciation run, 20,000 asset | 15 মিনিটের নিচে | PF-4 |
| FX revaluation, 10,000 open item | 10 মিনিটের নিচে | PF-4 |
| Consolidation, 20 entity | 15 মিনিটের নিচে | PF-5 |
| Event intake, ঘণ্টায় 20,000 event | Backlog জমে না | PF-6 |
| Reference প্রোফাইল | OD-119 নিশ্চিত না হলে: 200 company, সবচেয়ে বড়টায় বছরে 5,000,000 line | সব PF |

### ৪. Data Model ও API

**কোনো নতুন table নেই।** Master Plan অনুযায়ী এই phase `acc_` table যোগ করে না। প্রমাণ ও অবস্থা রাখা হয় বিদ্যমান shared table-এ:

| বিদ্যমান table | Phase 17-এ কী কাজে |
| --- | --- |
| `acc_reconciliation_runs` | RC1 থেকে RC18-এর ফল, UAT cycle, parallel run ও hypercare-এর দৈনিক ফল |
| `acc_report_exports` | Parallel run তুলনার report, UAT প্রমাণ report, সই করা report pack |
| `acc_enablement_log` | Gate সই, wave শুরু, প্রতিটা capability চালু ও বন্ধ, hypercare শুরু ও শেষ, rollback, কারণ ও অনুমোদনকারী |
| `acc_settings`, `acc_settings_history`, `acc_setting_change_requests` | `flag:` key, wave plan, tolerance, hypercare দিন। বদল দ্বিতীয় অনুমোদনে |
| `acc_setup_status` | Company-র precondition অবস্থা |
| `acc_alert_state` | Hypercare alert, auto-পরামর্শ |
| `acc_period_events`, `acc_period_balances` | Period close-এর প্রমাণ, parallel run তুলনার নতুন-পক্ষ |
| `acc_finance_event_inbox`, `acc_intake_heartbeats` | Integration scenario ও hypercare-এ intake স্বাস্থ্য |
| `acc_idempotency_keys` | সব write API-র পুনরাবৃত্তি সুরক্ষা |
| `activity_logs` (বাইরের, শুধু পড়া) | Audit প্রমাণ |

Defect register, discrepancy log আর scenario catalog কোথায় থাকবে তা OD-105a। Baseline: বাইরের tracker, প্রতিটা এন্ট্রিতে `acc_reconciliation_runs` বা `acc_report_exports`-এর id রেফারেন্স। কারণ Phase 5-এ `uat_runs` table ঐচ্ছিক ও tool-এর বাইরে রাখা হয়েছিল।

**API (বিদ্যমান বাড়ানো, ও পাতলা নতুন সারি):**

| Endpoint | কাজ | Permission |
| --- | --- | --- |
| `POST /accounting/release-gates/{gate}` (P5-06, বিদ্যমান) | Gate নাম এখন বাড়ানো: `gate:uat`, `gate:cutover`, `gate:security` ছাড়াও `gate:enterprise_uat`, `gate:parallel_run`, `gate:scale`, `gate:dr`, `gate:pentest`, `gate:training`, `gate:support`, `gate:go_no_go`। সই, কারণ ও প্রমাণের লিংক সহ | `accounting.release.approve` |
| `GET /accounting/release/readiness` | সব gate, সর্বশেষ RC, খোলা defect সংখ্যা (tracker রেফারেন্স থেকে), sign-off অবস্থা একসাথে | `accounting.release.view` |
| `GET /accounting/release/waves` | Wave plan ও প্রতিটা company-capability জোড়ার অবস্থা (বন্ধ, চালু, soak, hypercare, স্থির) | `accounting.release.view` |
| `POST /accounting/release/waves/{wave}/start` | Wave-এর precondition যাচাই করে ধাপ তৈরি | `accounting.release.manage` |
| `POST /accounting/release/capabilities/{capability}/enable` | এক company-তে এক capability চালু। Body: reason, evidence লিংক। `Idempotency-Key` আবশ্যক। সংবেদনশীল, তাই দ্বিতীয় অনুমোদন (বিদ্যমান `flag:` change request) | `accounting.enable.manage` + `accounting.release.approve` |
| `POST /accounting/release/capabilities/{capability}/disable` | Flag বন্ধ। জরুরি হলে দ্বিতীয় অনুমোদন পরে (post-hoc review আবশ্যক) | `accounting.enable.manage` |
| `GET /accounting/release/hypercare` | দৈনিক সারাংশ: RC ফল, খোলা alert, dead letter, Draft বয়স | `accounting.release.view` |
| `POST /accounting/release/hypercare/close` | Exit criteria মিললে hypercare শেষ | `accounting.release.approve` |
| `POST /accounting/release/parallel-runs/{period_key}/compare` | Legacy-র trial balance ফাইল (Phase 16 import mapping-এ) নতুন trial balance-এর সাথে মেলায়, ফল `acc_report_exports`-এ। OD-105a অনুযায়ী script হতে পারে | `accounting.release.manage` |

Per-company, সব endpoint company context সহ। পুরো column spec Pack-এ নেই, কারণ column নেই; contract Pack-এর card-এ।

### ৫. Business Rules ও Configuration

**Business Rules:**

| # | নিয়ম |
| --- | --- |
| R17.1 | UAT, rehearsal ও scale test-এর পরিবেশ production থেকে আলাদা। Production data আসে শুধু মাস্ক করে |
| R17.2 | Expected result run-এর আগে লেখা ও accountant-এর অনুমোদিত। পরে বদলালে নতুন version ও আগের pass বাতিল |
| R17.3 | প্রমাণ ছাড়া pass নেই। প্রতিটা pass-এ ledger export বা RC run id লিংক |
| R17.4 | প্রতিটা scenario-র শেষে debit = credit ও প্রযোজ্য RC সবুজ |
| R17.5 | Period close ও year-end scenario snapshot থেকে চলে, কারণ এগুলো ফেরানো যায় না |
| R17.6 | Event replay আগের link থাকলে নতুন journal বানায় না |
| R17.7 | E2E-তে পরের ধাপ আগের ধাপের posted ফলের উপর চলে, সরাসরি test data বসানোর উপর নয় |
| R17.8 | Negative scenario pass = ledger অপরিবর্তিত ও সঠিক `AccountingError` |
| R17.9 | Company-সীমানার বাইরে তথ্য বেরিয়ে পড়লে একটাও sev1 |
| R17.10 | Parallel run ন্যূনতম নির্ধারিত সংখ্যক period, তার মধ্যে অন্তত একটা period close সহ। প্রতিটা discrepancy ব্যাখ্যাত বা সংশোধিত। অব্যাখ্যাত বা tolerance-এর বাইরে থাকলে exit নয় |
| R17.11 | Tolerance শুধু rounding-এর জন্য। ব্যাখ্যাহীন পার্থক্য ঢাকতে বাড়ানো যাবে না। বদলাতে দ্বিতীয় অনুমোদন |
| R17.12 | Go or no-go-তে পাঁচ সই লাগে: finance owner, accountant, tech lead, security, audit। একজন no বললে no-go। এক মানুষ দুই ভূমিকায় সই করতে পারে না |
| R17.13 | Wave: এক সময়ে এক company-তে এক capability। Flag default বন্ধ। পরের capability-র আগে soak শেষ ও দৈনিক RC সবুজ। ক্রম phase dependency মেনে (নির্ভরশীল আগে) |
| R17.14 | Rollback মানে flag বন্ধ ও code rollback। Migration rollback নেই (schema expand-only)। Posted journal মোছা যায় না, ভুল হলে reversal বা adjustment |
| R17.15 | Hypercare শেষ হয় exit criteria মিললে, তারিখ পার হলে নয় |
| R17.16 | খোলা sev1 বা sev2 defect থাকলে নতুন capability বা company চালু নয় |
| R17.17 | Pentest-এর critical ও high সংশোধন ও retest ছাড়া `gate:pentest` নয় |
| R17.18 | DR drill-এ RPO বা RTO ব্যর্থ হলে `gate:dr` নয়। Drill-এর পর RC সবুজ না হলেও নয় |
| R17.19 | ভূমিকার assessment পাস ছাড়া ওই ভূমিকার ব্যবহারকারীর production access নেই |
| R17.20 | সব sign-off ও gate audit-এ আসে। পরে বাতিল সম্ভব (`gate_revoked`, P5-06-এর মতো)। বাতিল হলে নির্ভর wave থমে যায় |

**Configuration:** সব `acc_settings`-এ, company ধরে (যেখানে না বলা, সেখানে Scope = Company)। সবগুলো Post-এর সম্পর্কিত নয়, তাই “lock-after-post” প্রযোজ্য নয়; তবে প্রতিটা বদল `acc_settings_history`-তে থাকে।

| Key | উদ্দেশ্য ও Business Impact | Option ও Validation | Default Policy | Scope | বাধ্যতামূলক কি না | কে বানায়, বদলায়, অনুমোদন করে | বদলের প্রভাব | Lock ও Audit |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `release.parallel_run.min_periods` | কতগুলো period পাশাপাশি চলবে। কম হলে period-end ভুল ধরা পড়ে না | সংখ্যা 1 থেকে 6। কমপক্ষে একটা close থাকতে হবে | 2 (OD-105) | Company | হ্যাঁ, যখন legacy আছে | বানায়: Admin (`accounting.settings.manage`)। অনুমোদন: finance owner, দ্বিতীয় অনুমোদন লাগে | চলমান run-এর exit শর্ত বদলায়, পুরোনো ফল নয় | Run শুরুর পর lock। `accounting.settings.changed` |
| `release.parallel_run.tolerance_amount` | প্রতি account-এ rounding-এর বিচ্যুতি। বড় হলে ভুল লুকায় | সংখ্যা ≥0, base currency, 4 decimal। সর্বোচ্চ সীমা Admin-এর দেওয়া | 1.0000 | Company | হ্যাঁ | সমান | শুধু নতুন comparison run-এ | Run-এ lock। সমান event |
| `release.parallel_run.control_total_tolerance` | Trial balance মোট, receivable ও payable control, bank আলাদা সহনশীলতা | সংখ্যা ≥0 | 0.0000 | Company | হ্যাঁ | সমান, দ্বিতীয় অনুমোদন আবশ্যক | সমান | সমান |
| `release.wave.soak_days` | এক capability চালুর পর পরেরটার আগে অপেক্ষা। কম হলে সমস্যা লুকায়। বেশি হলে দেরি | সংখ্যা ১ থেকে 60, সমান বাড়ানো হলে পরে সবাই পারে, কমানোর আগে দ্বিতীয় অনুমোদন | 14 (কমপক্ষে এক period-end পার হতে হবে যে capability period-end ছুঁয়ে থাকে) | Company | হ্যাঁ | Admin বানায়, finance owner অনুমোদন | পরের enable-এর শর্ত, আগের enable নয় | Enable-এ lock। সমান event |
| `release.hypercare.days` | চালুর পর নিবিড় পর্যবেক্ষণের ন্যূনতম দিন | সংখ্যা 7 থেকে 90। Exit criteria-র সাথে (R17.15) | 30 (OD-106a) | Company | হ্যাঁ | Admin বানায়, finance owner অনুমোদন | চলমান hypercare-এ নয় | Hypercare শুরুর পর lock |
| `release.go_no_go.required_signoffs` | কারা সই করবে | তালিকা। কমপক্ষে পাঁচটা ভূমিকা থাকতে হবে (R17.12) | finance\_owner, accountant, tech\_lead, security, audit (OD-108) | System-wide | হ্যাঁ | Admin বানায়, দুইজন অনুমোদন | পরের go or no-go-তে | Gate সই হওয়ার পর lock |
| `release.defect.blocking_severity` | কোন defect gate আটকায় | তালিকা (sev1, sev2, বা sev3 যোগ করা যায়, কমানো যায় না) | sev1, sev2 | System-wide | হ্যাঁ | Admin বানায়, tech lead ও finance owner অনুমোদন | নতুন gate-এ | Lock-এর পর কমানো নিষিদ্ধ |
| `release.scale.reference_profile` | Load test-এর সংখ্যা (company, বছরে line, concurrent user) | সংখ্যা তিনটা, ধনাত্বক | বাধ্যতামূলক, Admin দেবে (OD-119 সবার আগে, প্রস্তাব OD-105b) | System-wide | হ্যাঁ | Admin, tech lead ও finance owner | নতুন scale test-এ | সমান |
| `release.dr.drill_interval_days` | DR drill কত দিন পর পর | সংখ্যা 30 থেকে 180 | 90 | System-wide | হ্যাঁ | Admin বানায়, tech lead অনুমোদন | পরের drill-এর তারিখ | সমান |
| `release.access_review.interval_days` | Role ও permission পর্যালোচনার বিরতি | সংখ্যা 30 থেকে 180 | 90 | Company | হ্যাঁ | Admin বানায়, audit অনুমোদন | পরের review | সমান |
| `release.pentest.max_age_days` | Pentest-এর বয়স পার হলে gate লাল | সংখ্যা 90 থেকে 400 | 365, বড় বদলের পর আবার | System-wide | হ্যাঁ | Admin বানায়, security অনুমোদন | পরের gate মূল্যায়ন | সমান |
| `flag:<capability>.enabled` (বিদ্যমান key, প্রতি capability-র) | Capability company-তে চালু বা বন্ধ। ভুল সময়ে চালু হলে বড় ফল | true বা false। Precondition সবুজ হলে true | false (default বন্ধ) | Company | হ্যাঁ | `accounting.enable.manage` + `accounting.release.approve`। দ্বিতীয় অনুমোদন লাগে | নতুন লেনদেনে নতুন আচরণ। পুরোনো posted journal কখনো বদলায় না (বন্ধ করলেও posted থাকে, শুধু নতুন লেখা থামে) | Enable-এ lock। `accounting.release.capability_enabled`, `accounting.release.capability_disabled`; আগে ও পরের অবস্থা দেখায় |

**অংশ ৬ থেকে ১৪ (Operational Workflow, Dependencies, Tasks, Testing, Security, Deliverables, Acceptance, Gates, Open Decisions):** block সীমার কারণে আলাদা tab-এ: Phase 17 ২৭.৬ থেকে ২৭.১৪ (Workflow থেকে Open Decisions)

## ২৮. Consolidated Open Decision Register

OD-24 থেকে OD-120 এক জায়গায় (options, recommended, decider, urgency): ২৮. Consolidated Open Decision Register

## ২৯. Completeness Validation

দাবিভিত্তিক যাচাই ও অমীমাংসিত তালিকা: ২৯. Completeness Validation
