Skip to Content
Go Realm v1 is released 🎉
Accountingবই (Print Edition)অধ্যায় ২৮: Purchase ও GRNI

অধ্যায় ২৮ — 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

ঘটনাDebitCredit
PO approved— (commitment memo)
GRN (stock)Inventory (PO দরে × accepted qty)GRNI
GRN (asset / খরচ)Fixed Asset / ExpenseGRNI
Service acceptanceExpenseGRNI
Bill, match ✓GRNI (GRN cost × qty), VAT ReceivableA/P [supplier]
Bill, দর বেশি (মানা) — stock গুদামেGRNI, Inventory (পার্থক্য), VATA/P
Bill, দর বেশি (মানা) — stock বিক্রি হয়ে গেছেGRNI, COGS (পার্থক্য), VATA/P
Bill, দর কমGRNI, VATA/P, Inventory / COGS (পার্থক্য)
ফেরত, bill-এর আগেGRNIInventory
ফেরত, bill-এর পরে (debit note)A/PInventory, 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 → closed

L5-এর কিছু বিক্রি হয়ে থাকলে সেই অংশের ৫০০ করে 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,500

Bill-এর আগে ধরা পড়লে: 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

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-level
goods_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 : received

Non-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 = closed

GRNI-র 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 line

Amend-এর পরে আগের 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 ≤ budget

GL-এ নয় — কিন্তু 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,175

Balance 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 2110supplier-এর subledger-এ bill-বিহীন দেনা, aging ভুল, due date নেই2115; bill-এ 2110
Bill post-এ আবার Dr 1145stock দুবার ঢুকল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 reversalmatch ভাঙে, GRNI ঋণাত্মকbill-এর পরে debit note
PO amend-এ পুরনো GRN-এর cost বদলGRNI subledger ≠ GLGRN 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-এ হাতে JVsubledger ≠ 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/cancelled cache, po_price_log), goods_receipts/lines (qty_delivered/accepted/rejected, unit_cost PO থেকে, 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_item movement ঠিক করে; 2115-এ grn_line dimension; 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 — অধ্যায় ১৪-এর তিন স্তর, এবার মানুষের জন্য।