Back to ER Diagram
Purchase Order

Purchase Order Lifecycle & Revisions

Complete documentation of PO processing from creation through vendor acknowledgment, goods receipt, invoicing, and closure. Includes revision management, schedule lines, and completion tracking.

PostgreSQL
18 Tables
Schema: procurement
Revision Tracking

PO Lifecycle Overview

A Purchase Order (PO) is the legal document sent to vendors to procure goods or services. It flows through internal approval, vendor communication, receipt tracking, and invoice matching before closure.

DRAFT
PENDING_APPROVAL
APPROVED
SENT
ACKNOWLEDGED
PARTIAL
RECEIVED
CLOSED
REJECTED
CANCELLED
4
PO Types
9
Status States
N
Revisions
3-Way
Match Support

PO Status States

Status Description Allowed Actions Next States
DRAFT PO being prepared. Not yet submitted. Edit, Delete, Submit PENDING_APPROVAL
PENDING_APPROVAL Submitted for approval workflow. Approve, Reject, Recall APPROVED, REJECTED, DRAFT
APPROVED Approved. Ready to send to vendor. Send to Vendor, Revise, Cancel SENT, DRAFT (revise), CANCELLED
SENT Transmitted to vendor via email/portal. Record Acknowledgment ACKNOWLEDGED
ACKNOWLEDGED Vendor confirmed receipt and acceptance. Create GRN, Revise PARTIAL, RECEIVED
PARTIAL Some items received, more pending. Create GRN, Close Manually RECEIVED, CLOSED
RECEIVED All ordered quantities received. Process Invoice CLOSED
CLOSED Fully received and invoiced. Terminal. View Only -
CANCELLED Cancelled. No further action. View Only -

PO Types

STANDARD

Created via RFQ award. PR → RFQ → Bid → PO. Most common type. Full approval workflow applies based on amount.

DIRECT

PR → PO without RFQ. Used for single-source items, low-value purchases, or when vendor is pre-determined. po_items.pr_item_id links directly to PR.

SPOT

Direct PO without PR or RFQ. Used for urgent one-off purchases. No pr_item_id or rfq_item_id. Requires justification.

BLANKET

Pre-approved PO for recurring needs. Releases can be made against it without re-approval up to the blanket limit.

CONTRACT_RELEASE

PO issued against an existing rate contract. Pricing and terms pulled from contract. Tracks utilization.

EMERGENCY

Urgent purchase. May have expedited or post-facto approval. Requires justification and audit trail.

PO Creation Paths

Path po_type po_rfqs po_items.pr_item_id po_items.rfq_item_id
PR → RFQ → PO STANDARD Populated Via rfq_item → pr_item Populated
PR → PO (skip RFQ) DIRECT Empty Populated NULL
Direct PO (no PR, no RFQ) SPOT Empty NULL NULL
Rate Contract → PO CONTRACT_RELEASE Empty Optional NULL

Database Schema

po.purchase_orders

  • id - UUID primary key
  • po_number - Unique PO number (auto-generated)
  • company_id, vendor_id - Key relationships
  • quotation_id, contract_id - Source documents (RFQs linked via po_rfqs junction table)
  • po_date, required_date, expected_delivery_date
  • po_type - STANDARD (via RFQ), DIRECT (PR→PO, no RFQ), SPOT (no PR/RFQ), BLANKET, CONTRACT_RELEASE, EMERGENCY
  • buyer_id - Purchasing agent
  • vendor_contact_* - Vendor snapshot at PO time
  • ship_to_*, bill_to_* - Addresses
  • payment_terms_id, delivery_terms_id
  • subtotal, tax_amount, total_amount
  • currency_code, exchange_rate
  • status, current_approval_level
  • revision, revised_at, revision_reason
  • total_received_qty, total_invoiced_amount
  • is_fully_received, is_fully_invoiced

po.po_items

  • po_id, line_number - Parent and sequence
  • pr_item_id, quotation_item_id - Source tracing
  • item_id, item_code, item_description
  • quantity, uom_id
  • received_qty, invoiced_qty, cancelled_qty
  • unit_price, discount_*, tax_*, line_total
  • required_date, promised_date
  • gl_account_id, cost_center_id, project_id
  • warehouse_id - Destination warehouse
  • status - OPEN, PARTIAL, RECEIVED, CANCELLED, CLOSED

po.po_rfqs Junction Table

Enables multiple RFQ awards to be consolidated into a single PO for the same vendor, reducing admin overhead and shipping costs.

  • id - Primary key
  • po_id - FK to purchase_orders
  • rfq_id - FK to rfqs (source RFQ)
  • awarded_amount - Amount awarded from this RFQ
  • added_by - User who consolidated the RFQ into PO
  • added_at - Timestamp
  • remarks - Notes on consolidation

