Menu
MRO Purchasing Data Model: Requisition to Invoice Match

MRO Purchasing Data Model: Requisition to Invoice Match

Requisition, purchase order, goods receipt and invoice: the fields, the tolerances that block an invoice, and supplier on-time delivery two defensible ways.
MRO Purchasing Data Model: Requisition to Invoice Match

Key takeaways

  • Four records, not one. A requisition asks, a purchase order commits, a goods receipt says what arrived, an invoice says what you are billed.
  • On three lines ordered at €2,732.00 the supplier billed €2,802.00 while only €2,640.00 was payable, an over-billing of €162.00 or 6.1%.
  • With no over-receipt rule, 111 pieces against a 100 piece line put €1,376.40 of stock behind a €1,240.00 order, 11.0% more than anyone bought.
  • A box-to-piece factor of 100 where the truth is 50 books 2,000 pairs when 1,000 arrived, inventing €1,080.00 of stock value.
  • One quarter, 162 received lines: on-time delivery is 76.5% by line and 61.5% by value, a gap of 15.0 percentage points from identical rows.

Four records, four different facts

MRO purchasing runs on four records, and each asserts something the other three cannot. A requisition says somebody asked, a purchase order says the company committed, a goods receipt says goods arrived, an invoice says a supplier wants money.

Four people write them at four moments, and that separation is the point. The model earns its keep when the four disagree, because it can say which one is wrong.

RecordWhat it asserts
Requisi­tionSomebody inside the plant asked to buy something
Pur­chase orderThe company com­mitted to a named sup­plier
Goods receiptGoods physically reached the store­room
InvoiceThe sup­plier says you owe this amount

The storeroom is where maintenance meets finance, and this join is the commonest place a plant loses the true cost of a repair. A part paid for but never tied to the job that needed it lands on the storeroom, not on the machine.

What breaks when you collapse two of them

Most setups collapse at least one pair to save a screen.

CollapseWhat you lose
Requisi­tion into orderRefused and cancelled asks leave no row at all
Order into receiptNo open balance, so partial deliv­eries cannot be tracked
Receipt into invoiceStock is booked from the bill, not from what arrived
Invoice into orderEvery invoice matches by con­struction, so price drift is invisible

Match an invoice against the order alone and a short delivery is paid in full, because the order is exactly what the supplier billed against.

On the three lines worked through below, the supplier billed €2,802.00 against orders worth €2,732.00, while goods supported only €2,640.00. That is €162.00 of over-billing, 6.1% above the payable amount.

What this page covers that the others do not

The conceptual difference between the two documents belongs to our guide on work order vs purchase order. The storeroom itself, the part master and the stock policy sit in benefits of spare parts management.

What a missing part costs in downtime minutes is worked out in spare parts availability and downtime, and is not recomputed here. Reorder point and safety stock arithmetic belongs to safety stock vs reorder point, and is not re-derived here either.

This page adds the records themselves: the fields, the tolerance fields that decide whether an invoice is paid, and the two clocks that measure a supplier. The wider record set around it sits in our CMMS data model, and the ask that starts many purchases in the work request data model.

The requisition: somebody asks to buy

A requisition is an internal ask with no supplier attached yet. It exists so the decision to spend is recorded separately from the decision about who to spend it with.

Keep it as its own record and refused asks survive. Fold it into the purchase order and every rejected line disappears, because an order never raised leaves nothing behind.

FieldTypeWhy
requisi­tion_ididManda­tory. Own key, quotable before a sup­plier exists
store­room_ididManda­tory. Which store will hold or issue the parts
requested_byidManda­tory. Employee record, never a typed name
requested_attime­stampManda­tory. Server time, starts the purchas­ing clock
need_by_datedateManda­tory. When the plant needs it in its hand
purposeenumManda­tory. Stock, work order, project, emer­gency
work_order_ididManda­tory when purpose is work order
cost_centreidManda­tory. Who carries the spend if nothing is issued
approval_statusenumManda­tory. Draft, sub­mitted, approved, rejected, can­celled
approved_byidManda­tory on approval. A person, never a role
approved_attime­stampManda­tory on approval. Stops the approval clock
cur­rencychar3Manda­tory. Fixed at the header, never per line

