Skip to content

Sync sales order SAP document numbers back to local orders#21

Merged
Ngonie69 merged 1 commit into
mainfrom
claude/sales-order-doc-number-sync-eeb4a1
Jul 20, 2026
Merged

Sync sales order SAP document numbers back to local orders#21
Ngonie69 merged 1 commit into
mainfrom
claude/sales-order-doc-number-sync-eeb4a1

Conversation

@Ngonie69

Copy link
Copy Markdown
Owner

The problem

A SAP sales order create can commit while the response read, the local save, or the short in-request reconciliation window fails. Every failure path then discarded the SAP linkage and reverted the order to Pending — so the order sat stale in the web app against a live SAP document, with nothing in the codebase that would ever re-check it.

SO-20260720-0015 hit this: posted to SAP as DocNum 78997 (remarks confirm "Approved by Tatenda Chikaka on 20 Jul 2026 09:35"), still showing Pending with no document number locally. Recovery required a human to click Approve again — the pre-create duplicate check would then find and link it — but nothing prompted anyone to do that.

What changed

New SalesOrderReconciliationJob — Quartz sweep on a 2-minute interval that links recent unlinked orders to SAP documents found by U_OrderNumber, reusing the existing duplicate-safe attach path. Scoped to a 7-day lookback and 25 orders per run. Jobs/ already had posting jobs for invoices, transfers and incoming payments; sales orders had no equivalent.

Three request-path handlers no longer discard a valid linkage — the web auto-post catch, the approve rollback, and PostToSAPAsync. The last had a subtler variant: its status revert was correctly guarded, but it then set a sync error and rethrew regardless, so a successfully-posted order could end up Approved-with-DocNum yet reported to the caller as a failure. Each now treats a linked order as a post that succeeded.

Widened in-request reconciliation — from 5 linear attempts over ~7.5s to 6 attempts of capped exponential backoff (~25s). The old window gave up while SAP was still making the document queryable, which is the likely proximate cause for this 103-line order.

5s bounded retry on the posting advisory lock — the sweep takes the same per-order lock, so without this it could surface to a user as "already being approved by another request". That error message is unchanged byte-for-byte; it now fires only after the full 5s.

Reviewer notes

  • The lock retry does not mask genuine contention. The two cases separate cleanly by duration: the sweep holds the lock for one SAP lookup (well under a second), while a real concurrent approve holds it across the full post plus up to ~25s of backoff. No real approve finishes inside 5s, so a genuine double-approval still gets told to wait and refresh. The sweep passes TimeSpan.Zero so it always yields to user requests rather than queuing ahead of them.
  • Why the backoff is capped at ~25s and not longer: it runs inside the request holding the posting lock, and on the post-failure path it is invoked with CancellationToken.None, so the delay is not interruptible by a client disconnect. Longer would risk client-side timeouts on an already-failing request. The 2-minute sweep absorbs anything slower.
  • Not covered by tests — there's no test project in the repo, and the reconciliation path only exercises against a live SAP. Verified by build only (clean, 0 warnings).
  • Known limitation: orders recovered by the sweep or the approve-path guard will show Approved with a blank "Approved by" where the original rollback already cleared ApprovedByUserId. That information isn't recoverable. SO-20260720-0015 is in that state.

Verification after deploy

SO-20260720-0015 should pick up DocNum 78997 within a few minutes of startup — the job logs at Information when it links anything, so grep the API logs for "linked". Expect the first few runs to be chattier against the Service Layer than steady state if there's a backlog of stale orders in the 7-day window.

🤖 Generated with Claude Code

A SAP sales order create can commit while the response read, the local
save, or the short in-request reconciliation window fails. Every failure
path then discarded the SAP linkage and reverted the order to Pending, so
the order sat stale in the web app against a live SAP document with
nothing to re-check it. SO-20260720-0015 hit this: posted as DocNum
78997, still showing Pending locally.

Add SalesOrderReconciliationJob, a Quartz sweep on a 2 minute interval
that links recent unlinked orders to SAP documents found by
U_OrderNumber, reusing the existing duplicate-safe attach path. Scoped to
a 7 day lookback and 25 orders per run.

Stop the three request-path handlers from discarding a linkage that is
already valid: the web auto-post catch, the approve rollback, and
PostToSAPAsync, which kept a linked order Approved but still flagged a
sync error and rethrew. Each now treats a linked order as a post that
succeeded.

Widen the in-request reconciliation from 5 linear attempts over ~7.5s to
6 attempts of capped exponential backoff (~25s), which was giving up
while SAP was still making the document queryable. Kept short on purpose
since it runs under the posting lock; the sweep absorbs anything slower.

Give the posting advisory lock a 5s bounded retry so the sweep, which
takes the same per-order lock, cannot surface to a user as "already being
approved by another request". A genuine concurrent approve runs far
longer than 5s and still reports unchanged; the sweep passes TimeSpan.Zero
so it always yields to user requests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Ngonie69
Ngonie69 merged commit ed5d6f1 into main Jul 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant