অধ্যায় ২৮ — Purchase ও GRNI
Volume 2 · Part 4 — Application-specific Accounting · Chapter 28
পূর্বশর্ত: অধ্যায় ২৭ (Inventory Accounting), অধ্যায় ১৫ (Purchase), অধ্যায় ১৬ (Accounts Payable), অধ্যায় ১৯ (Debit Note), অধ্যায় ১৮ (Accrual)
১. Learning Objective
এই অধ্যায় শেষে আপনি পারবেন:
Procure-to-pay-এর চারটি document — PO, GRN, bill, payment — আর কোনটা accounting event, কোনটা নয়, বলতে
Purchase Order কেন entry নয়, অথচ commitment হিসেবে কেন গুনতে হয় — দুটো একসাথে বোঝাতে
GRN-এ Dr Inventory / Cr GRNI — মাল আছে, bill নেই — এই মাঝের অবস্থার entry লিখতে
Bill এলে GRNI clear করে A/P-তে নিতে; GRNI-র balance মানে কী, পড়তে
Three-way match — qty, দর, অঙ্ক — tolerance সহ কোডে লিখতে; না মিললে bill কোথায় আটকায়
Partial delivery, over-delivery, short-close — PO-র জীবনচক্রে তিনটি বাস্তবতা সামলাতে
দর-পার্থক্য (price variance) — inventory-তে না খরচে — নীতি ঠিক করতে
GRN-এর পরে, bill-এর আগে ফেরত বনাম bill-এর পরে ফেরত — দুটো আলাদা entry
Non-stock PO (asset, খরচ, সেবা) — GRN-এর বদলে acceptance — একই কাঠামোয়
মাস শেষে GRNI report কেন অধ্যায় ১৮-এর accrual-কে স্বয়ংক্রিয় করে দেয়
GRNI subledger (GRN line-ধরে) আর GL 2115 মেলাতেসময়: পড়া ৫৫ মিনিট + অনুশীলন ৬০ মিনিট।
২. Concept Explanation
চারটি document, দুটো accounting event
অধ্যায় ১৫-এ bill এলেই entry — সেটা “bill-ভিত্তিক” ক্রয়। বড় ক্রয়ে, আর যেকোনো stock-এর ক্রয়ে, bill-ই শুরু নয়:
Business Flow Financial Event? Entry
───────────── ──────────────── ─────
Purchase Order না — প্রতিশ্রুতি (commitment, memo)
│
▼
Goods Receipt (GRN) হ্যাঁ — asset এল, দেনা জন্মাল Dr 1145 Inventory / Cr 2115 GRNI
│
▼
Supplier Bill হ্যাঁ — দেনার মালিক বদলাল Dr 2115 GRNI / Dr 1160 VAT / Cr 2110 A/P
│
▼
Payment হ্যাঁ — টাকা গেল Dr 2110 A/P / Cr 1120 Bank (অধ্যায় ১৬)অধ্যায় ১-এর সেই সতর্কবাণী — “PO তৈরি হওয়া মাত্র journal entry বানিয়ে ফেলা” — এখানে আবার। PO-তে কিছু আসেনি, কিছু যায়নি; asset নেই, দেনা নেই। কিন্তু ৬,০০,০০০ টাকার PO সই হয়ে গেলে ওই টাকা আর “খালি” নয় — budget-এ (অধ্যায় ৩৩) সেটা commitment। দুটো আলাদা জিনিস: accounting বলে “কিছু হয়নি”, management বলে “টাকা বাঁধা”। দুটোই সত্য, দুটো আলাদা ledger।
GRNI — মাঝের অবস্থার account
মাল এসেছে ০৫-০৮, bill আসবে ০৮-০৮ — বা ২৮-০৮, বা আসবেই না ভুলে। মাঝের সময়টায়:
stock গুদামে → 1145 বাড়তে হবে (নইলে বিক্রি করলে COGS কোথা থেকে?)
bill নেই → 2110 A/P-তে বসানো যায় না (কার কাছে, কত, কবে due — bill-ই বলে)
কিন্তু দেনা আছে → কোথাও থাকতে হবেসেই “কোথাও” — 2115 Goods Received Not Invoiced। একটা liability, supplier-ধরে নয়, GRN line-ধরে। Bill এলে GRNI থেকে A/P-তে সরে যায়:
০৫-০৮ GRN Dr 1145 Inventory 4,00,000
Cr 2115 GRNI 4,00,000 ← দেনা, নাম নেই
০৮-০৮ bill Dr 2115 GRNI 4,00,000 ← GRNI খালি
Dr 1160 VAT Receivable 60,000
Cr 2110 A/P [টেক] 4,60,000 ← দেনা, নাম আছে, due আছেGRNI-র balance যেকোনো দিন = “মাল এসেছে, bill আসেনি” তালিকার যোগফল। মাস শেষে এটাই অধ্যায় ১৮-এর accrual — কিন্তু হাতে নয়, আন্দাজে নয়: প্রতিটি GRN নিজেই নিজের accrual। DESCO-র বিদ্যুৎ bill-এর জন্য এখনো হাতে accrual লাগবে (GRN নেই); মালের জন্য আর লাগবে না।
Developer-এর দৃষ্টিতে: 2115 একটা control account, 1145-এর মতোই — হাতে JV নয়, প্রতিটি line-এর source একটা GRN line বা bill line। Subledger-এর ঘর:
grn_line—(qty_accepted − qty_billed) × unit_cost। Section ৫-এর যাচাই ১০২।
Three-way match — তিন কাগজ, এক সত্য
Bill post হওয়ার আগে তিনটি সংখ্যা মিলতে হবে:
PO GRN Bill
── ─── ────
qty 30 (order) 20 (accepted) 20 ← bill ≤ GRN-এর unbilled
দর 20,000 20,000 (PO থেকে) 20,000 ← bill ≈ PO, tolerance
অঙ্ক 6,00,000 4,00,000 4,00,000 ← qty × দর
match ⇔ bill.qty ≤ Σ grn.unbilled_qty AND |bill.price − po.price| ≤ toleranceকেন তিনটি? দুটো হলে কী হয়:
PO ↔ bill শুধু মাল না এলেও bill post — কাগজে কেনা, গুদামে নেই
GRN ↔ bill শুধু যা খুশি দরে bill — PO-র দর অর্থহীন
PO ↔ GRN শুধু bill-এর অঙ্ক কেউ দেখল না — supplier যা লিখল তা-ই দেনাTolerance ছাড়া three-way match অচল — ৪,০০,০০০-এর bill-এ ২০ টাকা rounding-এর জন্য bill আটকে থাকলে হিসাবরক্ষক নিয়মটাই বন্ধ করে দেবেন। তাই নীতি: qty tolerance (সাধারণত ০ — মাল হয় এসেছে নয় আসেনি), price tolerance (১–২%), আর একটা absolute cap (যেমন ৫০০ টাকা)। সীমার ভিতরে পার্থক্য → স্বয়ংক্রিয়, বাইরে → hold, একজন মানুষ সিদ্ধান্ত নেন।
Match ব্যর্থ — তিনটি পথ
qty: bill 22, GRN 20 → bill hold; বাকি ২টা এলে (নতুন GRN) match, নইলে supplier-কে: "২টা আসেনি"
দর: bill 20,500, PO 20,000 → 2.5% > 2% → hold; ক্রেতা সিদ্ধান্ত:
(ক) PO amend, 20,500 মেনে নিন → variance-এর entry (নিচে)
(খ) supplier-কে debit note / নতুন bill চান, PO-র দরে post
অঙ্ক: qty × দর ≠ bill total → supplier-এর যোগে ভুল; hold, ফোনHold মানে bill status = on_hold (অধ্যায় ১৬-এর যাচাই ৩৪ — ৩০ দিনের বেশি hold → সতর্কতা)। Hold-এ থাকা bill-এর দেনা GRNI-তে আছেই — তাই hold করলেও Balance Sheet মিথ্যা বলে না। এটাই GRNI-র দ্বিতীয় উপকার: match-এর ঝগড়া accounting-কে থামায় না।
দর-পার্থক্য — কোথায় যাবে
GRN-এ stock ঢুকেছে PO-র দরে (২০,০০০) — কারণ bill তখন ছিল না। Bill এল ২০,৫০০-এ, মেনে নেওয়া হলো। ১০টা unit-এ ৫,০০০ বেশি — কোথায়?
unit এখনো গুদামে → 1145 Inventory: layer-এর cost 20,000 → 20,500 (অধ্যায় ২৭-এর landedCost-এর মতো)
unit বিক্রি হয়ে গেছে → 5140 COGS: ওই issue-র cost বাড়ল (recost)
standard cost-এ চললে → 5160 Purchase Price Variance: stock standard-এ থাকে, পার্থক্য খরচে (অধ্যায় ৫৭)এই বইয়ে actual cost (FIFO/avg) — তাই প্রথম দুটো। নীতিটা company_settings.price_variance_policy; আর অধ্যায় ২৭-এর landedCost() function-ই এটা করে: receipt-এর cost বাড়ান, issue হয়ে থাকলে recost। নতুন কোড নয়, নতুন caller।
Partial, over, short
PO ৩০টা; supplier দুই lot-এ পাঠায় — বাস্তবতা:
partial delivery GRN-1 20, GRN-2 10 PO line: qty_received 20 → 30; status partially_received → received
over-delivery ৩২টা এল, PO ৩০ qty tolerance 0 → ৩০ গ্রহণ, ২ gate-এ ফেরত (movement নেই, entry নেই);
tolerance 5% হলে ৩১ পর্যন্ত গ্রহণ, PO auto-amend, log
short-close ২৮ এল, supplier বলল আর নেই → PO short-closed: qty_ordered থাকে 30, qty_cancelled 2;
commitment মুক্ত; বাকি ২-এর জন্য কেউ অপেক্ষা করে নাOver-delivery-তে gate-এ ফেরত মানে GRN-এ qty_delivered 32, qty_accepted 30, qty_rejected 2 — কাগজে থাকে, খাতায় নয়। Stock movement শুধু accepted-এর।
ফেরত — bill-এর আগে না পরে
GRN-এর পরে, bill-এর আগে GRN reversal: Dr 2115 GRNI / Cr 1145 GRN line qty_accepted কমে; bill এলে কম qty
bill-এর পরে debit note (অধ্যায় ১৯): Dr 2110 / Cr 1145, Cr 1160দুটোতেই stock কমে (return_out, মূল layer থেকে — অধ্যায় ২৭)। পার্থক্য শুধু দেনার কোন ঘর কমে — GRNI না A/P। Bill post হয়ে গেলে GRN reversal আর করা যায় না (bill-এর match ভেঙে যাবে) — debit note-ই পথ।
Non-stock PO — একই কাঠামো
PO শুধু মালের নয়: ৬টা চেয়ার (খরচ), একটা server (asset), office renovation (সেবা)। GRN-এর জায়গায় কী?
stock item GRN: গুদামে গোনা Dr 1145 / Cr 2115
asset GRN: হাতে পাওয়া, register-এ Dr 1510 / Cr 2115 (অধ্যায় ৩০)
খরচের জিনিস GRN: হাতে পাওয়া Dr 5250 / Cr 2115
সেবা acceptance: কাজ শেষ, সই Dr 5xxx / Cr 2115 (milestone-ধরে হতে পারে)Debit পাশটা অধ্যায় ১৫-এর তিন প্রশ্ন — item master থেকে। Credit সবসময় 2115। এতে অধ্যায় ১৮-এর accrual-এর অর্ধেক কাজ চলে যায়: যা PO দিয়ে কেনা, তার accrual GRN-ই। শুধু PO-বিহীন খরচ (বিদ্যুৎ, ভাড়া) হাতে থাকে।
Bill আগে, মাল পরে
উল্টোটাও হয় — bill এল, মাল রাস্তায়। Match হবে না (GRN নেই)। দুটো নীতি:
কঠোর bill status = awaiting_receipt; entry নেই; GRN এলে match → post। দেনা খাতায় নেই — কিন্তু মাল-ও নেই; ঠিক
নরম post করুন: Dr 1147 Goods in Transit / Dr 1160 / Cr 2110; GRN এলে Dr 1145 / Cr 1147এই বইয়ে কঠোরটা — সরল, আর “asset নেই অথচ দেনা আছে” পরিস্থিতি এড়ায়। আমদানিতে (মাল সমুদ্রে তিন সপ্তাহ, bill হাতে, LC-র টাকা গেছে) নরমটা লাগে — অধ্যায় ৫৪-এর exercise।
PO-র জীবনচক্র
DRAFT ──▶ APPROVED ──▶ SENT ──▶ PARTIALLY_RECEIVED ──▶ RECEIVED ──▶ CLOSED
│ │ │ (সব bill এলে)
│ │ └──▶ SHORT_CLOSED
└────────────┴──▶ CANCELLED (কিছু আসার আগে)Approval PO-তে — bill-এ নয়? দুটোতেই, কিন্তু আলাদা প্রশ্ন: PO-র approval “এই টাকা খরচ করব?” (budget, অধ্যায় ৩৩); bill-এর approval “যা এসেছে তার জন্যই bill?” (match)। Match-এ পাশ করা bill-এর approval স্বয়ংক্রিয় হতে পারে — কারণ মানুষ PO-তে একবার সিদ্ধান্ত নিয়েছেন। PO-বিহীন bill-এ অধ্যায় ১৫-এর হাতে approval থাকে।
যা এই অধ্যায়ে নেই
Payment, payment run, supplier aging অধ্যায় ১৬ (বদলায়নি)
Budget check PO approval-এ অধ্যায় ৩৩
Standard cost, PPV-র বিশ্লেষণ অধ্যায় ৫৭
Import: LC, goods in transit, শুল্ক অধ্যায় ৫৪, ৫৫
Fixed asset-এর GRN → register অধ্যায় ৩০
Requisition, quotation, supplier selection procurement — accounting নয়৩. Accounting Rule
Entry
| ঘটনা | Debit | Credit |
|---|---|---|
| PO approved | — | — (commitment memo) |
| GRN (stock) | Inventory (PO দরে × accepted qty) | GRNI |
| GRN (asset / খরচ) | Fixed Asset / Expense | GRNI |
| Service acceptance | Expense | GRNI |
| Bill, match ✓ | GRNI (GRN cost × qty), VAT Receivable | A/P [supplier] |
| Bill, দর বেশি (মানা) — stock গুদামে | GRNI, Inventory (পার্থক্য), VAT | A/P |
| Bill, দর বেশি (মানা) — stock বিক্রি হয়ে গেছে | GRNI, COGS (পার্থক্য), VAT | A/P |
| Bill, দর কম | GRNI, VAT | A/P, Inventory / COGS (পার্থক্য) |
| ফেরত, bill-এর আগে | GRNI | Inventory |
| ফেরত, bill-এর পরে (debit note) | A/P | Inventory, VAT Receivable |
| Over-delivery gate-এ ফেরত | — | — |
| Short-close | — | — (commitment মুক্ত) |
Match
qty: Σ bill_line.qty ≤ Σ grn_line.(qty_accepted − qty_billed) for that po_line; tolerance সাধারণত 0
price: |bill.unit_price − po.unit_price| ÷ po.unit_price ≤ price_tol (১–২%) AND পার্থক্য × qty ≤ abs_cap
amount: bill.line_total == qty × unit_price (supplier-এর যোগ)
ফল: matched → post; বাইরে → on_hold; GRN নেই → awaiting_receipt (entry নেই)GRNI
2115 control: হাতে JV নয়
subledger: grn_line-ধরে, balance = (qty_accepted − qty_billed) × unit_cost
GL 2115 == Σ grn_line balance
মাস শেষে GRNI balance == "মাল এসেছে, bill আসেনি" — এটাই মালের accrual; হাতে accrual শুধু PO-বিহীন খরচেদর-পার্থক্য
actual cost: গুদামে → 1145 (layer recost); বিক্রি হয়ে গেছে → 5140; অধ্যায় ২৭-এর landedCost()
standard cost: 5160 PPV (অধ্যায় ৫৭)অলঙ্ঘনীয়
PO entry নয়। GRN ছাড়া stock-এর bill post নয়। 2115-এ হাতে JV নয়।৪. Real Business Example
আগস্ট ২০২৬ — মাল আগে, bill পরে
একই company, অধ্যায় ২৭-এর পরের মাস। ১ আগস্টে PT-200: ১০টা গুদামে (FIFO: L2 ৯ × ২০,০০০ + L3 ১ × ১৮,৮০০ = ১,৯৮,৮০০), allowance ১৩,৮০০। গুজবটা মিথ্যা ছিল — নতুন model আসছে না, দাম আগের জায়গায়; আর ABC-র দ্বিতীয় দোকানের জন্য বড় order আসছে।
০১-০৮ — NRV ফিরল: write-down উল্টে
JV-08-0001 source = stock_movement (writedown, ঋণাত্মক)
Dr 1146 Allowance for Inventory Write-down 13,800
Cr 5150 Inventory Loss & Write-down 13,800 ← cost পর্যন্তই, বেশি নয়০১-০৮ — PO-08-0001: টেক সাপ্লাইকে ৩০টা PT-200, ২০,০০০ করে + VAT; দুই lot-এ delivery
entry: নেই
commitment: 6,00,000 (budget ledger, অধ্যায় ৩৩)
PO status: approved → sent; line: qty_ordered 30, received 0, billed 0০৫-০৮ — GRN-08-0001: প্রথম lot, ২০টা এল, গোনা হলো, ঠিক আছে
GRN line: po_line PO-08-0001/1, delivered 20, accepted 20, rejected 0, unit_cost 20,000 (PO থেকে)
JV-08-0002 source = goods_receipt GRN-08-0001
Dr 1145 Inventory 4,00,000
Cr 2115 GRNI 4,00,000
movement #9 receipt +20 @ 20,000 source = goods_receipt je = JV-08-0002
layer L4 qty 20 unit 20,000
stock L2 9 + L3 1 + L4 20 = 30 units, 5,98,800
GRNI GRN-08-0001/1: (20 − 0) × 20,000 = 4,00,000
PO received 20 / 30 → partially_received০৭-০৮ — ABC Ltd-এর নতুন দোকানে ১২টা, ২৬,০০০ করে + VAT — bill এখনো আসেনি
SI-08-0001 JV-08-0003 source = stock_movement #10
Dr 1130 A/R [ABC] 3,58,800 Dr 5140 COGS 2,38,800
Cr 4140 Product Sales 3,12,000 Cr 1145 Inventory 2,38,800
Cr 2140 VAT Payable 46,800
COGS (FIFO): L2 9 × 20,000 + L3 1 × 18,800 + L4 2 × 20,000 = 1,80,000 + 18,800 + 40,000 = 2,38,800
stock L4 18 × 20,000 = 3,60,000লক্ষ করুন — L4-এর ২টা বিক্রি হয়ে গেল, যার bill এখনো আসেনি। COGS ঠিকই এসেছে, কারণ GRN-এ cost ঢুকেছিল। GRNI ছাড়া হলে হয় stock-ই থাকত না (বিক্রি আটকে), নয় stock থাকত দেনা ছাড়া (Balance Sheet মিথ্যা)।
০৮-০৮ — bill TS/2026/0522: ২০টা × ২০,০০০ + VAT ৬০,০০০ = ৪,৬০,০০০
match: qty 20 ≤ unbilled 20 ✓ price 20,000 == 20,000 ✓ amount 4,00,000 ✓ → matched
PB-08-0004 source = purchase_invoice
Dr 2115 GRNI 4,00,000 ← GRN-08-0001/1 থেকে
Dr 1160 VAT Receivable 60,000
Cr 2110 A/P [টেক সাপ্লাই] 4,60,000
GRNI GRN-08-0001/1: (20 − 20) × 20,000 = 0
PO billed 20 / 30
1145 স্পর্শ নেই — stock GRN-এই ঢুকেছিল১৮-০৮ — GRN-08-0002: দ্বিতীয় lot; ১২টা এল, PO-তে বাকি ১০; qty tolerance ০
GRN line: delivered 12, accepted 10, rejected 2 ("PO-র বেশি — gate-এ ফেরত, challan-এ সই")
JV-08-0005
Dr 1145 Inventory 2,00,000
Cr 2115 GRNI 2,00,000
movement #11 receipt +10 @ 20,000 layer L5
stock L4 18 + L5 10 = 28 units, 5,60,000
GRNI GRN-08-0002/1: 10 × 20,000 = 2,00,000
PO received 30 / 30 → receivedফেরত যাওয়া ২টার কোনো movement নেই, entry নেই — গুদামে ঢোকেনি।
২২-০৮ — bill TS/2026/0561: ১০টা × ২০,৫০০ + VAT ৩০,৭৫০ = ২,৩৫,৭৫০
match: qty 10 ≤ unbilled 10 ✓ price 20,500 vs 20,000: 2.5% > tol 2% ✗ → on_hold
২৩-০৮ ক্রেতা টেক-কে ফোন: "PO-তে 20,000"। টেক: "১৫ আগস্ট থেকে দাম বেড়েছে, PO-র পরে; দুঃখিত"।
ক্রেতা মেনে নিলেন (বাজারে এটাই দর); PO line amend: unit_price 20,500 এই lot-এর জন্য, approved, log।
২৩-০৮ re-match: price 20,500 == 20,500 ✓ → matched
দর-পার্থক্য: 10 × 500 = 5,000; L5-এর ১০টাই গুদামে → 1145
PB-08-0006
Dr 2115 GRNI 2,00,000 ← GRN-এর cost-এ
Dr 1145 Inventory 5,000 ← পার্থক্য, layer L5-এ
Dr 1160 VAT Receivable 30,750
Cr 2110 A/P [টেক সাপ্লাই] 2,35,750
movement #12 landed_cost qty 0 value 5,000 ref #11 → L5 unit 20,000 → 20,500
stock L4 18 × 20,000 + L5 10 × 20,500 = 3,60,000 + 2,05,000 = 5,65,000
GRNI GRN-08-0002/1: 0
PO billed 30 / 30 → closedL5-এর কিছু বিক্রি হয়ে থাকলে সেই অংশের ৫০০ করে 5140-এ যেত — অধ্যায় ২৭-এর landedCost() নিজেই recost করত।
২৬-০৮ — দ্বিতীয় lot-এর ১টা ত্রুটিপূর্ণ; bill post হয়ে গেছে → debit note
DN-08-0007 (অধ্যায় ১৯; counterparty CN পরে)
Dr 2110 A/P [টেক সাপ্লাই] 23,575
Cr 1145 Inventory 20,500 ← L5-এর cost
Cr 1160 VAT Receivable 3,075
movement #13 return_out −1 @ 20,500 ref #11 (L5)
stock L4 18 + L5 9 = 27 units, 5,44,500Bill-এর আগে ধরা পড়লে: Dr 2115 20,000 / Cr 1145 20,000, GRN line accepted 10 → 9, আর bill ৯টার হতো। এখানে সেটা আর সম্ভব নয় — bill post হয়ে গেছে।
২৮-০৮ — PO-08-0002: অফিসের ৬টা চেয়ার, ৪,৫০০ করে (অধ্যায় ১৫-এর exercise ১), অফিস মার্ট থেকে; VAT নেই (ছোট দোকান)
২৮-০৮ PO approved; item non-stock, purchase_account 5250 (threshold 20,000-এর নিচে — খরচ)
৩০-০৮ চেয়ার এল; GRN-08-0003 accepted 6
JV-08-0008 source = goods_receipt GRN-08-0003
Dr 5250 Office Supplies 27,000
Cr 2115 GRNI 27,000
movement: নেই (non-stock)
bill: ৩১-০৮ পর্যন্ত আসেনি৩১-০৮ — মাস শেষ: GRNI report
GRNI — ৩১ আগস্ট ২০২৬ (মাল এসেছে, bill আসেনি)
GRN Supplier Item Accepted Billed Unbilled Unit Balance দিন
─── ──────── ──── ──────── ────── ──────── ──── ─────── ───
GRN-08-0003/1 অফিস মার্ট চেয়ার 6 0 6 4,500 27,000 1
───────
27,000
GL 2115: Cr 4,00,000 − Dr 4,00,000 + Cr 2,00,000 − Dr 2,00,000 + Cr 27,000 = 27,000 Cr ✓চেয়ারের খরচ আগস্টে — bill সেপ্টেম্বরে এলেও। অধ্যায় ১৮-এ এর জন্য হাতে accrual লিখতে হতো, আর সেপ্টেম্বরে reversal; এখন কিছুই না — GRN-ই accrual, bill-ই reversal।
PO-08-0001 — শেষ চেহারা
PO-08-0001 টেক সাপ্লাই status: closed
line 1 PT-200 ordered 30 received 30 (GRN-1 20, GRN-2 10; rejected 2) billed 30 (PB-4 20 @ 20,000, PB-6 10 @ 20,500)
commitment: 6,00,000 → 0
returned after bill: 1 (DN-08-0007)একটা PO থেকে দুটো GRN, দুটো bill, একটা amendment, একটা debit note। প্রতিটি সংখ্যার নিচে একটা document — অধ্যায় ২৬-এর শেষ কথাটা এখানেও।
GL 2115 — আগস্ট
2115 Goods Received Not Invoiced
────────────────────────────────────────────────────────────────
05-08 JV-08-0002 GRN-08-0001/1 4,00,000
08-08 PB-08-0004 bill TS/0522 ← GRN-1 4,00,000
18-08 JV-08-0005 GRN-08-0002/1 2,00,000
22-08 PB-08-0006 bill TS/0561 ← GRN-2 2,00,000
30-08 JV-08-0008 GRN-08-0003/1 27,000
───────── ─────────
6,00,000 6,27,000 Bal 27,000 Cr
subledger: GRN-08-0003/1 6 × 4,500 = 27,000 ✓৫. Implementation — Software ও Database
Schema — চারটি document, তিনটি link
purchase_orders purchase_order_lines
id, company_id, po_no (gapless) id, po_id, line_no
supplier_id item_id, description, uom
po_date, expected_date qty_ordered DECIMAL(18,4)
currency unit_price DECIMAL(18,6) -- amend হলে log
status draft | approved | sent | qty_received DECIMAL(18,4) -- Σ grn accepted (cache)
partially_received | received | qty_billed DECIMAL(18,4) -- Σ bill qty (cache)
closed | short_closed | cancelled qty_cancelled DECIMAL(18,4) -- short-close
approved_by, approved_at debit_account_id BIGINT FK -- item থেকে: 1145 | 15xx | 5xxx
subtotal, tax_total, grand_total warehouse_id BIGINT FK NULL
tax_code_id
UNIQUE (company_id, po_no) cost_center_id, project_id (অধ্যায় ৩৪, ৩৫)
CHECK (qty_received ≤ qty_ordered + tolerance) -- app-levelgoods_receipts goods_receipt_lines
id, company_id, grn_no (gapless) id, grn_id, line_no
supplier_id, po_id po_line_id BIGINT FK -- three-way match-এর প্রথম link
receipt_date item_id, warehouse_id
posting_date qty_delivered DECIMAL(18,4)
supplier_challan_no qty_accepted DECIMAL(18,4) -- এটাই movement ও entry
status draft | posted | reversed qty_rejected DECIMAL(18,4) -- gate-এ ফেরত; কাগজে
journal_entry_id unit_cost DECIMAL(18,6) -- PO-র দর, receipt-এর দিনে
received_by, checked_by qty_billed DECIMAL(18,4) -- Σ matched bill qty
qty_returned_pre_bill DECIMAL(18,4) -- GRN reversal
stock_movement_id BIGINT FK NULL -- stock হলে
grni_balance = (qty_accepted − qty_returned_pre_bill − qty_billed) × unit_cost (computed)purchase_invoice_lines (অধ্যায় ১৫-এর table, দুটো column)
grn_line_id BIGINT FK NULL -- দ্বিতীয় link; NULL = PO-বিহীন bill (অধ্যায় ১৫-এর পথ)
po_line_id BIGINT FK NULL -- অধ্যায় ১৫-এ রাখা ছিল
match_status VARCHAR(20) -- matched | qty_variance | price_variance | amount_error | no_receipt
price_variance DECIMAL(18,4) -- (bill price − grn cost) × qtyতিনটি link: grn_line.po_line_id, bill_line.grn_line_id, bill_line.po_line_id। Match এই তিনটি ধরে। qty_received, qty_billed cache — rebuild-able (যাচাই ১০৩)।
Tolerance — নীতি table-এ
match_tolerances
company_id, scope (company | supplier | item_group)
qty_pct DECIMAL(5,2) DEFAULT 0
price_pct DECIMAL(5,2) DEFAULT 2
amount_abs_cap DECIMAL(18,4) DEFAULT 500
over_delivery_pct DECIMAL(5,2) DEFAULT 0সবচেয়ে নির্দিষ্ট scope জেতে (item_group > supplier > company)। কোড-এ hardcode নয় — হিসাবরক্ষক বদলাবেন, log-এ থাকবে।
GRN post
postGRN(grn, by):
যাচাই:
grn.po.status IN (sent, partially_received)
প্রতিটি line: po_line.supplier == grn.supplier
qty_accepted ≤ po_line.qty_ordered − po_line.qty_received (+ over_delivery_pct)
qty_accepted + qty_rejected == qty_delivered
by ≠ po.approved_by (segregation: যিনি কিনলেন তিনি গুনলেন না — অধ্যায় ৪৮)
entry = JournalEntry(voucher 'JV', posting_date = grn.posting_date,
source = ('goods_receipt', grn.id), idempotency_key = ('grn', grn.id))
প্রতিটি line l WHERE qty_accepted > 0:
l.unit_cost = po_line.unit_price -- bill-এর আগে এটাই সত্য
value = qty_accepted × unit_cost
if item.is_stock_item:
m = insertMovement(kind = receipt, qty = qty_accepted, unit_cost = l.unit_cost,
movement_date = grn.receipt_date, source = ('goods_receipt', l.id))
receive(m); l.stock_movement_id = m.id; m.journal_entry_id = entry.id -- অধ্যায় ২৭
entry.line(Dr item.inventory_account_id, value, dims = {warehouse, item})
else:
entry.line(Dr po_line.debit_account_id, value, dims = {cost_center, project})
if asset: fixedAssetRegister.stage(l) -- অধ্যায় ৩০
entry.line(Cr 2115, value, dims = {supplier, grn_line = l.id})
po_line.qty_received += qty_accepted
post(entry); grn.status = posted
po.status = Σ received < Σ ordered ? partially_received : receivedNon-stock-এর GRN-ও একই function — is_stock_item শুধু ঠিক করে movement হবে কি না। 2115-এর line-এ grn_line dimension — এটাই subledger-এর চাবি।
Three-way match
match(bill):
প্রতিটি bill line b:
if b.po_line_id IS NULL: b.match_status = 'no_po'; continue -- অধ্যায় ১৫-এর পথ
tol = tolerance(b.item.group, bill.supplier)
-- ১. qty: ওই po_line-এর unbilled GRN lines, receipt_date ক্রমে (FIFO-র মতো)
unbilled = grn_lines WHERE po_line_id = b.po_line_id AND grni_balance_qty > 0 ORDER BY receipt_date
if Σ unbilled.qty × (1 + tol.qty_pct) < b.quantity:
b.match_status = unbilled.empty ? 'no_receipt' : 'qty_variance'; continue
-- ২. price
po_price = po_line.unit_price
diff = b.unit_price − po_price
if |diff| ÷ po_price > tol.price_pct OR |diff| × b.quantity > tol.amount_abs_cap:
b.match_status = 'price_variance'; continue
-- ৩. amount
if |b.line_total − b.quantity × b.unit_price| > 1:
b.match_status = 'amount_error'; continue
-- allocate qty to GRN lines
need = b.quantity
প্রতিটি g in unbilled:
take = min(need, g.unbilled_qty)
insert bill_grn_allocation(b.id, g.id, take, g.unit_cost)
need −= take; if need == 0: break
b.grn_line_id = first(g).id; b.match_status = 'matched'
b.price_variance = diff × b.quantity
bill.status = all matched ? 'matched' : 'on_hold'
-- on_hold → অধ্যায় ১৬-এর যাচাই ৩৪; no_receipt → awaiting_receipt (GRN এলে auto re-match)একটা bill line একাধিক GRN line-এ ভাগ হতে পারে (২০টার bill, দুটো GRN ১২ + ৮) — তাই bill_grn_allocation table, অধ্যায় ১৪-এর allocation-এর মতো। Re-match: GRN post হলে awaiting_receipt bill-গুলো আবার match(); PO amend হলে price_variance bill-গুলো।
Bill post — GRNI clear
অধ্যায় ১৫-এর buildDocumentEntry(bill, −1) চলবে, শুধু line-এর debit account বদলায়:
lineAccount(b): -- buildDocumentEntry-র line পাশে
if b.grn_line_id IS NULL: return b.debit_account_id -- অধ্যায় ১৫: 5xxx | 15xx | 1145
return 2115 -- GRN-এ debit হয়ে গেছে; এখন GRNI clear
postBill(bill):
যাচাই: bill.status == 'matched' (PO আছে) বা 'approved' (PO নেই)
entry = buildDocumentEntry(bill, −1) -- party Cr 2110; lines Dr lineAccount × grn cost
প্রতিটি line b WHERE grn_line_id:
প্রতিটি alloc in b.allocations:
entry.line(Dr 2115, alloc.qty × alloc.unit_cost, dims = {grn_line = alloc.grn_line_id})
alloc.grn_line.qty_billed += alloc.qty
-- দর-পার্থক্য
if b.price_variance != 0:
if b.item.is_stock_item:
m = insertMovement(kind = landed_cost, value = b.price_variance, ref = grn_line.stock_movement_id)
landedCost(m) -- অধ্যায় ২৭: layer বাড়ে; বিক্রি হয়ে থাকলে recost → 5140
entry.line(Dr 1145, b.price_variance) -- (ঋণাত্মক হলে Cr)
else:
entry.line(Dr b.debit_account_id, b.price_variance) -- খরচ/asset-এ সরাসরি
assert entry.balanced(); post(entry)
po_line.qty_billed += …; if Σ billed == Σ received and po.status == received: po.status = closedGRNI-র debit GRN-এর cost-এ (২,০০,০০০), bill-এর দরে নয় — নইলে GRNI-তে ৫,০০০ ধুলো থেকে যেত চিরকাল। পার্থক্য আলাদা line-এ, আলাদা account-এ।
GRN reversal (bill-এর আগে ফেরত)
reverseGRNLine(l, qty, reason, by):
যাচাই: qty ≤ l.qty_accepted − l.qty_billed − l.qty_returned_pre_bill -- bill হয়ে গেলে debit note (অধ্যায় ১৯)
entry: Dr 2115 (grn_line = l.id) qty × l.unit_cost / Cr inventory or expense account
if stock: m = insertMovement(kind = return_out, qty, ref = l.stock_movement_id); issue(m) -- মূল layer
l.qty_returned_pre_bill += qty; po_line.qty_received −= qty -- supplier আবার পাঠাতে পারে
log (reason, by)Short-close ও amend
shortClosePO(po, reason, by): po.status = short_closed; প্রতিটি line: qty_cancelled = ordered − received; commitment মুক্ত; log
amendPOPrice(line, new_price, reason, by):
যাচাই: by has 'po.amend'; by ≠ bill.created_by
po_price_log(line, old, new, reason, by, at); line.unit_price = new_price
re-match price_variance bills on this lineAmend-এর পরে আগের GRN-এর unit_cost বদলায় না — GRN-এর দিনে যা সত্য ছিল তা-ই থাকে; পার্থক্য bill-এ ধরা পড়ে। নইলে GRNI subledger আর GL-এর মাঝে ফাঁক।
Commitment — memo ledger
commitments po_line_id, account_id, cost_center_id, amount_committed, amount_received, amount_cancelled
open commitment = committed − received − cancelled
budget check (অধ্যায় ৩৩): actual (GL) + open commitment ≤ budgetGL-এ নয় — কিন্তু GL-এর পাশে, একই dimension-এ। PO approve-এ বাড়ে, GRN-এ কমে (actual-এ সরে), short-close-এ মুক্ত।
API / Service
POST /purchase-orders … /approve … /send … /short-close … /lines/{id}/amend-price
POST /goods-receipts {po_id, lines: [{po_line_id, qty_delivered, qty_accepted, qty_rejected}]} → post
POST /goods-receipts/{id}/lines/{lid}/reverse {qty, reason}
POST /purchase-invoices {…, lines: [{po_line_id?, …}]} → match() স্বয়ংক্রিয়; status matched | on_hold | awaiting_receipt
GET /purchase-invoices?status=on_hold → hold তালিকা, কারণ সহ (qty | price | amount | no_receipt)
GET /grni?as_of=&supplier= → section ৪-এর report, aging সহ
GET /purchase-orders/{id}/trace → PO → GRN → bill → DN → payment, এক পাতায়
GET /commitments?cost_center=&period= → অধ্যায় ৩৩-এর জন্যPOST /purchase-invoices-এ client match_status পাঠাতে পারে না — match server-এ। /trace পাতাটা audit-এ সবচেয়ে বেশি চাওয়া হয়।
রাতের যাচাই
১০২. closing(2115) == Σ grn_line.(qty_accepted − qty_returned_pre_bill − qty_billed) × unit_cost — supplier-ধরেও
১০৩. po_line.qty_received == Σ grn_line.qty_accepted − qty_returned_pre_bill; qty_billed == Σ allocation.qty (cache == truth)
১০৪. 2115-এ posted line যার source_type ∉ (goods_receipt, purchase_invoice, grn_reversal, opening_balance) → ০টি
১০৫. grn_line.qty_billed > qty_accepted → ০টি; bill line with grn_line_id whose allocation Σ ≠ quantity → ০টি
১০৬. GRNI balance > ৩০ দিন → সতর্কতা (bill হারিয়েছে? supplier-কে চাইতে হবে); > ৯০ দিন → escalate
১০৭. awaiting_receipt bill > ১৪ দিন → সতর্কতা (মাল কোথায়?); po.status = received কিন্তু billed < received > ৩০ দিন → ১০৬-এ১০২ ব্যর্থ হলে প্রথমে ১০৪ — কেউ 2115-এ JV লিখেছে; তারপর ১০৩ — cache ভুল। ১০৬ accounting নয়, কিন্তু accounting-এর ফল: পুরনো GRNI মানে হয় bill হারিয়েছে (supplier একদিন দুটো পাঠাবেন — অধ্যায় ১৫-এর duplicate), নয় মাল ফেরত গেছে কেউ লেখেনি।
Test হিসেবে
test "PO approve-এ entry নেই, commitment আছে":
po = approve(PO(30 × PT-200 @ 20,000))
assert entries(source = purchase_order).count == 0
assert openCommitment(po.line) == 6,00,000
test "GRN: Dr 1145 / Cr 2115, movement, PO partially_received":
grn = postGRN(po, accepted 20)
assert Δ 1145 == +4,00,000 and Δ 2115 == −4,00,000 and Δ 2110 == 0
assert movement(source = grn.line).unit_cost == 20,000
assert po.status == partially_received and po.line.qty_received == 20
test "GRN-এর মাল bill-এর আগে বিক্রি — COGS ঠিক":
postGRN(po, 20); inv = postSalesInvoice(2 × PT-200)
assert Δ 5140 == +40,000 -- GRN-এর cost থেকে
test "bill match → Dr 2115, 1145 স্পর্শ নেই":
postGRN(po, 20); bill = receiveBill(20 @ 20,000)
assert bill.status == matched
postBill(bill)
assert Δ 2115 == +4,00,000 and Δ 1145 == 0 and Δ 2110 == −4,60,000
assert grniBalance(grn.line) == 0
test "qty variance → hold; GRN এলে auto re-match":
postGRN(po, 20); bill = receiveBill(22 @ 20,000)
assert bill.status == on_hold and bill.line.match_status == qty_variance
postGRN(po, 2); assert bill.status == matched
test "price variance বাইরে → hold; amend → matched; পার্থক্য 1145-এ":
postGRN(po, 10); bill = receiveBill(10 @ 20,500) -- 2.5% > 2%
assert bill.status == on_hold
amendPOPrice(po.line, 20,500, reason, cfo); assert bill.status == matched
postBill(bill)
assert Δ 2115 == +2,00,000 and Δ 1145 == +5,000 and layer(L5).unit_cost == 20,500
test "price variance, stock বিক্রি হয়ে গেছে → 5140":
postGRN(po, 10); postSalesInvoice(10 × PT-200); bill = receiveBill(10 @ 20,500); amend; postBill(bill)
assert Δ 1145 == 0 and Δ 5140 == +5,000 -- recost
test "price variance সীমার ভিতরে → auto":
postGRN(po, 10); bill = receiveBill(10 @ 20,020) -- 0.1%, 200 < cap
assert bill.status == matched and bill.line.price_variance == 200
test "GRN ছাড়া stock bill post নয়":
bill = receiveBill(20 @ 20,000, po_line) -- GRN নেই
assert bill.status == awaiting_receipt
assert rejects postBill(bill)
test "over-delivery: tolerance 0 → accepted = বাকি, rejected কাগজে":
postGRN(po, 20); grn2 = postGRN(po, delivered 12)
assert grn2.line.qty_accepted == 10 and qty_rejected == 2
assert movement(grn2.line).quantity == 10
test "ফেরত bill-এর আগে → GRN reversal; পরে → debit note":
postGRN(po, 10); reverseGRNLine(grn.line, 1)
assert Δ 2115 == +20,000 and Δ 1145 == −20,000 and Δ 2110 == 0
postBill(receiveBill(9)); assert rejects reverseGRNLine(grn.line, 1) -- এখন debit note
test "non-stock GRN: Dr 5250 / Cr 2115, movement নেই":
postGRN(chairs_po, 6)
assert Δ 5250 == +27,000 and Δ 2115 == −27,000 and movements(source = grn).count == 0
test "মাস শেষে GRNI == unbilled":
(section ৪-এর আগস্ট)
assert closing(2115, 31-08) == 27,000 == Σ grniBalance(all)
test "2115-এ হাতে JV নয়":
assert rejects post(JV(Dr 2115 / Cr 5250))৬. Financial Statement Impact
INCOME STATEMENT (আগস্ট ২০২৬)
4140 Product Sales 3,12,000
5140 Cost of Goods Sold 2,38,800
5150 Inventory Loss & Write-down (13,800) ← reversal; ঋণাত্মক খরচ
5250 Office Supplies 27,000 ← চেয়ার, bill ছাড়াই
(PO-08-0001-এর 6,00,000 — কোথাও না; stock-এ, তারপর COGS-এ)
BALANCE SHEET (৩১ আগস্ট ২০২৬, নড়াচড়া)
1145 Inventory +3,45,700 (1,98,800 → 5,44,500)
1146 Allowance +13,800 (−13,800 → 0)
2115 GRNI +27,000 ← current liability; "Accrued Expenses"-এর পাশে
2110 Accounts Payable +6,72,175 (4,60,000 + 2,35,750 − 23,575)
1160 VAT Receivable +87,675 (60,000 + 30,750 − 3,075)
CASH FLOW (indirect)
increase in inventory −3,45,700; increase in payables (2110 + 2115) +6,99,175Balance Sheet-এ 2115 আলাদা লাইনে, না 2110-এর সাথে? আলাদা — reader-এর জন্য “A/P” মানে “bill এসেছে, due date আছে”; GRNI-র কোনোটাই নেই। Auditor-ও দুটোকে আলাদা মাপেন — GRNI-র প্রতিটি সারির পিছনে একটা GRN আর একটা challan চান। অধ্যায় ২১-এ 1130 আর 1170-এর যে যুক্তি, এটা তার liability-সংস্করণ।
৭. Common Developer Mistakes
| ভুল | কী ঘটে | সঠিক পথ |
|---|---|---|
| PO-তে journal entry | না আসা মালের দেনা, না হওয়া খরচ | PO = commitment memo; entry GRN-এ |
| GRNI নেই — bill এলে তবে stock | মাল গুদামে অথচ খাতায় নেই; বিক্রি আটকে বা COGS শূন্য | GRN → Dr 1145 / Cr 2115 |
| GRN-এ সরাসরি Cr 2110 | supplier-এর subledger-এ bill-বিহীন দেনা, aging ভুল, due date নেই | 2115; bill-এ 2110 |
| Bill post-এ আবার Dr 1145 | stock দুবার ঢুকল | GRN-linked line → Dr 2115 |
| GRNI clear bill-এর দরে | GRNI-তে দর-পার্থক্যের ধুলো, কখনো শূন্য হয় না | GRN cost-এ clear; পার্থক্য আলাদা line |
| Tolerance নেই | ২০ টাকার জন্য bill hold, নিয়ম বন্ধ | qty/price/abs cap, table-এ |
| Tolerance শুধু % | ১ কোটির bill-এ ১% = ১ লাখ auto-pass | % AND abs cap |
| Two-way match (PO ↔ bill) | মাল না এলেও দেনা | GRN বাধ্যতামূলক stock-এ |
| Over-delivery গুদামে ঢুকিয়ে PO-র বেশি | commitment ভাঙে, বাড়তি বিল | tolerance; বাকি gate-এ ফেরত, qty_rejected |
| Bill-এর পরে GRN reversal | match ভাঙে, GRNI ঋণাত্মক | bill-এর পরে debit note |
| PO amend-এ পুরনো GRN-এর cost বদল | GRNI subledger ≠ GL | GRN cost অক্ষত; পার্থক্য bill-এ |
| দর-পার্থক্য সবসময় খরচে | বিক্রি না হওয়া stock-এর cost ভুল | গুদামে → 1145 (recost); বিক্রি → 5140 |
qty_received cache-কে সত্য ধরা | GRN reversal-এ cache পিছিয়ে | যাচাই ১০৩; rebuild |
| Non-stock PO-র GRN নেই | চেয়ারের খরচ bill-এর মাসে; accrual হাতে | acceptance/GRN সবার জন্য |
| GRNI report কেউ দেখে না | ৯০ দিনের GRNI, bill হারিয়েছে, supplier দুটো পাঠায় | যাচাই ১০৬; মাসিক review |
| Match client-side / form-এ | ”matched” পাঠিয়ে দেওয়া যায় | server-এ match(); client status পাঠাতে পারে না |
| 2115-এ হাতে JV | subledger ≠ GL, কে করেছে জানা নেই | control account, block; যাচাই ১০৪ |
| GRNI আর A/P এক লাইনে Balance Sheet-এ | reader ভাবেন bill আছে | আলাদা লাইন |
দ্বিতীয় আর পঞ্চম সবচেয়ে ব্যয়বহুল। দ্বিতীয়টা ছাড়া inventory আর purchase একসাথে চলে না — গুদাম বলে “আছে”, খাতা বলে “নেই”, আর মাসের শেষে হিসাবরক্ষক Excel-এ accrual লেখেন। পঞ্চমটা নীরব: দু’বছর পরে 2115-এ ৪৭,৩২০ টাকার ধুলো, কোন GRN-এর কেউ জানে না, আর auditor জিজ্ঞেস করেন।
৮. Exercises
সেট ক — entry লিখুন (VAT ১৫%, stock item, FIFO)
১। PO: ৫০টা keyboard @ ১,২০০। entry? commitment?
২। GRN: ৩০টা এল। entry, movement, PO status।
৩। Bill: ৩০ @ ১,২০০ + VAT। match? entry। GRNI balance।
৪। GRN: বাকি ২০-র জায়গায় ২৪ এল; over_delivery_pct ১০। কত accepted? entry।
৫। Bill: ২২ @ ১,২৫০ + VAT; price_pct ২, abs cap ৫০০। match status? কেন?
ক্রেতা ১,২৫০ মেনে নিলেন, ২২টাই গুদামে। entry।
৬। ৫ নম্বরে ২২-এর ৮টা bill-এর আগেই বিক্রি হয়ে গিয়েছিল। entry কী বদলাবে?
৭। GRN-এর ২টা ত্রুটিপূর্ণ, bill আসেনি। entry। PO-র qty_received?
৮। ৭ নম্বর, কিন্তু bill post হয়ে গেছে। entry।
৯। Service PO: office renovation ১,৫০,০০০, দুই milestone (৬০% / ৪০%)। প্রথম
milestone সই হলো ২৮-০৮, bill ০৫-০৯। আগস্টের entry। সেপ্টেম্বরের।সেট খ — GRNI ও মাস শেষ
১০। ৩১-০৮-এ: GRN-A 10 × 500 (billed 10), GRN-B 8 × 700 (billed 5), GRN-C 3 × 1,000 (billed 0),
GRN-D 4 × 200 (billed 4, তারপর ১টা bill-এর আগে ফেরত — সম্ভব?)। GRNI report লিখুন,
GL 2115 কত হওয়া উচিত।
১১। অধ্যায় ১৮-এ আগস্টের বিদ্যুৎ bill-এর জন্য accrual লিখতে হতো। চেয়ারের জন্য এখন লাগে না —
কেন? বিদ্যুতের জন্য এখনো লাগে — কেন? কোন শর্তে বিদ্যুৎ-ও GRNI-তে আসতে পারে?
১২। GRNI-তে ১২০ দিনের একটা সারি, ৮৫,০০০। তিনটি সম্ভাব্য কারণ, প্রতিটির সমাধান।
১৩। Section ৪-এর আগস্টে GRNI না থাকলে (bill-এ stock) — ০৭-০৮-এর বিক্রির COGS কত হতো?
Balance Sheet ৩১-০৮-এ কী ভুল থাকত?সেট গ — কোনটা ভুল
১৪। PO approve: Dr 1145 6,00,000 / Cr 2110 6,00,000
১৫। GRN: Dr 1145 4,00,000 / Cr 2110 [টেক] 4,00,000
১৬। Bill (GRN আছে): Dr 1145 4,00,000 / Dr 1160 60,000 / Cr 2110 4,60,000
১৭। Bill @ 20,500, GRN @ 20,000: Dr 2115 2,05,000 / Dr 1160 30,750 / Cr 2110 2,35,750
১৮। Bill-এর পরে ১টা ফেরত: Dr 2115 20,500 / Cr 1145 20,500
১৯। match_tolerances: price_pct 5, abs cap নেই
২০। purchase_invoices.match_status — form থেকে POST-এ আসে
২১। PO amend-এ: UPDATE goods_receipt_lines SET unit_cost = 20,500 WHERE po_line_id = …সেট ঘ — নকশা
২২। একটা bill line দুটো GRN-এ ভাগ (১২ + ৮), দুটো GRN-এর unit_cost আলাদা (PO amend
মাঝখানে)। GRNI clear-এর line কয়টা? price_variance কীভাবে হিসাব?
২৩। Bill আগে, মাল পরে — "নরম" নীতি (1147 Goods in Transit)। entry দুটো লিখুন।
GRNI-র সাথে সম্পর্ক কী? কোন ক্ষেত্রে দুটোই একসাথে লাগে?
২৪। Segregation: PO approve, GRN, bill approve, payment — চারটি কাজ। ন্যূনতম কয়জন
মানুষ? কোন দুটো একজনে হলে সবচেয়ে বিপজ্জনক? (অধ্যায় ৪৮-এর আগাম)
২৫। Commitment ledger GL-এর বাইরে — কিন্তু অধ্যায় ৩৩-এ budget report-এ
actual + commitment একসাথে দেখাতে হবে। একই dimension (account, cost_center)
কীভাবে নিশ্চিত করবেন? commitment-এর account কোনটা — 1145 না 5140?
২৬। Supplier ১০০টা PO-র বিপরীতে একটা মাসিক consolidated bill পাঠায়। match()
কীভাবে চলবে? bill_grn_allocation-এ কয়টা সারি? performance?উত্তর আছে Workbook-এর Answer Key, অধ্যায় ২৮-এ।
৯. Developer Challenge
একটি ProcureToPayService নকশা করুন — PO থেকে payment পর্যন্ত, তিন-মুখী match সহ — Part 4-এর দ্বিতীয় project (Projects ও Exit Criteria: Procure-to-pay accounting lifecycle)।
যা যা নকশা করবেন:
১. Schema —
purchase_orders/lines(status machine,qty_received/billed/cancelledcache,po_price_log),goods_receipts/lines(qty_delivered/accepted/rejected,unit_costPO থেকে,stock_movement_id),purchase_invoice_lines-এgrn_line_id+match_status+price_variance,bill_grn_allocation,match_tolerances(scope-ধরে),commitments। অধ্যায় ১৫-এর schema ভাঙবেন না — শুধু column যোগ।২.
postGRN()— stock ও non-stock এক function;is_stock_itemmovement ঠিক করে; 2115-এgrn_linedimension; segregation (গুনলেন ≠ কিনলেন)। Over-delivery tolerance; rejected কাগজে, খাতায় নয়।৩.
match()— qty (GRN lines receipt-date ক্রমে, allocation), price (% AND abs cap), amount; ফলmatched | qty_variance | price_variance | amount_error | no_receipt; hold; GRN post ও PO amend-এ auto re-match। Consolidated bill (exercise ২৬)।৪.
postBill()— অধ্যায় ১৫-এরbuildDocumentEntryঅক্ষত, শুধুlineAccount()বদল: GRN-linked → 2115 GRN cost-এ; দর-পার্থক্য → অধ্যায় ২৭-এরlandedCost()(গুদামে 1145, বিক্রি 5140) বা খরচে। GRNI-তে ধুলো শূন্য — test।৫. GRN reversal (bill-এর আগে) বনাম debit note (পরে) — একটাই user action “ফেরত”, system ঠিক করে কোন পথ; দুটোতেই stock return_out মূল layer থেকে।
৬. PO lifecycle — partial, received, closed (billed == received), short-close (commitment মুক্ত), cancel; amend-এ log ও অনুমতি; আগের GRN cost অক্ষত।
৭. GRNI report ও aging;
/traceপাতা: PO → GRN → bill → DN → payment এক নজরে; commitment report (অধ্যায় ৩৩-এর জন্য)।৮. রাতের যাচাই ১০২–১০৭; section ৫-এর চৌদ্দটি test, আর: (ক) GRN post ও bill match একসাথে দুই user — allocation দুবার নয়; (খ) PO amend চলাকালে bill post — কোন দর? (গ) ১০,০০০ open GRN line-এ ১০২ < ১ সেকেন্ড।
৯. অধ্যায় ৩০-এর জন্য — asset-এর GRN-এ
fixedAssetRegister.stage(); অধ্যায় ৩৩-এর জন্য — PO approve-এbudgetCheck()hook; অধ্যায় ৫৪-এর জন্য — 1147 Goods in Transit-এর জায়গা। তিনটিই এখন শুধু interface।৩ আর ৪ একসাথে আসল পরীক্ষা। Match-এর নিয়ম কঠোর হলে হিসাবরক্ষক নিয়ম বন্ধ করে দেবেন; নরম হলে supplier যা লেখেন তা-ই দেনা। আর GRNI clear ভুল দরে হলে 2115-এ এমন ধুলো জমবে যার কোনো GRN নেই — অধ্যায় ৯-এর control account-এর নিয়ম ভাঙার সবচেয়ে সূক্ষ্ম রূপ। GRN-এর cost-এ clear, পার্থক্য আলাদা line-এ, tolerance table-এ — এটা পারলে অধ্যায় ৪৪-এর Reconciliation Architecture-এ GRNI হবে আপনার সবচেয়ে সহজ উদাহরণ।
১০. Summary Card
চার document, দুই event
PO entry নেই; commitment memo
GRN Dr 1145 / 15xx / 5xxx Cr 2115 GRNI (PO-র দরে × accepted)
bill Dr 2115 (GRN cost) Dr 1160 Cr 2110 A/P (+ দর-পার্থক্য আলাদা line)
payment Dr 2110 Cr 1120 (অধ্যায় ১৬)GRNI
balance = Σ (accepted − returned_pre_bill − billed) × grn unit_cost — grn_line-ধরে
মাস শেষে = "মাল এসেছে, bill আসেনি" = মালের accrual, স্বয়ংক্রিয়
control: হাতে JV নয়; Balance Sheet-এ A/P থেকে আলাদা লাইনেThree-way match
qty bill ≤ Σ GRN unbilled (po_line-ধরে) tol 0
price |bill − PO| ≤ price_pct AND ≤ abs cap tol ১–২%, cap ৫০০
amount qty × price == line total
ফল matched → post; বাইরে → on_hold; GRN নেই → awaiting_receiptদর-পার্থক্য
গুদামে → 1145 (layer recost — অধ্যায় ২৭-এর landedCost)
বিক্রি → 5140
standard → 5160 PPV (অধ্যায় ৫৭)ফেরত
bill-এর আগে GRN reversal: Dr 2115 / Cr 1145; qty_received কমে
bill-এর পরে debit note: Dr 2110 / Cr 1145, Cr 1160 (অধ্যায় ১৯)Developer checklist
□ PO-তে entry নেই; commitments memo ledger
□ GRN: qty_delivered / accepted / rejected; unit_cost PO থেকে; stock হলে movement
□ 2115 line-এ grn_line dimension; subledger = grn_line
□ purchase_invoice_lines: grn_line_id, po_line_id, match_status, price_variance; bill_grn_allocation
□ match_tolerances table: qty_pct, price_pct, abs cap, over_delivery_pct; scope-ধরে
□ match() server-এ; client status পাঠাতে পারে না; GRN/amend-এ auto re-match
□ bill: GRN-linked line → Dr 2115 GRN cost-এ; পার্থক্য → landedCost() / খরচ
□ GRN ছাড়া stock bill post নয় (awaiting_receipt)
□ ফেরত: bill-এর আগে reversal, পরে debit note — system ঠিক করে
□ PO amend → log; আগের GRN cost অক্ষত
□ short-close → commitment মুক্ত
□ non-stock PO-ও GRN/acceptance → accrual স্বয়ংক্রিয়
□ GRNI report + aging, মাসিক review; /trace
□ 2115 control: হাতে JV block
□ রাতে: ১০২ GL == Σ grn_line, ১০৩ cache, ১০৪ অচেনা source, ১০৫ over-billed, ১০৬ পুরনো GRNI, ১০৭ awaiting_receiptপরবর্তী অধ্যায়
অধ্যায় ২৯ — HRM ও Payroll Accounting: এ পর্যন্ত যা কিনেছেন — মাল, চেয়ার, সেবা — তার একটা supplier আছে, একটা bill আছে। এবার সবচেয়ে বড় খরচটা, যার কোনো bill নেই: বেতন। একটা payroll run-এ একসাথে অনেক account: gross salary খরচ, কিন্তু কর্মীর হাতে যায় net; মাঝখানে income tax (TDS), provident fund (কর্মীর অংশ ও প্রতিষ্ঠানের অংশ), loan কিস্তি, advance সমন্বয়। একটা entry, দশটা line, তিনটে দেনা — কর্মীর কাছে, সরকারের কাছে, fund-এর কাছে। আর একটা নতুন কাঠামো: কর্মী-ধরে subledger, কিন্তু GL-এ একটাই line — অধ্যায় ১৪-এর তিন স্তর, এবার মানুষের জন্য।