need_by_date is the date the plant needs the part in its hand. It is not a delivery date, which is a supplier's opinion about shipping.

work_order_id is what makes the true cost of a repair possible. Parts are one half of that cost and labour is the other, specified in our maintenance labour data model.

The requisition line

Lines are approved and refused one at a time, so each needs its own key and status. A requisition with nine lines can be approved on seven, and a header status cannot express that.

FieldTypeWhy
req_line_ididManda­tory. Own key, because lines are decided one by one
part_ididOptional. Points at the cata­logue row when the part exists
free_text_desctextManda­tory when part_id is empty
requested_qtydecimalManda­tory. Counted in the ordering unit
requested_uomenumManda­tory. The unit the number above is counted in
est_unit_pricedecimalOptional. What the requester expects to pay
sug­gested_sup­plieridOptional. A hint for the buyer, never a commit­ment
line_need_bydateOptional. Over­rides the header date for this line
line_statusenumManda­tory. Open, con­verted, rejected, can­celled
po_line_ididOptional. Filled when the line becomes an order line
rejec­tion_codeenumManda­tory when the line is rejected

The purchase order: the company commits

A purchase order is the first record a third party can hold you to. Everything on it is a promise, so everything on it is frozen when it is sent.

FieldTypeWhy
po_noidManda­tory. The number the sup­plier quotes back to you
sup­plier_ididManda­tory. A sup­plier record, never typed text
po_statusenumManda­tory. Draft, sent, acknowledged, part received, closed
ordered_byidManda­tory. The buyer who com­mitted the company
po_sent_attime­stampManda­tory. Starts the measured lead time
cur­rencychar3Manda­tory. Copied onto the order, never read live
pay­ment_termsenumManda­tory. Frozen at the moment the order is sent
deliv­ery_addr_ididManda­tory. The site and the door, not the head office
inco­termenumOptional. Decides who pays freight and who carries risk
freight_chargedecimalOptional. On the order, so the invoice can match it

po_sent_at makes lead time measurable and is the field most often left empty. An order created one day and sent three days later carries a three day hole nobody will see again.

The purchase order line

FieldTypeWhy
po_line_ididManda­tory. Own key, the unit of matching and receiving
part_ididManda­tory once the line may be received into stock
order_qtydecimalManda­tory. Counted in order_uom, not in pieces
order_uomenumManda­tory. Box, drum, reel, metre, piece
unit_pricedecimalManda­tory. Price per order_uom, in the header cur­rency
conver­sion_factordecimalManda­tory. Stock units inside one order unit
requested_datedateManda­tory. The date you asked the sup­plier for
con­firmed_datedateManda­tory once acknowledged. The date they promised
received_qtydecimalManda­tory. A running total, written only by receipts
invoiced_qtydecimalManda­tory. A running total, written only by invoices
line_statusenumManda­tory. Open, part received, received, closed, can­celled
req_line_ididOptional. Points back at the ask that caused the line

received_qty and invoiced_qty are totals maintained by the receipt and invoice rows, never typed. If a person can edit either, the match is a formality.

confirmed_date is the supplier's answer and requested_date is your ask. Storing only one is the commonest reason two honest people report different on-time figures from the same data.

The goods receipt: what physically arrived

The receipt is the only record written by somebody who touched the goods. That makes it the anchor, and it is why an invoice should never be matched without one.

FieldTypeWhy
receipt_line_ididManda­tory. Own key, one row per deliv­ery per line
po_line_ididManda­tory. Which commit­ment this deliv­ery answers
received_qtydecimalManda­tory. What was counted, not what the note claims
receipt_uomenumManda­tory. The unit counted at the door
received_attime­stampManda­tory. Server time, stops the lead time clock
received_byidManda­tory. A person, never a store or a depart­ment
deliv­ery_note_notextManda­tory. The sup­plier's own refer­ence for the drop
condi­tion_codeenumManda­tory. Good, damaged, wrong item, short packed
batch_or_serialtextOptional. Manda­tory for parts with a shelf life
put_away_locidManda­tory. The bin, so the part can be found again
over_receipt_flagbool­eanManda­tory. Set when the line balance is exceeded
receipt_statusenumManda­tory. Posted, held, returned, reversed

