← All white papersBuild plan · September 2026

Checking Subcontractor Invoices on Microsoft 365

Four checks in a fixed order, stopping at the first failure. The order is the claim, and a parallel implementation quietly discards it.

10 min read·Free to implement·Published by AI Pathway

Used by

  • Business Systems Analyst
  • Contract Administrator
  • Commercial Manager

The thing that makes this workflow work is not the extraction. It is that the checks run in an order, stop when one fails, and leave a record that outlives the decision somebody made about it. Power Automate makes the first of those slightly awkward and the second entirely up to you, which is why both are called out here.

In short

  • What this is

    A build plan for the invoice check standard on Microsoft 365. Three lists, a library, three flows, six measures.

  • The core rule

    The checks are ordered because each one assumes the previous passed. Running them in parallel produces differences that are not real.

  • What it does not do

    It never approves or rejects a payment. It produces a difference in dollars and the evidence behind it, for a person who is accountable for the decision.

  • The hard part

    The cumulative position. Every individual variance is defensible; the run of them is the thing nobody is looking at.

Take the spec to Claude, ChatGPT or Copilot. The sentence to paste is one click away.

Want the whole thing by email?

This paper, plus the rest of the library. One click to stop them.

1. Before you start

Read the invoice check build standard first. It defines its objects, and the lists below are those objects with SharePoint types on them. Shared conventions are in building the standards on Microsoft 365.

Decide one thing first: what the invoice is being checked against. A purchase order, a subcontract, a schedule of rates. If that reference does not exist in a queryable form, no amount of extraction helps, and standing it up is the real first task.

2. The lists, with their columns

For your developer. Show the fields
Site: InvoiceChecks

LIBRARY  Originals
  InvoiceRef      Text            indexed. The file, kept as received.
  ReceivedAt      DateTime

LIST  Invoices
  InvoiceRef      Text            indexed
  SupplierRef     Text            indexed
  Amount          Currency
  OrderRef        Text            PO, subcontract or rates schedule
  OrderAmount     Currency
  SourceDocument  Hyperlink       into Originals
  ReceivedAt      DateTime
  Outcome         Choice          pass | exception | not_checked

LIST  CheckResults          (one row per check, per invoice)
  InvoiceRef      Text            indexed
  Check           Choice          rate | quantity | variation | contract_sum
  Sequence        Number          1 to 4. The order is data, not code.
  Status          Choice          pass | fail | not_run
  Expected        Text
  Found           Text
  Difference      Currency        dollars. Not a percentage, not a score.
  Confidence      Number
  ConfidenceBasis Text            so a person can discount it

LIST  Exceptions            (a record, not a notification)
  InvoiceRef      Text            indexed
  FailedCheck     Choice          rate | quantity | variation | contract_sum
  NeverRan        Text            the checks after the failure
  Difference      Currency
  Queue           Choice          needs_decision | waiting | done_reversible
                                  | blocked
  OpenedAt        DateTime
  DecidedBy       Person          blank until decided
  DecidedAt       DateTime
  Evidence        Hyperlink       what informed the decision

Nothing is deleted on resolution. Deciding writes two fields.

Sequence is stored rather than implied by the flow's shape, so the order is inspectable and a Power BI page can show which check stops the most invoices. A reader who wants to know where the money leaks asks that question first.

3. Every check, and what enforces it

The checkEnforced byHow
Four checks in a fixed order, stopping at the first failureCheck flowA terminating loop, not four parallel branches. The checks after a failure are recorded as not_run rather than silently skipped, because which ones never ran is information.
Differences are dollars, not scoresList designDifference is a Currency column. No percentage, no confidence-weighted total. A person approving a payment needs an amount.
An exception is a record, not a notificationExceptions listRaised as an item with a queue, an owner and an opened-at. A Teams card may announce it; the card is not the exception.
The record survives the decisionList designDeciding sets DecidedBy and DecidedAt. It does not delete the row or clear the difference, so the position is still readable next quarter.
The cumulative position, not just this claimMeasureCumulative is computed in the model across the order, because a run of small accepted differences is the failure mode a per-invoice view cannot see.
Confidence carries its basisList designConfidenceBasis is stored next to the number so a person can discount it. A bare confidence score is a number nobody can argue with, which is worse than none.

4. The three flows

For your developer. Show the fields
FLOW 1  Intake
  trigger  a file arrives in Originals, or an email with an attachment
  logic    store the original unchanged
           extract InvoiceRef, SupplierRef, Amount, OrderRef
           create the Invoices row with Outcome = not_checked
  never    modify the original. Extraction writes a row beside it.

