Comprehensive documentation of the Rate Contract management system - storing pre-negotiated vendor pricing from awarded RFQs, enabling quick PO creation at agreed rates without repeated quotations.
A Rate Contract is a pre-negotiated pricing agreement established after an RFQ award. Instead of running a full RFQ cycle for each purchase, the buyer can directly create POs referencing the rate contract, ensuring consistent pricing, faster procurement, and reduced administrative overhead.
After an RFQ is awarded (status = AWARDED), the procurement manager identifies line items suitable for rate contract establishment. Typically MRO items, standard components, or recurring services.
-- Identify awarded RFQ items for rate contract creation SELECT q.id AS quotation_id, qi.id AS quotation_item_id, q.vendor_id, qi.item_id, qi.item_description, qi.unit_price, qi.uom_id, qi.tax_code_id, r.id AS rfq_id, r.company_id FROM rfq.quotations q JOIN rfq.quotation_items qi ON qi.quotation_id = q.id JOIN rfq.rfqs r ON r.id = q.rfq_id WHERE q.is_selected = true AND r.status = 'AWARDED' AND r.id = @rfqId;
For each selected quotation line item, create a rate contract record. The system auto-generates the rate_contract_number and sets initial status to DRAFT or PENDING_APPROVAL based on configuration.
-- Insert rate contract from awarded quotation item INSERT INTO rfq.rate_contracts ( rate_contract_number, vendor_id, item_id, item_description, rfq_id, quotation_id, company_id, supply_rate, installation_rate, total_rate, uom_id, tax_code_id, tax_percent, currency_code, valid_from, valid_until, min_order_quantity, max_order_quantity, delivery_days, status, approval_status, created_by, metadata ) VALUES ( generate_rate_contract_number(@companyId), -- e.g., RC-2026-001 @vendorId, @itemId, @itemDescription, @rfqId, @quotationId, @companyId, @supplyRate, @installationRate, @supplyRate + @installationRate, @uomId, @taxCodeId, @taxPercent, @currencyCode, CURRENT_DATE, CURRENT_DATE + INTERVAL '1 year', -- Default 1 year validity @minQty, @maxQty, @deliveryDays, 'PENDING_APPROVAL', 'PENDING', @userId, '{}'::jsonb );
Attach specific terms copied from the awarded quotation or contract master. Payment terms, delivery terms, warranty clauses, penalty clauses, etc.
-- Insert rate contract terms INSERT INTO rfq.rate_contract_terms ( rate_contract_id, term_type, term_category, term_text, is_mandatory, display_order ) VALUES (@rateContractId, 'PAYMENT', 'PAYMENT_TERMS', 'Net 30 days from invoice date', true, 1), (@rateContractId, 'DELIVERY', 'LEAD_TIME', '15 working days from PO date', true, 2), (@rateContractId, 'WARRANTY', 'WARRANTY_PERIOD', '12 months warranty from delivery', false, 3), (@rateContractId, 'PENALTY', 'LATE_DELIVERY', '0.5% penalty per week for delayed delivery', true, 4);
If approval is required (based on rate contract value or company policy), route through the approval matrix. Approver verifies pricing, validity period, T&C, and vendor reliability.
Uses the standard approval.approval_matrix with document_type='RATE_CONTRACT'. Multi-level approvals may be required for high-value contracts or long validity periods.
-- Approve rate contract UPDATE rfq.rate_contracts SET approval_status = 'APPROVED', status = 'ACTIVE', approved_by = @approverId, approved_at = NOW(), updated_at = NOW() WHERE id = @rateContractId;
When creating a PO against a rate contract, set po_type = 'CONTRACT_RELEASE' and reference the rate_contract_id. The system auto-populates pricing, vendor, terms, and tax details.
Buyer searches for active rate contracts by item, vendor, or rate contract number. The system shows only contracts within validity period and not yet exhausted.
-- Search active rate contracts for a given item SELECT rc.id, rc.rate_contract_number, v.vendor_name, i.item_code, i.item_name, rc.total_rate, u.uom_code, rc.valid_from, rc.valid_until, rc.delivery_days, rc.total_quantity_allowed, rc.total_quantity_consumed, (rc.total_quantity_allowed - rc.total_quantity_consumed) AS available_quantity FROM rfq.rate_contracts rc JOIN vendor.vendors v ON v.id = rc.vendor_id JOIN inventory.items i ON i.id = rc.item_id JOIN master.units_of_measure u ON u.id = rc.uom_id WHERE rc.company_id = @companyId AND rc.status = 'ACTIVE' AND rc.valid_from <= CURRENT_DATE AND rc.valid_until >= CURRENT_DATE AND (rc.total_quantity_allowed IS NULL OR rc.total_quantity_consumed < rc.total_quantity_allowed) AND i.id = @itemId ORDER BY rc.total_rate ASC;
Create the PO header with po_type='CONTRACT_RELEASE' and link to the rate contract. Pricing and terms auto-populate from the contract.
-- Create PO against rate contract INSERT INTO procurement.purchase_orders ( po_number, company_id, vendor_id, po_type, rate_contract_id, buyer_id, po_date, status, currency_code, payment_terms_id, delivery_terms_id, created_by ) VALUES ( generate_po_number(@companyId), @companyId, @vendorId, -- From rate contract 'CONTRACT_RELEASE', @rateContractId, @buyerId, CURRENT_DATE, 'DRAFT', @currencyCode, -- From rate contract @paymentTermsId, -- From rate contract terms @deliveryTermsId, -- From rate contract terms @userId );
PO line items inherit unit price, tax code, and UOM from the rate contract. Quantity can vary per release within contract limits.
-- Add PO line item referencing rate contract INSERT INTO procurement.purchase_order_items ( po_id, line_number, item_id, item_description, quantity, uom_id, unit_price, tax_code_id, tax_amount, line_total, rate_contract_id, delivery_date ) VALUES ( @poId, 1, @itemId, -- From rate contract @itemDescription, @quantity, -- User input (within min/max limits) @uomId, -- From rate contract @totalRate, -- From rate contract (supply + installation) @taxCodeId, -- From rate contract @quantity * @totalRate * (@taxPercent / 100), @quantity * @totalRate * (1 + @taxPercent / 100), @rateContractId, CURRENT_DATE + (@deliveryDays || ' days')::interval -- From rate contract );
After PO approval, update the rate contract's consumed quantity and value. This prevents over-utilization and triggers expiry when limits are reached.
-- Update rate contract utilization after PO approval UPDATE rfq.rate_contracts SET total_quantity_consumed = total_quantity_consumed + @poQuantity, total_value_consumed = total_value_consumed + @poLineTotal, status = CASE WHEN total_quantity_allowed IS NOT NULL AND (total_quantity_consumed + @poQuantity) >= total_quantity_allowed THEN 'EXHAUSTED' ELSE status END, updated_at = NOW() WHERE id = @rateContractId;
| Status | Description | Trigger |
|---|---|---|
| DRAFT | Initial state, not yet submitted for approval | Created by buyer |
| PENDING_APPROVAL | Submitted for approval workflow | Buyer submits |
| APPROVED | Approved but not yet active (before valid_from date) | All approvers approve |
| ACTIVE | Currently valid and usable for PO creation | valid_from ≤ today ≤ valid_until, within limits |
| EXPIRED | Validity period ended | System job checks valid_until |
| EXHAUSTED | Quantity or value limit reached | total_quantity_consumed ≥ total_quantity_allowed |
| CANCELLED | Manually cancelled by buyer/admin | User action |
| REJECTED | Rejected during approval workflow | Approver rejects |
A scheduled system job (daily at midnight) checks all ACTIVE rate contracts and updates their status to EXPIRED if valid_until < CURRENT_DATE. Similarly, contracts are marked EXHAUSTED when utilization limits are reached during PO approval.
Track how much of each rate contract has been consumed, identify underutilized contracts, and forecast expiry dates.
-- Rate contract utilization summary SELECT rc.rate_contract_number, v.vendor_name, i.item_name, rc.total_quantity_allowed, rc.total_quantity_consumed, ROUND((rc.total_quantity_consumed::numeric / NULLIF(rc.total_quantity_allowed, 0)) * 100, 2) AS utilization_percent, rc.total_value_allowed, rc.total_value_consumed, rc.valid_from, rc.valid_until, (rc.valid_until - CURRENT_DATE) AS days_remaining, rc.status FROM rfq.rate_contracts rc JOIN vendor.vendors v ON v.id = rc.vendor_id JOIN inventory.items i ON i.id = rc.item_id WHERE rc.company_id = @companyId AND rc.status IN ('ACTIVE', 'APPROVED') ORDER BY utilization_percent DESC;
Compare rate contract pricing vs. current market rates to measure cost savings achieved through locked-in pricing.
-- Cost savings via rate contracts (vs. spot pricing) SELECT rc.rate_contract_number, i.item_name, rc.total_rate AS contract_rate, i.standard_cost AS current_market_rate, (i.standard_cost - rc.total_rate) AS savings_per_unit, rc.total_quantity_consumed, ((i.standard_cost - rc.total_rate) * rc.total_quantity_consumed) AS total_savings FROM rfq.rate_contracts rc JOIN inventory.items i ON i.id = rc.item_id WHERE rc.company_id = @companyId AND rc.status = 'ACTIVE' AND i.standard_cost > rc.total_rate ORDER BY total_savings DESC;
Evaluate vendor reliability in fulfilling rate contract commitments (on-time delivery, quality, compliance).
-- Vendor performance against rate contracts SELECT v.vendor_name, COUNT(DISTINCT rc.id) AS total_contracts, COUNT(DISTINCT po.id) AS total_pos_released, AVG(vs.on_time_delivery_pct) AS avg_otd, AVG(vs.quality_score) AS avg_quality, SUM(rc.total_value_consumed) AS total_business_value FROM rfq.rate_contracts rc JOIN vendor.vendors v ON v.id = rc.vendor_id LEFT JOIN procurement.purchase_orders po ON po.rate_contract_id = rc.id LEFT JOIN vendor.vendor_scores vs ON vs.vendor_id = v.id AND vs.is_latest = true WHERE rc.company_id = @companyId GROUP BY v.id, v.vendor_name ORDER BY total_business_value DESC;
| Rule ID | Description | Enforcement Point |
|---|---|---|
BR-RC-001 |
Rate contract can only be created from an AWARDED RFQ quotation | Rate contract creation |
BR-RC-002 |
valid_from must be ≤ valid_until | Rate contract creation/update |
BR-RC-003 |
total_quantity_consumed cannot exceed total_quantity_allowed | PO approval (prevents over-commitment) |
BR-RC-004 |
PO quantity must be within min_order_quantity and max_order_quantity | PO line item creation |
BR-RC-005 |
Only ACTIVE rate contracts can be referenced in new POs | PO creation |
BR-RC-006 |
Rate contract cannot be deleted if referenced by any PO | Rate contract deletion (prevents orphaned POs) |
BR-RC-007 |
Approval required if total_value_allowed exceeds company threshold | Rate contract creation |
BR-RC-008 |
Vendor must be APPROVED status to be included in rate contract | Rate contract creation |
BR-RC-009 |
Cannot modify pricing or vendor after approval (requires cancellation + new contract) | Rate contract update |
BR-RC-010 |
System auto-expires contracts daily where valid_until < CURRENT_DATE | Scheduled job (daily) |
public class RateContractService { // Create rate contract from awarded RFQ quotation public async Task<RateContractDto> CreateFromQuotationAsync( Guid quotationId, Guid quotationItemId, RateContractCreateDto dto, Guid userId) { // 1. Validate quotation is from awarded RFQ var quotation = await _quotationRepo.GetByIdAsync(quotationId); if (!quotation.IsSelected || quotation.Rfq.Status != "AWARDED") throw new BusinessException("BR-RC-001"); // 2. Create rate contract var rateContract = new RateContract { RateContractNumber = await GenerateRateContractNumberAsync(dto.CompanyId), VendorId = quotation.VendorId, ItemId = quotationItem.ItemId, ItemDescription = quotationItem.ItemDescription, RfqId = quotation.RfqId, QuotationId = quotationId, CompanyId = dto.CompanyId, SupplyRate = quotationItem.UnitPrice, InstallationRate = dto.InstallationRate ?? 0, TotalRate = quotationItem.UnitPrice + (dto.InstallationRate ?? 0), UomId = quotationItem.UomId, TaxCodeId = quotationItem.TaxCodeId, TaxPercent = quotationItem.TaxAmount / quotationItem.UnitPrice * 100, CurrencyCode = quotation.CurrencyCode, ValidFrom = dto.ValidFrom ?? DateTime.Today, ValidUntil = dto.ValidUntil ?? DateTime.Today.AddYears(1), Status = "PENDING_APPROVAL", ApprovalStatus = "PENDING", CreatedBy = userId }; await _rateContractRepo.AddAsync(rateContract); // 3. Submit for approval if required await _approvalService.SubmitForApprovalAsync( "RATE_CONTRACT", rateContract.Id, rateContract.TotalValueAllowed ?? 0, userId); return MapToDto(rateContract); } // Search active rate contracts for PO creation public async Task<List<RateContractDto>> SearchActiveContractsAsync( Guid companyId, Guid? itemId = null, Guid? vendorId = null) { return await _rateContractRepo.GetActiveContractsAsync( companyId, itemId, vendorId); } // Update utilization after PO approval public async Task UpdateUtilizationAsync( Guid rateContractId, decimal quantity, decimal value) { var contract = await _rateContractRepo.GetByIdAsync(rateContractId); contract.TotalQuantityConsumed += quantity; contract.TotalValueConsumed += value; // Check if exhausted if (contract.TotalQuantityAllowed.HasValue && contract.TotalQuantityConsumed >= contract.TotalQuantityAllowed) { contract.Status = "EXHAUSTED"; } await _rateContractRepo.UpdateAsync(contract); } // Daily job: Expire contracts past validity public async Task ExpireContractsAsync() { await _rateContractRepo.BulkUpdateStatusAsync( status: "EXPIRED", whereClause: rc => rc.Status == "ACTIVE" && rc.ValidUntil < DateTime.Today); } }