Skip to content

User Flow — Procurement Cycle

The end-to-end purchasing chain: Purchase Request → Request for Quotation → Purchase Order → Goods Received Note → Payment Order. Each document is its own approval workflow with the same shape — a Details screen, contextual action buttons, and a single ContextActions endpoint that advances an integer StatusId — and each document is created from the approved one before it.

Source: NKO.Main/NKO.Main/Controllers/{PurchaseRequest,RFQ,PurchaseOrder,GRN,PaymentOrder}Controller.cs, BAL NKO.Main/NKO.Main/BAL/{BPurchaseRequest,BRFQ,BPurchaseOrder,BGRN,BPaymentOrder}.cs, NKO.Lib/NKO.Shared/Helpers/CommonConstants.cs (Areas, *UserTypes, *OpTypes), NKO.Lib/NKO.Shared/Helpers/Enums.cs (PRStatus, POStatus, GRNStatus, PaymentOrderStatus, PRTrackerStatus).

Actors

Role Appears as In
Requester *UserTypes.Requester every document — raises and sends it
Technical / opening committee PRUserTypes.CommitteeMember, RFQUserTypes.Committee1..3 RFQ evaluation — the PR technical-control methods are commented out, see Purchase Request
Budget Holder *UserTypes.BudgetHolder RFQ, PO, Payment Order — not the PR, see Purchase Request
Finance *UserTypes.Finance PR, PO, Payment Order
CEO / GM POUserTypes.CEO, PaymentOrderUserTypes.CEO PO, Payment Order
Supply Chain (officer / manager) RFQUserTypes.SupplyChainOfficer, SupplyChain, PRUserTypes.Procurement assigns the procurement officer on the PR, runs the RFQ
Recipient GRNUserTypes.Recipient GRN — confirms goods received
Payer PaymentOrderUserTypes.Payer Payment Order — records the payment

Segregation-of-duty roles PRUserTypes.SOD1..4 / FinanceSod1 carry the PR approval chain after Finance; how many of them a PR passes through depends on its USD value.

Preconditions

  • The signer has an approved e-signature (AppUserSign). Every procurement ContextActions call starts with _sign.HasSign(userId) and refuses with "Please upload your E-signature." otherwise — except PurchaseOrder/JustificationContextActions, which does not check it (R3-9).
  • The PR is charged to a project and budget lines. Their remaining budget (ProcBudgetLineHandler) is shown on the PR, but it is not enforced — a PR can be raised and approved beyond it.
  • Each downstream document requires the upstream one in the right state — e.g. RFQ needs PR.StatusId == PRStatus.Accepted && PR.ProcMId != null.

Flow

flowchart TD
    subgraph PR["Purchase Request"]
        PR1["Create: materials, project, budget lines"] --> PR2["Requester sends"]
        PR2 --> PR3["Finance"] --> PR4{"USD value over 1,000?"}
        PR4 -->|yes| PR5["SOD 1 to SOD 4, by value"] --> PR7["Supply Chain assigns procurement officer"]
        PR4 -->|no| PR7
        PR7 --> PR8{"PRStatus Accepted"}
    end
    PR8 --> RFQ1
    subgraph RFQ["Request for Quotation"]
        RFQ1["Create from accepted PR"] --> RFQ2["Add providers and quotations"]
        RFQ2 --> RFQ3["Providers analyse and technical assessment"]
        RFQ3 --> RFQ4["Compare CBA and provider justification"]
        RFQ4 --> RFQ5["Committee to Supply Chain Officer to Supply Chain to Budget Holder"]
        RFQ5 --> RFQ6{"Provider awarded"}
    end
    RFQ6 --> PO1
    subgraph PO["Purchase Order"]
        PO1["Create from RFQ award"] --> PO2["Requester sends"] --> PO3["Finance"] --> PO4["Budget Holder"] --> PO5["CEO or GM"] --> PO6{"POStatus Accepted"}
    end
    PO6 --> GRN1
    subgraph GRN["Goods Received Note"]
        GRN1["Create from PO"] --> GRN2["Requester sends"] --> GRN3["Recipient accepts or refuses"] --> GRN4{"GRNStatus Accepted"}
    end
    GRN4 --> PMT1
    subgraph PMT["Payment Order"]
        PMT1["Create from an accepted GRN, or manual"] --> PMT2["Requester sends"] --> PMT3["Budget Holder"] --> PMT4["Finance"] --> PMT5["CEO"] --> PMT6["Payer records payment"] --> PMT7{"PaymentOrderStatus Paid"}
    end

PurchaseOrder/Tracker shows where a request sits across the whole cycle using PRTrackerStatus (PRSigning → WaitingForRFQ → RFQCollection → TechnicalCommitteeStage → CBAStage → WaitingForPO → POStage → WaitingForGRN → GRNStage → Completed).

The Purchase Request list, with a status tile per PRStatus value (/PurchaseRequest):

PR list with status tiles

The lifecycle tracker — one request across PR → RFQ → PO (/PurchaseOrder/Tracker/{id}):

Procurement lifecycle tracker

A completed document's signature timeline — the same shape on every document. Here a Purchase Order, Requester → Finance → Budget Holder → CEO (signer names masked):

Purchase Order signature timeline — part 1 part 2 part 3 part 4

A Payment Order and its Requester → Budget Holder → Finance → CEO → Payer steps (/PaymentOrder/Details/{guid}):