condition_code earns its place the first time a pallet arrives damaged. Received and rejected is a different fact from not received, and only one of them leaves the supplier still owing you goods.

The supplier record

The supplier record holds two kinds of field. One set is policy copied onto an order, the other is history you measure the supplier by.

FieldTypeWhy
sup­plier_ididManda­tory. Own key, stable across name changes
sup­plier_nametextManda­tory. The legal name, not the rep's name
sup­plier_statusenumManda­tory. Approved, on hold, blocked, retired
cate­goryenumManda­tory. Bearings, elec­trical, lubri­cants, consum­ables
default_cur­rencychar3Manda­tory. Copied onto an order, never read live
pay­ment_termsenumManda­tory. The default the buyer may over­ride
quoted_lead_daysintManda­tory. What they claim, beside what you measure
min_order_valuedecimalOptional. Why small asks get batched into one order
tax_idtextManda­tory. Needed before any invoice can be posted
approved_byidManda­tory. Who let this sup­plier into the plant
approved_untildateOptional. Forces a review rather than silent per­manence
contact_reftextOptional. Where the buyer finds the account manager

quoted_lead_days is a claim, not a measurement. It belongs on the record next to the measured figure, so the gap is visible to anyone who opens the page.

The three-way match, with the arithmetic shown

The match is two comparisons, not one. Quantity is matched receipt against invoice, price is matched order against invoice, and a system doing only one of them passes half the errors you care about.

The tolerance fields that decide the outcome

Tolerances exist because an exact match is too brittle to run a plant on.

FieldWhat it controls
price_toler­ance_pctHow far the invoiced unit price may drift, as a percent­age
price_toler­ance_absA hard cap in money, so a percent­age cannot pass a large sum
over_receipt_pctHow much more than ordered the store may accept
invoice_qty_toler­anceHow far the invoiced quantity may exceed the received quantity
toler­ance_scopeWhether the test runs per line or per invoice
vari­ance_accountWhere an accepted differ­ence posts instead of blocking

The pair of price tolerances matters more than either alone. A drift of 3.0% is trivial on a €40.00 line and is €900.00 on a €30,000.00 line, so the percentage needs a money cap beside it.

Set the rule so a variance passes only inside both limits. tolerance_scope is the quiet one: testing per invoice lets a credit on one line hide an overcharge on another.

Three lines, three outcomes

Three purchase order lines from one week, with what was ordered and what was billed. Money is rounded to the nearest cent, half up, and every percentage to one decimal place.

LineOrderedInvoiced
A bearings40 at €18.5040 at €18.50
B filters12 at €46.0012 at €46.00
C hose200 at €7.20200 at €7.55
LineReceivedOutcome
A bearings40Passes, €740.00 released
B filters10Blocked on quantity
C hose200Blocked on price

Line A passes. Ordered 40 at €18.50 is €740.00, received 40, invoiced 40 at the same price, so both tests return zero and €740.00 is released.

Line B is a short delivery billed in full. Ten cartridges arrived and twelve were invoiced, so the invoice exceeds the receipt by 2 units worth €92.00.

With invoice_qty_tolerance at zero units the line blocks, and the supportable amount is 10 at €46.00 = €460.00. The €92.00 gap is 16.7% of the €552.00 ordered value, paid for goods not in the building.

Line C is a price variance. The order says €7.20 per metre and the invoice says €7.55, a drift of €0.35 per metre, which is 4.9%.

Across 200 metres that is €70.00. Against a price_tolerance_pct of 3.0% and a price_tolerance_abs of €50.00 the line fails both tests and blocks.

Change one number and the same machinery pays. At €7.35 per metre the drift is €0.15, which is 2.1% and €30.00 across the line, inside both limits.

It passes, and the €30.00 posts to the variance account instead of stopping the payment run.

Totalled, the three lines were ordered at €2,732.00 and invoiced at €2,802.00. Priced at the ordered price, the receipts support €2,640.00, so €162.00 of that invoice run has nothing behind it.

