Back to ER Diagram
GRN Processing

Goods Receipt Note (GRN) Processing

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.

PostgreSQL
10 Tables
Schema: inventory
Quality Inspection

GRN Workflow Overview

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.

PO Sent to Vendor
Goods Shipped
Create GRN
DRAFT
SUBMITTED
Inspection
ACCEPTED
PARTIAL
REJECTED
Update Stock
COMPLETED
6
GRN Status States
5
Inspection States
Batch
Lot Tracking
Serial
Item Tracking

GRN Status States

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)

Inspection Workflow

Quality inspection is a separate workflow that runs in parallel with GRN processing. Each GRN item can have its own inspection status:

PENDING
IN_PROGRESS
PASSED
FAILED
PARTIAL
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

Inspection Note

For items with track_batch = true or track_expiry = true, inspection is mandatory. Items cannot be moved to available stock until inspection is complete.

Database Schema

inventory.grns

  • id, grn_number - Primary key and unique reference
  • po_id - Mandatory PO reference
  • vendor_id - Vendor who shipped goods
  • warehouse_id - Receiving warehouse
  • receipt_date - Date goods received
  • delivery_note_number, delivery_note_date - Vendor's delivery document
  • vehicle_number, driver_name - Transport details
  • received_by - User who received goods
  • status - DRAFT, SUBMITTED, ACCEPTED, PARTIAL, COMPLETED, CANCELLED
  • inspection_status - PENDING, IN_PROGRESS, PASSED, FAILED, PARTIAL
  • inspection_date, inspected_by, inspection_notes
  • total_qty, total_received_qty, total_accepted_qty, total_rejected_qty

inventory.grn_items

  • grn_id - Parent GRN reference
  • po_item_id - PO line item reference
  • item_id - Inventory item
  • ordered_qty - Quantity on PO
  • received_qty - Quantity physically received
  • accepted_qty - Quantity passing inspection
  • rejected_qty - Quantity failing inspection
  • uom_id, unit_price, line_total
  • batch_number, serial_numbers (array)
  • manufacturing_date, expiry_date
  • storage_location_id - Put-away location
  • inspection_status, rejection_reason

inventory.stock_batches

  • item_id, warehouse_id
  • batch_number - Unique within item+warehouse
  • serial_number - For serialized items
  • manufacturing_date, expiry_date
  • quantity - Current batch quantity
  • cost_price - Cost at time of receipt
  • grn_id - Source GRN reference
  • supplier_batch_number - Vendor's batch reference
  • status - AVAILABLE, QUARANTINE, EXPIRED, CONSUMED

inventory.stock_movements

  • movement_number - Unique movement reference
  • movement_type - RECEIPT, ISSUE, TRANSFER, ADJUSTMENT, RETURN, SCRAP
  • item_id, quantity, uom_id
  • from_warehouse_id, from_location_id
  • to_warehouse_id, to_location_id
  • batch_number, serial_number
  • unit_cost, total_cost
  • reference_type = 'GRN', reference_id = grn_id

logistics.inspection_checklist_results

Results of inspection checklist items

  • id - UUID primary key
  • grn_item_id - Reference to the GRN line item inspected
  • checklist_item_id - Reference to the checklist template item
  • result - PASS, FAIL, N/A
  • measured_value - Actual measured/observed value
  • expected_value - Expected value from checklist spec
  • remarks - Inspector comments
  • inspected_by - User who performed the check
  • inspected_at - Timestamp of inspection

logistics.inspection_evidence

Photographic/document evidence from inspections

  • id - UUID primary key
  • grn_item_id - Reference to the inspected GRN item
  • checklist_result_id - Optional link to specific checklist result
  • file_path - Storage path of the evidence file
  • file_name - Original file name
  • file_type - MIME type (image/jpeg, application/pdf, etc.)
  • description - Caption or description of the evidence
  • uploaded_by - User who uploaded the evidence
  • uploaded_at - Timestamp of upload

logistics.grn_rejections

Details of rejected goods during GRN

  • id - UUID primary key
  • grn_item_id - Reference to rejected GRN line item
  • rejected_qty - Quantity rejected
  • rejection_reason - Reason code (DAMAGED, WRONG_ITEM, QUALITY_FAIL, EXPIRED, etc.)
  • rejection_notes - Detailed rejection description
  • disposition - RETURN_TO_VENDOR, SCRAP, REWORK, HOLD
  • return_reference - Debit note or return shipment reference
  • rejected_by - User who recorded the rejection
  • rejected_at - Timestamp of rejection

logistics.grn_attachments

File attachments on GRN records

  • id - UUID primary key
  • grn_id - Parent GRN reference
  • file_name - Original file name
  • file_path - Storage path
  • file_type - MIME type
  • file_size - Size in bytes
  • attachment_type - DELIVERY_NOTE, INVOICE, PHOTO, WEIGHT_SLIP, OTHER
  • description - Attachment description
  • uploaded_by - User who uploaded
  • uploaded_at - Timestamp of upload

logistics.transporters

Transporter/carrier master data

  • id - UUID primary key
  • transporter_code - Unique transporter identifier
  • transporter_name - Company name of the carrier
  • gstin - GST identification number
  • contact_person - Primary contact name
  • phone - Contact phone number
  • email - Contact email
  • address - Registered address
  • transporter_type - OWN_FLEET, THIRD_PARTY, COURIER
  • is_active - Active/inactive flag
  • created_at, updated_at - Audit timestamps

GRN Processing Steps

1

Create GRN from PO

GRN 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;
2

Record Receipt Details

Enter physical receipt information including delivery note, vehicle details, and quantities received per line item. Capture batch/serial numbers for tracked items.

Quantity Variance

Received quantity can differ from ordered quantity. System allows over-receipt (with approval) and short-receipt scenarios. Variance is tracked for vendor performance.

3

Quality Inspection

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;
4

Update Stock Levels

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
);
5

Update PO Status

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;

Batch & Serial Number Tracking

Batch Tracking

For items with track_batch = true:

  • Batch number required at GRN
  • Manufacturing date capture
  • Supports FIFO/FEFO issue logic
  • Batch-level quantity tracking

Serial Tracking

For items with track_serial = true:

  • Unique serial per unit
  • Stored as TEXT[] array
  • Full traceability to receipt
  • Warranty tracking support

Expiry Date Tracking

For items with track_expiry = true, expiry date is mandatory at GRN. System can alert for approaching expiry and auto-quarantine expired batches.

Query Examples

GRN Summary with Variance Analysis

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;

Batches Expiring Soon

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;

Pending Inspections

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;

Best Practices

Receipt Verification

Always verify received quantities against delivery note before creating GRN. Document any discrepancies with photos if possible.

Timely Inspection

Complete quality inspection within 24-48 hours of receipt. Prolonged quarantine affects inventory availability and vendor payment processing.

Rejection Handling

For rejected items, create return documentation immediately. Track rejection reasons for vendor performance analysis and future sourcing decisions.