po.po_schedule_lines

  • po_item_id, schedule_number
  • quantity - Scheduled delivery quantity
  • delivery_date - Expected delivery date
  • received_qty - Quantity received against this schedule
  • ship_to_location_id - Specific delivery location
  • status - SCHEDULED, PARTIAL, RECEIVED, CANCELLED

po.po_approvals

  • po_id, approval_level
  • approver_id, delegate_id
  • action - APPROVED, REJECTED, RETURNED
  • action_date, comments
  • status, due_date, reminded_at

po.po_terms

Standard and custom terms and conditions attached to purchase orders. Supports both reusable company-wide terms and PO-specific custom terms.

  • id - UUID primary key
  • po_id - FK to purchase_orders
  • term_type - STANDARD, CUSTOM
  • term_category - PAYMENT, DELIVERY, WARRANTY, PENALTY, LIABILITY, OTHER
  • title - Term heading
  • description - Full term text
  • sequence - Display order
  • is_mandatory - Whether term is required
  • template_id - FK to standard term template (if STANDARD type)
  • created_by, created_at

po.po_amendment_items

Tracks line-item-level changes within a PO amendment, capturing before/after values for quantity, price, dates, and specifications.

  • id - UUID primary key
  • amendment_id - FK to po_amendments
  • po_item_id - FK to po_items
  • change_type - ADD, MODIFY, CANCEL
  • field_name - Column that changed (quantity, unit_price, delivery_date, etc.)
  • old_value, new_value - Before/after snapshots
  • old_quantity, new_quantity - Quantity change detail
  • old_unit_price, new_unit_price - Price change detail
  • old_delivery_date, new_delivery_date - Date change detail
  • reason - Justification for the item-level change

po.po_amendment_approvals

Approval workflow specific to PO amendments. May follow different approval rules than original PO (e.g., value-increase triggers higher-level approval).

  • id - UUID primary key
  • amendment_id - FK to po_amendments
  • approval_level - Level in the approval hierarchy
  • approver_id - FK to users (assigned approver)
  • delegate_id - FK to users (delegated approver, if any)
  • action - APPROVED, REJECTED, RETURNED
  • action_date, comments
  • status - PENDING, COMPLETED, SKIPPED
  • due_date, reminded_at

po.po_amendment_vendor_responses

Captures vendor accept/reject decisions on PO amendments. Vendors may accept fully, reject, or propose counter-terms.

  • id - UUID primary key
  • amendment_id - FK to po_amendments
  • vendor_id - FK to vendors
  • response - ACCEPTED, REJECTED, COUNTER_PROPOSED
  • response_date - When vendor responded
  • respondent_name, respondent_email - Vendor contact who responded
  • comments - Vendor remarks or counter-proposal details
  • counter_proposal - JSON/text of proposed alternative terms
  • acknowledged_by, acknowledged_at - Internal acknowledgment of response

po.po_amendment_audit

Immutable audit trail for all amendment-related changes. Records every state transition, field change, and user action for compliance.

  • id - UUID primary key
  • amendment_id - FK to po_amendments
  • po_id - FK to purchase_orders
  • action - CREATED, SUBMITTED, APPROVED, REJECTED, VENDOR_ACCEPTED, VENDOR_REJECTED, APPLIED, CANCELLED
  • performed_by - FK to users
  • performed_at - Timestamp of action
  • old_values - JSONB snapshot of previous state
  • new_values - JSONB snapshot of new state
  • ip_address, user_agent - Request metadata
  • comments - Contextual notes

po.po_acknowledgments

Tracks vendor acknowledgment of PO receipt. Supports multiple acknowledgment methods (portal, email, manual) and captures vendor confirmation details.

  • id - UUID primary key
  • po_id - FK to purchase_orders
  • vendor_id - FK to vendors
  • acknowledged_at - Timestamp of acknowledgment
  • acknowledgment_method - PORTAL, EMAIL, MANUAL, API
  • vendor_reference - Vendor's internal reference number
  • respondent_name, respondent_email - Vendor contact
  • confirmed_delivery_date - Vendor-confirmed delivery date
  • comments - Vendor remarks or conditions
  • revision - PO revision number being acknowledged
  • recorded_by - User who recorded (for manual entries)

po.po_attachments

File attachments associated with purchase orders, such as specifications, drawings, terms documents, or vendor correspondence.

  • id - UUID primary key
  • po_id - FK to purchase_orders
  • file_name - Original file name
  • file_path - Storage path or URL
  • file_size - Size in bytes
  • mime_type - File MIME type
  • attachment_type - SPECIFICATION, DRAWING, TERMS, CORRESPONDENCE, OTHER
  • description - Brief description of the attachment
  • uploaded_by - FK to users
  • uploaded_at - Timestamp
  • is_vendor_visible - Whether attachment is shared with vendor