Partial deliveries and over-receipt

An MRO line rarely arrives once. The line carries a running balance, and that balance has to survive a delivery that overshoots it.

Take PO‑2026‑0412 line 10: 100 pieces of a V belt at €12.40 each, an ordered value of €1,240.00. Four deliveries arrived against it.

ReceiptQtyRunning balance
GR‑07814040 received, 60 open
GR‑08343575 received, 25 open
GR‑090230105 received, 5 over
GR‑09556111 received, 11 over

The third receipt takes the cumulative quantity past the ordered 100 to 105. One field decides whether the store may take it.

With over_receipt_pct at 5.0%, the allowance on a 100 piece line is 5 pieces, so 105 sits exactly at the cap and the receipt is accepted with the over-receipt recorded.

The fourth receipt breaks the rule. Six more pieces take the cumulative to 111, an over-receipt of 11 against an allowance of 5, so 6 pieces fall outside the tolerance and the receipt is held.

What the over-receipt field decides

ActionWhen it applies
AcceptInside over_receipt_pct, posts to stock, line closes
Accept and amendBuyer raises order_qty first, so the invoice can match
ReturnOutside toler­ance, sup­plier collects at their own cost
HoldOutside toler­ance, goods quaran­tined with no stock value

Now delete the rule and watch the money move. All 111 pieces post to stock at €12.40, which is €1,376.40 behind an order worth €1,240.00.

That is €136.40, or 11.0%, of stock with no order line under it. If the supplier bills for it you pay a quantity nobody approved, and if it never does, your stock account and your goods received not invoiced account disagree by that amount.

The quieter cost lasts longer. Those extra pieces make the item look better stocked than it was ever bought to be, which is a stock policy problem covered in spare parts inventory management.

Unit of measure traps

Storerooms buy in boxes, drums and reels and issue in pieces, litres and metres. The conversion is a field, and when it is wrong the storeroom loses count without anyone making a mistake.

FieldWhy
order_uomThe unit on the order and on the sup­plier price list
stock_uomThe unit the balance is held in, and the one stock value uses
issue_uomThe unit a work order consumes in
conver­sion_factorHow many stock units sit inside one order unit
factor_valid_fromThe date it started, so old receipts keep their own factor
factor_sourceWho set it: the sup­plier cata­logue, a buyer or a stock­take

factor_valid_from is the field nobody specifies until a supplier first changes a pack size. Without it, correcting today's factor silently rewrites the quantity of every receipt ever taken against that part.

A wrong factor, priced out

A line covers 20 boxes of gloves at €54.00 a box, an ordered value of €1,080.00. A box holds 50 pairs, so the correct receipt is 1,000 pairs at €1,080.00 divided by 1,000 = €1.08 per pair.

The catalogue carries a conversion factor of 100 instead of 50. The receipt posts 20 times 100 = 2,000 pairs where 1,000 physically arrived.

FigureCorrectBooked
Pairs received1,0002,000
Value at €1.08€1,080.00€2,160.00
Pairs on the shelf1,0001,000

Valued at a standard cost of €1.08 per pair, the receipt books €2,160.00 against a purchase worth €1,080.00. The error invents €1,080.00, an overstatement of 100.0%.

A stocktake finds 1,000 pairs where the book says 2,000, a shortfall of 1,000 pairs and 50.0% of the book quantity. At 40 pairs a week, the phantom half is 25.0 weeks of cover that does not exist.

The min level never trips, nobody buys gloves, and the first person to notice is an operator at an empty shelf. Setting those levels properly is answered in safety stock vs reorder point.

Lead time is measured, not quoted

Every supplier record carries a quoted lead time and almost none carry the measured one. They are different fields and both belong on the page.

MeasureFormula
Requested lead timerequested_date minus po_sent_at
Promised lead timecon­firmed_date minus po_sent_at
Actual lead timefinal receipt date minus po_sent_at
Vari­ance to promisefinal receipt date minus con­firmed_date
Vari­ance to requestfinal receipt date minus requested_date

Each needs its denominator printed beside it, because the population is a choice and the choice moves the answer.