FLOW 2  The four checks, in order
  trigger  an Invoices item is created
  logic    for sequence 1 to 4, IN A SEQUENTIAL SCOPE:
             run the check, write a CheckResults row
             if status = fail:
               write the remaining checks as not_run
               raise an Exception with NeverRan listing them
               set Invoices.Outcome = exception
               TERMINATE the scope
           all four pass -> Outcome = pass
  never    parallel branches. The order carries the meaning, and
           a quantity difference computed on a wrong rate is noise
           presented as a finding.

FLOW 3  Decide
  trigger  an operator action on an Exception
  logic    write DecidedBy, DecidedAt, Evidence and the new Queue
  never    delete the row, clear Difference, or reopen it as new.
           The cumulative measure reads the decided ones.

Power Automate makes parallel the easy shape and sequential the deliberate one. That is the whole reason this flow is written out here rather than left as an exercise.

5. The Power BI model and its measures

For your developer. Show the fields
Exceptions Open =
CALCULATE ( COUNTROWS ( Exceptions ), ISBLANK ( Exceptions[DecidedAt] ) )

Difference Open =
CALCULATE ( SUM ( Exceptions[Difference] ), ISBLANK ( Exceptions[DecidedAt] ) )

Cumulative By Order =
CALCULATE (
    SUM ( Exceptions[Difference] ),
    ALLEXCEPT ( Invoices, Invoices[OrderRef] )
)

Stopped At =                         -- which check stops the most invoices
CALCULATE ( COUNTROWS ( CheckResults ), CheckResults[Status] = "fail" )

Never Ran =
CALCULATE ( COUNTROWS ( CheckResults ), CheckResults[Status] = "not_run" )

Decision Latency Days =
AVERAGEX (
    FILTER ( Exceptions, NOT ISBLANK ( Exceptions[DecidedAt] ) ),
    DATEDIFF ( Exceptions[OpenedAt], Exceptions[DecidedAt], DAY )
)

Unchecked =                          -- should be near zero
CALCULATE ( COUNTROWS ( Invoices ), Invoices[Outcome] = "not_checked" )

Cumulative By Order is the measure the standard is really arguing for. Put it on the front page against each order, because the pattern it shows is invisible in any view built one invoice at a time.

6. The order to build it in

  1. Stand up the reference the invoice is checked against. Without it nothing downstream means anything.
  2. Create the library and three lists. Index InvoiceRef everywhere.
  3. Build Flow 1 and confirm the original is stored unchanged with a row beside it.
  4. Build Flow 2 with check 1 only, in a sequential scope with the termination already in place. Add checks 2 to 4 afterwards.
  5. Verify a failure at check 2 writes checks 3 and 4 as not_run. This is the pass test.
  6. Build Flow 3, and confirm deciding leaves the row readable.
  7. Connect Power BI. Cumulative By Order goes on the front page.

7. Four traps specific to this build

Running the four checks in parallel

Parallel branches are the obvious Power Automate shape and they are wrong here. The order is the claim the standard makes: a rate failure makes the quantity comparison meaningless, and running it anyway produces a second difference that is not real. Use a sequential scope with a termination condition.

Storing the source document as an attachment only

Attachments cannot be queried and do not survive a list migration cleanly. Put the original in a library with a link on the row, so an exception can always be opened next to what it came from.

Deleting exceptions when they are resolved

The tidy instinct destroys the only evidence of the pattern. Resolution is two fields on the row. The cumulative measure depends on the closed ones still being there.

A confidence number with nothing behind it

If the extraction produces 0.82 and nothing says why, nobody can decide whether to trust it, so they either trust all of them or none. Store the basis in words, in the same row.

8. Where the automation stops

  • Approving or rejecting a payment. The workflow produces a difference and evidence; the decision is a person's and it is recorded as theirs.
  • Accepting a variation. That is a commercial decision with contractual consequences.
  • Anything that assesses, chases or advises on a statutory payment claim. Security of Payment is a statutory process and it stays with the client and their advisor.
  • Changing the reference. If the order is wrong, that is a conversation, not a correction a flow makes on the way past.

The pass test. Stage an invoice that fails the second check and passes the first. Run it. You must get exactly two CheckResults rows with a status of pass or fail, two more recorded as not_run, and one Exception whose NeverRan names those two. Then decide that exception and confirm the row is still there with its Difference intact and DecidedBy set. A minute on test data. If all four checks produced a difference, they ran in parallel and three of those numbers are meaningless. If deciding emptied or removed the row, the cumulative position across the order is already gone.

Related reading