Payment Order signature timeline — part 1 part 2 part 3

How each document advances

Every stage uses the same POST endpoint on its controller:

POST /<Doc>/ContextActions?id=<guid>&operationType=<*OpTypes>&userType=<*UserTypes>&idText=<signature text>

The controller matches (userType, operationType) and calls the matching BAL method (SetSent, SetFinanceAccepted, SetBudgetHolderAccepted, SetCEOAccepted, SetRecipientAccepted, SetPayerAccepted, …). The BAL updates StatusId, records the signature/date, writes a Notification, and points the document at the next role. *Refuse sends it back (usually to Draft / the requester); Cancel → Cancelled; Reset (requester) → Draft.

Purchase Request

From status Actor / op To status
Draft (1) Requester · Send WaitingForFinance (4) — next user is Project.FinanceManager
WaitingForFinance Finance · FinanceAccept WaitingForSOD1 (10) when the USD value is > 1 000, otherwise WaitingForSC (6)
WaitingForSOD1 SOD 1 · SOD1Accept WaitingForSOD2 (11) when > 5 000, otherwise WaitingForSC
WaitingForSOD2 SOD 2 · SOD2Accept WaitingForSOD3 (12) when > 10 000, otherwise WaitingForSC
WaitingForSOD3 SOD 3 · SOD3Accept WaitingForSOD4 (13) when > 100 000, otherwise WaitingForSC
WaitingForSOD4 SOD 4 · SOD4Accept WaitingForSC (6)
WaitingForSC Procurement · ProcurementSetAssigned Accepted (7) + ProcMId set
any waiting any approver · *Refuse Refused (8)
any Requester · Cancel Cancelled (9)

The SOD tiers are the PR approval chain, not an addition to it: after Finance a PR either enters WaitingForSOD1 or goes straight to Supply Chain.

Three PR statuses are unreachable

WaitingForTechnicalControl (2), WaitingForBudgetHolder (3) and WaitingForCEO (5) are defined in PRStatus, but no code path assigns any of them to a Purchase Request. The technical-control methods (SetCommitteeAccepted / SetCommitteeRefused) are commented out in BPurchaseRequest. SetBudgetHolderAccepted / SetBudgetHolderRefused exist, the ContextActions branch calls them and NextUserHelper routes the status to Project.ProgramManager — but because the status is never set, the guard can never pass and the action returns the generic "You Can't Sign the request". So the Budget Holder does not approve a Purchase Request at all: the control is live on the Purchase Order, Payment Order and Job/SRF flows, but not here (R6-11).

Request for Quotation

Created only from a PR in Accepted with a procurement officer assigned. Sub-steps: add providers & quotations (Providers), record technical/financial scores (ProvidersAnalyse, TechnicalAssesment), run the comparative bid analysis (Compare — "CBA"), and justify the chosen provider (RFQProviderJustification, ProviderJustificationStatus). The evaluation chain (RFQOpTypes / RFQUserTypes): Committee 1 → Committee 2 → Committee 3 → Supply Chain Officer → Supply Chain → Budget Holder(s) 1–5, with Cancel / Reset / ComReset / ComCancel. Provider outcome is ProviderStatus (Draft → Passed → FullWon / PartiallyWon or Rejected).

Purchase Order

From status Actor / op To status
Draft (0) Requester · Send WaitingForFinance (1)
WaitingForFinance Finance · FinanceAccept WaitingForBudgetHolder (2)
WaitingForBudgetHolder Budget Holder · BudgetHolderAccept WaitingForCEO (3) (labelled "Waiting For GM")
WaitingForCEO CEO · CEOAccept Accepted (4)
any waiting approver · *Refuse Refused (5)
any Requester · Cancel / Reset Cancelled (6) / Draft

A Justification sub-document (PurchaseOrder/Justification*) carries its own approval via JustificationContextActions.

Goods Received Note

From status Actor / op To status
Draft (0) Requester · Send WaitingForRecipient (1)
WaitingForRecipient Recipient · RecipienteAccept Accepted (2)
WaitingForRecipient Recipient · RecipienteRefuse Rejected (3)
any Requester · Reset Draft

Accepted goods can be pushed to the warehouse via GRN/TransfareToWarehouse.

Payment Order

From status Actor / op To status
Draft (0) Requester · Send WaitingForBudgetHolder (1)
WaitingForBudgetHolder Budget Holder · BudgetHolderAccpet WaitingForFinance (2)
WaitingForFinance Finance · FinanceAccpet WaitingForCEO (3)
WaitingForCEO CEO · CEOAccpet Accepted (4)
Accepted Payer · PayAccpet Paid (7)
any waiting approver · *Reject Rejected (5)
any Requester · Cancel Cancelled (6)

PaymentOrder/CreateManual raises a standalone payment order not tied to a PO.

Edge cases

Situation Behaviour
Signer has no e-signature ContextActions refuses immediately; the document does not move. (Not on PO justification — R3-9.)
Budget Holder is also the requester PR Send returns -2 — "The Budget Holder can't request a PR."
Any approver refuses The document goes to Refused. The requester can Reset it to Draft, edit and re-send.
RFQ started before a procurement officer is assigned RFQ/Create redirects back to the RFQ index — no RFQ is created.
PR cancelled after RFQ started RFQ / PO already created continue on their own status; the tracker shows InCompleted.
Comment added at any stage AddComment writes a Comment + notification without changing status.