MeasurePopu­lation it runs on
Actual lead timeLines fully received in the period, dated at final receipt
On time to promiseThe same lines, restricted to those with a con­firmed date
On time to requestThe same lines, restricted to those with a requested date
Open line ageLines not yet fully received, measured to today

Walk one line. PO‑2026‑0412 was sent on 2026‑03‑02, the plant asked for 2026‑03‑16, the supplier confirmed 2026‑03‑23, and the last piece landed on 2026‑03‑27.

Requested lead time is 14 days and promised lead time is 21 days, because the supplier answered with a date of its own. Actual lead time is 25 days.

Against the promise the line is 4 days late. Against the request it is 11 days late, and the supplier's catalogue lead time of 14 days was never once tested.

Worked example: one quarter of MRO purchasing on one storeroom

Storeroom STORE‑01, one calendar quarter, one plant. Every figure below is computed from the rows in these tables and nothing else.

1. The quarter, reconciled

OutcomeRequisi­tion lines
Con­verted to an order line196
Rejected at approval18
Can­celled by the requester12
Still open at quarter end14
Total raised240

Check the arithmetic first: 196 + 18 + 12 + 14 = 240. A quarter that does not reconcile to the raw count has rows in a state nobody agreed on.

Those 196 converted lines produced 180 purchase order lines, because 16 were added to orders that already existed. That is 196 divided by 180 = 1.09 requisition lines per order line.

Of the 180 order lines, 162 were fully received inside the quarter and 18 were still open. Those 162 lines took 219 goods receipts, which is 219 divided by 162 = 1.35 deliveries per line.

The invoice side closed 168 invoice lines: 141 matched clean, 14 blocked on quantity and 13 blocked on price. That is 83.9% clean, with 27 lines or 16.1% stopped by a tolerance.

The 162 received lines were ordered at €211,950.00 and received at €213,860.00. The €1,910.00 difference is over-receipts accepted inside tolerance, 0.9% of the ordered value.

Of that received value, €148,300.00 was issued against work orders inside the quarter and €65,560.00 was still on the shelf. That is 69.3% consumed and 30.7% held.

2. On-time delivery, computed two defensible ways

The 162 received lines came from five suppliers. Both methods run on these same rows and the same definition of on time: received on or before the confirmed date.

Sup­plierLinesOn time
S1 bearings4641
S2 filters5244
S3 elec­trical2415
S4 drives1811
S5 consum­ables2213
Total162124
Sup­plierValue €On time €
S1 bearings38,400.0034,100.00
S2 filters22,750.0019,300.00
S3 elec­trical61,200.0028,900.00
S4 drives74,500.0039,600.00
S5 consum­ables17,010.009,700.00
Total213,860.00131,600.00

Method A counts lines. 124 of 162 arrived on or before the confirmed date, so on-time delivery is 76.5%.

Method B counts money. €131,600.00 of €213,860.00 of received value arrived on time, so on-time delivery is 61.5%.

The gap is 15.0 percentage points out of the same 162 rows. In counts, the 38 late lines are 23.5% of the lines and carry €82,260.00, which is 38.5% of the value.

3. Why an average flatters the wrong supplier

Sup­plierBy lineBy value
S1 bearings89.1%88.8%
S2 filters84.6%84.8%
S3 elec­trical62.5%47.2%
S4 drives61.1%53.2%
S5 consum­ables59.1%57.0%

S2, the filter supplier, looks excellent and is cheap line for line. It carries 52 of the 162 lines, 32.1% of them, and only 10.6% of the value.

S3, the electrical supplier, is the case a line count hides. It runs 62.5% on time by line and 47.2% by value, a drop of 15.3 percentage points inside its own rows.

That gap says its late deliveries are its expensive ones. A supplier punctual with fuses and late with a drive is a different risk from one uniformly 62.5% on time.

A simple average makes it worse again. The mean of the five by-line percentages is 71.3%, while the true combined figure is 124 divided by 162 = 76.5%.

4. A third clock: the date you actually asked for

Every figure so far measured each supplier against its own confirmed date. Re-measure the same 162 lines against the date the plant asked for and the 124 on-time lines become 103.