po.company_addresses

Company and plant addresses used for ship-to and bill-to on purchase orders. Supports multiple address types per company location.

  • id - UUID primary key
  • company_id - FK to companies
  • address_type - SHIPPING, BILLING, PLANT, WAREHOUSE, HEAD_OFFICE
  • address_name - Label/name for the address
  • address_line_1, address_line_2
  • city, state, postal_code, country
  • contact_person, contact_phone, contact_email
  • is_default - Default address for the type
  • is_active - Soft-delete flag
  • created_at, updated_at

po.po_closures

Records PO closure and short-close events. Tracks whether closure was automatic (fully received/invoiced) or manual with justification.

  • id - UUID primary key
  • po_id - FK to purchase_orders
  • closure_type - AUTO, MANUAL, SHORT_CLOSE, FORCE_CLOSE
  • closure_reason - Justification text (required for manual/short-close)
  • closed_by - FK to users
  • closed_at - Closure timestamp
  • total_ordered_value - Original PO value at closure
  • total_received_value - Value of goods received
  • total_invoiced_value - Value invoiced at closure
  • variance_amount - Difference between ordered and received/invoiced
  • approved_by, approved_at - Closure approval (for manual closures)

po.po_closure_items

Line-item-level closure details capturing outstanding quantities, short-close quantities, and per-item closure reasons.

  • id - UUID primary key
  • closure_id - FK to po_closures
  • po_item_id - FK to po_items
  • ordered_qty - Original ordered quantity
  • received_qty - Quantity received at closure
  • invoiced_qty - Quantity invoiced at closure
  • short_close_qty - Quantity being short-closed (not to be delivered)
  • cancelled_qty - Previously cancelled quantity
  • item_closure_reason - Per-item justification
  • final_status - RECEIVED, SHORT_CLOSED, CANCELLED

po.po_rfqs Junction Table

Links purchase orders to their source RFQs, enabling consolidation of multiple RFQ awards into a single PO for the same vendor.

  • id - UUID primary key
  • po_id - FK to purchase_orders
  • rfq_id - FK to rfqs (source RFQ)
  • awarded_amount - Amount awarded from this RFQ
  • added_by - User who consolidated the RFQ into PO
  • added_at - Timestamp
  • remarks - Notes on consolidation

po.po_approvals Multi-Level

Multi-level PO approval workflow supporting delegation, escalation, and configurable approval hierarchies based on PO value and type.

  • id - UUID primary key
  • po_id - FK to purchase_orders
  • approval_level - Level in the approval hierarchy
  • approver_id - FK to users (assigned approver)
  • delegate_id - FK to users (delegated approver)
  • action - APPROVED, REJECTED, RETURNED
  • action_date, comments
  • status - PENDING, COMPLETED, SKIPPED
  • due_date, reminded_at
  • escalated_to - FK to users (if escalated)
  • escalated_at - Escalation timestamp

po.po_schedule_lines Delivery Schedules

Delivery schedule lines for PO items, enabling split deliveries across multiple dates and locations for a single line item.

  • id - UUID primary key
  • po_item_id - FK to po_items
  • schedule_number - Sequence within the item
  • quantity - Scheduled delivery quantity
  • delivery_date - Expected delivery date
  • received_qty - Quantity received against this schedule
  • ship_to_location_id - FK to locations (specific delivery point)
  • status - SCHEDULED, PARTIAL, RECEIVED, CANCELLED
  • grn_reference - Link to GRN that fulfilled this schedule
  • remarks - Delivery notes

Revision Management

POs support revisions after approval to handle changes in quantities, pricing, delivery dates, or specifications. Each revision is tracked for audit purposes.

1

When to Revise

Revisions are needed for: quantity changes, price adjustments, delivery date changes, line item additions/cancellations, or specification updates after the PO has been sent to the vendor.

2

Revision Process

The revision process increments the version number and may require re-approval depending on the nature of changes (e.g., value increase).

-- Create revision
UPDATE po.purchase_orders
SET
    revision = revision + 1,
    revised_at = CURRENT_TIMESTAMP,
    revised_by = :user_id,
    revision_reason = :reason,
    status = CASE
        WHEN :value_increased THEN 'PENDING_APPROVAL'
        ELSE 'APPROVED'
    END
WHERE id = :po_id;
3

Revision Communication

Revised PO is re-sent to vendor with revision number clearly indicated. Vendor acknowledgment is tracked again for the new revision.

Revision Rules

Value increases typically require re-approval. Quantity reductions or delivery date changes may not. Configure thresholds in the approval matrix.

Schedule Lines (Delivery Schedules)

