Complete documentation of goods receipt workflow including receiving, inspection, acceptance/rejection decisions, batch tracking, and stock updates. The GRN is the critical link between PO fulfillment and inventory management.
A Goods Receipt Note documents the physical receipt of goods from a vendor against a Purchase Order. It triggers inventory updates, enables three-way matching, and may include quality inspection before acceptance.
| Status | Description | Stock Impact | Next States |
|---|---|---|---|
| DRAFT | GRN created but not submitted for processing | None | SUBMITTED, CANCELLED |
| SUBMITTED | Submitted for inspection/acceptance | None (Pending) | ACCEPTED, PARTIAL, CANCELLED |
| ACCEPTED | All items fully accepted after inspection | Full qty added | COMPLETED |
| PARTIAL | Some items accepted, some rejected or pending | Accepted qty added | COMPLETED |
| COMPLETED | GRN processing fully complete | Finalized | - (Terminal) |
| CANCELLED | GRN cancelled before completion | None/Reversed | - (Terminal) |
Quality inspection is a separate workflow that runs in parallel with GRN processing. Each GRN item can have its own inspection status:
| Inspection Status | Description | Result |
|---|---|---|
| PENDING | Awaiting quality inspection | Items in quarantine zone |
| IN_PROGRESS | Inspection being performed by QC team | Items being tested |
| PASSED | All items passed quality checks | accepted_qty = received_qty |
| FAILED | All items failed quality checks | rejected_qty = received_qty |
| PARTIAL | Some items passed, some failed | accepted_qty + rejected_qty = received_qty |
For items with track_batch = true or track_expiry = true, inspection is mandatory. Items cannot be moved to available stock until inspection is complete.
id, grn_number - Primary key and unique referencepo_id - Mandatory PO referencevendor_id - Vendor who shipped goodswarehouse_id - Receiving warehousereceipt_date - Date goods receiveddelivery_note_number, delivery_note_date - Vendor's delivery documentvehicle_number, driver_name - Transport detailsreceived_by - User who received goodsstatus - DRAFT, SUBMITTED, ACCEPTED, PARTIAL, COMPLETED, CANCELLEDinspection_status - PENDING, IN_PROGRESS, PASSED, FAILED, PARTIALinspection_date, inspected_by, inspection_notestotal_qty, total_received_qty, total_accepted_qty, total_rejected_qtygrn_id - Parent GRN referencepo_item_id - PO line item referenceitem_id - Inventory itemordered_qty - Quantity on POreceived_qty - Quantity physically receivedaccepted_qty - Quantity passing inspectionrejected_qty - Quantity failing inspectionuom_id, unit_price, line_totalbatch_number, serial_numbers (array)manufacturing_date, expiry_datestorage_location_id - Put-away locationinspection_status, rejection_reasonitem_id, warehouse_idbatch_number - Unique within item+warehouseserial_number - For serialized itemsmanufacturing_date, expiry_datequantity - Current batch quantitycost_price - Cost at time of receiptgrn_id - Source GRN referencesupplier_batch_number - Vendor's batch referencestatus - AVAILABLE, QUARANTINE, EXPIRED, CONSUMEDmovement_number - Unique movement referencemovement_type - RECEIPT, ISSUE, TRANSFER, ADJUSTMENT, RETURN, SCRAPitem_id, quantity, uom_idfrom_warehouse_id, from_location_idto_warehouse_id, to_location_idbatch_number, serial_numberunit_cost, total_costreference_type = 'GRN', reference_id = grn_idResults of inspection checklist items
id - UUID primary keygrn_item_id - Reference to the GRN line item inspectedchecklist_item_id - Reference to the checklist template itemresult - PASS, FAIL, N/Ameasured_value - Actual measured/observed valueexpected_value - Expected value from checklist specremarks - Inspector commentsinspected_by - User who performed the checkinspected_at - Timestamp of inspectionPhotographic/document evidence from inspections
id - UUID primary keygrn_item_id - Reference to the inspected GRN itemchecklist_result_id - Optional link to specific checklist resultfile_path - Storage path of the evidence filefile_name - Original file namefile_type - MIME type (image/jpeg, application/pdf, etc.)description - Caption or description of the evidenceuploaded_by - User who uploaded the evidenceuploaded_at - Timestamp of uploadDetails of rejected goods during GRN
id - UUID primary keygrn_item_id - Reference to rejected GRN line itemrejected_qty - Quantity rejectedrejection_reason - Reason code (DAMAGED, WRONG_ITEM, QUALITY_FAIL, EXPIRED, etc.)rejection_notes - Detailed rejection descriptiondisposition - RETURN_TO_VENDOR, SCRAP, REWORK, HOLDreturn_reference - Debit note or return shipment referencerejected_by - User who recorded the rejectionrejected_at - Timestamp of rejectionFile attachments on GRN records
id - UUID primary keygrn_id - Parent GRN referencefile_name - Original file namefile_path - Storage pathfile_type - MIME typefile_size - Size in bytesattachment_type - DELIVERY_NOTE, INVOICE, PHOTO, WEIGHT_SLIP, OTHERdescription - Attachment descriptionuploaded_by - User who uploadeduploaded_at - Timestamp of uploadTransporter/carrier master data
id - UUID primary keytransporter_code - Unique transporter identifiertransporter_name - Company name of the carriergstin - GST identification numbercontact_person - Primary contact namephone - Contact phone numberemail - Contact emailaddress - Registered addresstransporter_type - OWN_FLEET, THIRD_PARTY, COURIERis_active - Active/inactive flagcreated_at, updated_at - Audit timestampsGRN is created against a specific PO. The system pulls PO items with outstanding quantities and allows the receiver to enter received quantities.
-- Get outstanding PO items for GRN creation SELECT poi.id AS po_item_id, poi.item_id, poi.item_description, poi.quantity - poi.received_qty - poi.cancelled_qty AS outstanding_qty, poi.uom_id, poi.unit_price FROM procurement.po_items poi WHERE poi.po_id = :po_id AND poi.quantity > poi.received_qty + poi.cancelled_qty;
Enter physical receipt information including delivery note, vehicle details, and quantities received per line item. Capture batch/serial numbers for tracked items.
Received quantity can differ from ordered quantity. System allows over-receipt (with approval) and short-receipt scenarios. Variance is tracked for vendor performance.
For items requiring inspection, goods are held in quarantine. QC team inspects and records accepted/rejected quantities with reasons for rejection.
-- Record inspection results UPDATE inventory.grn_items SET accepted_qty = :accepted_qty, rejected_qty = :rejected_qty, inspection_status = CASE WHEN :rejected_qty = 0 THEN 'PASSED' WHEN :accepted_qty = 0 THEN 'FAILED' ELSE 'PARTIAL' END, rejection_reason = :rejection_reason WHERE id = :grn_item_id;
Upon acceptance, stock levels are updated. Batch records are created for tracked items. Stock movements are recorded for audit trail.
-- Update stock levels for accepted items INSERT INTO inventory.stock_levels (item_id, warehouse_id, quantity_on_hand, uom_id) VALUES (:item_id, :warehouse_id, :accepted_qty, :uom_id) ON CONFLICT (item_id, warehouse_id, storage_location_id) DO UPDATE SET quantity_on_hand = inventory.stock_levels.quantity_on_hand + :accepted_qty, last_movement_date = CURRENT_TIMESTAMP; -- Create stock movement record INSERT INTO inventory.stock_movements ( movement_number, movement_type, item_id, to_warehouse_id, quantity, uom_id, reference_type, reference_id ) VALUES ( :movement_number, 'RECEIPT', :item_id, :warehouse_id, :accepted_qty, :uom_id, 'GRN', :grn_id );
PO item received quantities are updated. PO status transitions to PARTIAL or RECEIVED based on total receipt.
-- Update PO item received quantity UPDATE procurement.po_items SET received_qty = received_qty + :accepted_qty, status = CASE WHEN received_qty + :accepted_qty >= quantity - cancelled_qty THEN 'RECEIVED' WHEN received_qty + :accepted_qty > 0 THEN 'PARTIAL' ELSE status END WHERE id = :po_item_id;
For items with track_batch = true:
For items with track_serial = true:
For items with track_expiry = true, expiry date is mandatory at GRN. System can alert for approaching expiry and auto-quarantine expired batches.
SELECT g.grn_number, po.po_number, v.vendor_name, g.receipt_date, SUM(gi.ordered_qty) AS total_ordered, SUM(gi.received_qty) AS total_received, SUM(gi.accepted_qty) AS total_accepted, SUM(gi.rejected_qty) AS total_rejected, ROUND( (SUM(gi.received_qty) - SUM(gi.ordered_qty)) / NULLIF(SUM(gi.ordered_qty), 0) * 100, 2 ) AS qty_variance_pct FROM inventory.grns g JOIN inventory.grn_items gi ON g.id = gi.grn_id JOIN procurement.purchase_orders po ON g.po_id = po.id JOIN vendor.vendors v ON g.vendor_id = v.id WHERE g.receipt_date >= :start_date GROUP BY g.id, g.grn_number, po.po_number, v.vendor_name, g.receipt_date;
SELECT i.item_code, i.item_name, sb.batch_number, sb.expiry_date, sb.quantity, sb.status, w.warehouse_name, sb.expiry_date - CURRENT_DATE AS days_to_expiry FROM inventory.stock_batches sb JOIN inventory.items i ON sb.item_id = i.id JOIN inventory.warehouses w ON sb.warehouse_id = w.id WHERE sb.expiry_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '30 days' AND sb.status = 'AVAILABLE' AND sb.quantity > 0 ORDER BY sb.expiry_date;
SELECT g.grn_number, g.receipt_date, gi.item_id, i.item_name, gi.received_qty, gi.inspection_status, u.full_name AS received_by FROM inventory.grns g JOIN inventory.grn_items gi ON g.id = gi.grn_id JOIN inventory.items i ON gi.item_id = i.id JOIN admin.users u ON g.received_by = u.id WHERE g.inspection_status IN ('PENDING', 'IN_PROGRESS') AND gi.inspection_status = 'PENDING' ORDER BY g.receipt_date;
Always verify received quantities against delivery note before creating GRN. Document any discrepancies with photos if possible.
Complete quality inspection within 24-48 hours of receipt. Prolonged quarantine affects inventory availability and vendor payment processing.
For rejected items, create return documentation immediately. Track rejection reasons for vendor performance analysis and future sourcing decisions.