That is 63.6%, which is 12.9 percentage points below the 76.5% figure, on 21 lines where the supplier confirmed a later date than the one requested and then kept its own promise.

A supplier confirming three weeks against your two week ask is on time in its own terms and late in yours. Only storing both dates lets you say so.

The defence is one line in the report header naming the population and the clock. "On-time delivery, 162 lines fully received in the quarter, measured against the confirmed date" is reproducible, and "supplier on-time delivery 76.5%" is not.

Five questions to ask a vendor about purchasing data

  1. Is a requisition a separate record from a purchase order? Ask them to show a requisition line rejected six months ago, with the reason and the name of the approver, without opening a purchase order.
  2. Show me a three-way match that blocks. Ask for a short delivery billed in full, then ask which field stopped it, who may change that field, and whether the change lands in the audit log.
  3. What happens to a receipt that goes over the ordered quantity? Ask to see the over-receipt tolerance, the disposition options, and where the extra stock value lands if the store accepts it.
  4. How does a purchase unit become an issue unit? Ask them to change a conversion factor in front of you, then ask what happened to the quantity on receipts taken before the change.
  5. What does your on-time delivery report divide by, and which date does it compare against? Ask whether open lines sit in the denominator, whether it counts lines or value, and whether the clock is the requested date or the confirmed one.

Question five separates the tools. Any system shows an on-time percentage, and very few print the population and date field behind it (equipment maintenance software).

How Fabrico helps

Fabrico is an OEE platform with a full CMMS built in, and its inventory module covers the storeroom half of this page rather than the purchasing half.

It holds a parts catalogue with min and max levels, records deliveries into the store, ties consumption to the work order that used the part, and supports stock-takes and a supplier listing.

Your team scans QR codes on parts and machines from the iOS, Android or web app, so what arrives and what is issued is recorded where it happens.

Approval workflows and the audit log record who approved what, and the analytics export to Excel, with a REST API, webhooks and SAP and other ERP connectors when the numbers have to meet your finance system.

Now the part that matters for this page, stated plainly. Fabrico records what arrives and what gets consumed, and the purchase order itself lives in your ERP or purchasing system.

Fabrico does not raise requisitions or purchase orders, does not run a three-way match against supplier invoices, and does not send anything to a supplier. The supplier listing is a list, with no messaging attached to it.

It does not reorder parts on its own, it does not plan or schedule production, and it has no predictive maintenance product. A work order is always created and confirmed by your team.

Everything else on this page is a specification to test a shortlist against rather than a feature list.

Want to see how received parts and work order consumption would look on your own storeroom? Book a 30 minute demo with a Fabrico consultant, no commitment, or contact us with your questions.

Frequently asked questions

What are the four records in MRO purchasing?

A requisition records the ask, a purchase order the commitment to a supplier, a goods receipt what arrived, and an invoice what the supplier says you owe. Four different people write them, which is what makes a disagreement detectable.

What is a three-way match?

It is two comparisons, not one: invoiced quantity against received quantity, and invoiced price against the ordered price. A line passes only when both sit inside the tolerance fields, and otherwise it blocks before payment.

Which fields decide whether an invoice is blocked?

price_tolerance_pct and price_tolerance_abs govern price drift, invoice_qty_tolerance governs billing more than arrived, and tolerance_scope decides whether the test runs per line or per invoice. A variance should pass only inside both price limits.

How should over-receipt be handled?

Store an over_receipt_pct on the line and an action of accept, accept and amend, return or hold. Without that rule, 111 pieces against a 100 piece line at €12.40 book €1,376.40 of stock value behind a €1,240.00 order.

How do you measure supplier lead time instead of quoting it?

Actual lead time is the final receipt date minus po_sent_at, compared against the confirmed date for the supplier's promise and the requested date for what the plant needed. Print the population beside it, because excluding open lines flatters every supplier.

Latest from our blog

Define Your Reliability Roadmap
Validate Your Potential ROI: Book a Live Demo
Define Your Reliability Roadmap
By clicking the Accept button, you are giving your consent to the use of cookies when accessing this website and utilizing our services. To learn more about how cookies are used and managed, please refer to our Privacy Policy and Cookies Declaration