For items with multiple delivery dates or locations, schedule lines allow splitting a single PO line into multiple deliveries:

-- Example: 1000 units split across 4 deliveries
-- PO Item: 1000 units of Item XYZ

INSERT INTO po.po_schedule_lines (po_item_id, schedule_number, quantity, delivery_date)
VALUES
    (:po_item_id, 1, 250, '2024-02-01'),
    (:po_item_id, 2, 250, '2024-02-15'),
    (:po_item_id, 3, 250, '2024-03-01'),
    (:po_item_id, 4, 250, '2024-03-15');

Schedule Line Benefits

Track partial deliveries against specific schedules. Monitor on-time delivery performance. Support just-in-time inventory management.

Completion Tracking

PO completion is tracked at both header and line item level:

Receipt Tracking

  • po_items.received_qty - Updated from GRNs
  • purchase_orders.total_received_qty - Sum
  • is_fully_received - All items received
-- Check if fully received
UPDATE po.purchase_orders
SET is_fully_received = (
    SELECT BOOL_AND(
        received_qty >= quantity - cancelled_qty
    )
    FROM po.po_items
    WHERE po_id = :po_id
)
WHERE id = :po_id;

Invoice Tracking

  • po_items.invoiced_qty - From invoices
  • purchase_orders.total_invoiced_amount
  • is_fully_invoiced - All items invoiced
-- Check if fully invoiced
UPDATE po.purchase_orders
SET is_fully_invoiced = (
    total_invoiced_amount >= total_amount
)
WHERE id = :po_id;

PO Closure Logic

A PO automatically closes when: (1) is_fully_received = true AND is_fully_invoiced = true, OR (2) manually closed by authorized user with justification. Closure prevents further GRNs or invoices against the PO.

PO Item Status Flow

OPEN
PARTIAL
RECEIVED
CLOSED
CANCELLED
Item Status Condition Triggered By
OPEN received_qty = 0 Initial state
PARTIAL 0 < received_qty < quantity GRN creation
RECEIVED received_qty >= quantity - cancelled_qty GRN creation
CLOSED Invoiced and payment processed Invoice completion
CANCELLED Line cancelled by user Manual action

Query Examples

Outstanding PO Items Awaiting Delivery

SELECT
    po.po_number,
    poi.line_number,
    poi.item_description,
    poi.quantity - poi.received_qty - poi.cancelled_qty AS outstanding_qty,
    poi.promised_date,
    v.vendor_name,
    CASE
        WHEN poi.promised_date < CURRENT_DATE THEN 'OVERDUE'
        ELSE 'ON_TIME'
    END AS delivery_status
FROM po.po_items poi
JOIN po.purchase_orders po ON poi.po_id = po.id
JOIN vendor.vendors v ON po.vendor_id = v.id
WHERE po.status IN ('SENT', 'ACKNOWLEDGED', 'PARTIAL')
  AND poi.status IN ('OPEN', 'PARTIAL')
ORDER BY poi.promised_date;

PO Completion Summary by Vendor

SELECT
    v.vendor_name,
    COUNT(po.id) AS total_pos,
    SUM(CASE WHEN po.is_fully_received THEN 1 ELSE 0 END) AS fully_received,
    SUM(CASE WHEN po.is_fully_invoiced THEN 1 ELSE 0 END) AS fully_invoiced,
    SUM(po.total_amount) AS total_value,
    SUM(po.total_invoiced_amount) AS invoiced_value
FROM po.purchase_orders po
JOIN vendor.vendors v ON po.vendor_id = v.id
WHERE po.po_date >= :start_date
  AND po.status NOT IN ('DRAFT', 'CANCELLED')
GROUP BY v.id, v.vendor_name
ORDER BY total_value DESC;

Schedule Line Delivery Status

SELECT
    po.po_number,
    poi.line_number,
    psl.schedule_number,
    psl.quantity AS scheduled_qty,
    psl.received_qty,
    psl.delivery_date,
    psl.status,
    l.location_name AS ship_to
FROM po.po_schedule_lines psl
JOIN po.po_items poi ON psl.po_item_id = poi.id
JOIN po.purchase_orders po ON poi.po_id = po.id
LEFT JOIN organization.locations l ON psl.ship_to_location_id = l.id
WHERE psl.status = 'SCHEDULED'
  AND psl.delivery_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '7 days'
ORDER BY psl.delivery_date;

Best Practices

Vendor Communication

Always obtain vendor acknowledgment before expecting delivery. Track acknowledged_at timestamp and vendor confirmation reference.

Three-Way Match Readiness

Ensure PO items have proper item_id links for automated matching. Capture unit prices accurately for variance detection.

Revision Discipline

Document all revision reasons. Avoid frequent revisions by confirming requirements upfront. Use amendments for significant changes.