Dynamics 365 Finance & Operations

From spec to delivery:
one D365 F&O feature built with Claude Code

What the consultant and the developer see at each step, and the tooling that runs underneath.

The example

The request: one button on the transfer order

Automotive distributor. Vehicles leave the pre-delivery inspection (PDI) warehouse on transfer orders and go to a logistics center.

What the spec asked for

  • A "Create logistics center shipment" button on the transfer order
  • Release to the warehouse with the standard release logic
  • Collect the open work into a cluster for the logistics center
  • Set the shipment account to the default logistics center from parameters

Where things run

  • Our machine: Claude Code, local copies of the standard packages and the customer model, our tools (DmrTools)
  • Dev VM: no Claude, no git. Gets a patch zip; the developer builds and syncs
  • D365 dev: tested by script in Edge; the person signs in, no password reaches Claude
1Start with context
2Decisions
3Impact analysis
4Build & verify
5Patch & dev build
6Scripted test
7Deliver & follow up

Orange: a person decides. Green: runs on the dev VM. White: Claude does the work and leaves evidence.

1 Context2 Decisions3 Impact4 Build5 Patch6 Test7 Deliver

Steps 1–2 · Start with context, then let the consultant decide

Claude never starts from a blank chat, and never fills a gap in the spec with its own assumption.

DECISIONS.md: every gap becomes a question with a recommendation Consultant

QuestionRecommendation → decision
Privilege level (spec said "Delete")Invoke: the button runs an action
Data type of the "box" fieldExtend the standard cluster position EDT
Filter clusters by profile?No: template + route group + not locked
Keep apart from an existing custom cluster flowBy route group, chosen in parameters
Not in spec: license plate on each workYes, like other cluster creators
Team

Principles

Loaded automatically every session: "only the target scenario changes", "no design without impact analysis", "the consultant decides".

Project

Project Card

CLAUDE.md: package/model, dev URL, company, where the standard source is. Missing → Claude asks first.

Job

Job folder

DECISIONS.md + CONTINUE.md: the next session picks up exactly here.

1 Context2 Decisions3 Impact4 Build5 Patch6 Test7 Deliver

Step 3 · Impact analysis on two metadata indexes Claude

Before any design: who already touches these objects, in the standard and in the customer model. Answers come from an index, not from memory.

How the index works

  • Standard index: 239k XML, built once per D365 version
  • Customer index: ~5k XML, refreshed incrementally in seconds
  • Both attached in one query: "VendTable as it really is" = standard fields + customer extensions + CoC from both sides
  • ~0.25 s per question; validated against D365's own serializer
q nesne InventTransferOrders   # object + all extensions
q coc   InventTransferOrders   # CoC classes, wrapped methods
q kaynak WhsWarehouseRelease transferOrderFormRelease

What it found for this job

  • Standard release entry point WhsWarehouseRelease → call it, don't copy it
  • Transfer order form and class already extended in the customer model → add there
  • Another custom flow creates clusters → keep the two apart
  • Standard release only releases reserved quantity

What must not change

  • Standard "Release to warehouse" · non-PDI transfers · the existing cluster flow

Each becomes a regression scenario in step 6.

1 Context2 Decisions3 Impact4 Build5 Patch6 Test7 Deliver

Step 4 · Build, and let D365's own rules check it Claude

11 objects: 4 new, 7 updated. Every metadata file Claude edits is checked automatically by a hook, in the same turn.

The change set

newAction class: release → cluster → shipment account
newEDT, menu item (action), privilege
updTransfer order form + class extension (the button)
updWarehouse parameters table + form extension
updLabel files (3 languages)

Checks on every XML

  1. Round trip through D365's serializer: nothing lost, nothing under the wrong parent
  2. Element order and namespaces from a contract catalog read from D365's DLL (580 types)
  3. Form pattern rules and name clashes
  4. Every X++ reference exists in the standard or customer index
  5. Team review checklist, then a human

Why it matters: a valid XML can still drop a control silently. D365 loads it without error and the button is simply not there.

1 Context2 Decisions3 Impact4 Build5 Patch6 Test7 Deliver

Step 5 · One patch, one build round Dev VM

No git, so delivery is a self-checking zip. The same tool on every project.

PackageChanged files, hashes, added / removed members, apply script, readme. Self-test: apply + roll back
Dry runEach file on dev must be the EXPECTED version. Different version or lost member → stop
ApplyBackup first, then write; objects and labels added to the VS project
Build + syncDeveloper, in Visual Studio
CollectOne script zips logs and errors back. All errors read in one round

This job

Clean on the first build.

Undo

Any patch can be rolled back from its backup with one parameter.

Shared files

Label files are merged by label ID, so parallel jobs touching the same label file don't overwrite each other.

1 Context2 Decisions3 Impact4 Build5 Patch6 Test7 Deliver

Step 6 · Scripted test, and why we moved to Playwright Claude

First we let Claude click through D365 in the browser pane. Every click was a model round trip that re-read the whole conversation, and every re-run paid it again.

Browser pane, step by step
40–60
steps per scenario
657
browser calls in the session
~97M
input tokens, 250 round trips
Playwright script
1
command, all scenarios
1
round trip to read the result
~300
characters back

How it is built

  • Shared library (one copy): sign-in, filters, grid, tabs, save, messages, OData. Fixes go here, every project benefits
  • Job script: spec test steps → ~100 lines. Data prepared and checked via OData, UI only for the button

Scenarios for this job

  • PDI → logistics center: work clustered, shipment account set
  • Not a PDI transfer → refused · already received → button disabled
  • Nothing reserved → "no open work" warning
  • Passed step by step in the browser pane; then scripted, S1 run by script so far
1 Context2 Decisions3 Impact4 Build5 Patch6 Test7 Deliver

Step 7 · Deliver, then turn the new request into a second patch

The consultant gets a Word test & delivery document with screenshots. On reading it they asked: "the button should reserve the lines itself."

EvaluatePatch 1 is on dev and tested. Global auto-reservation must stay OFF: imports use transfer orders too
OptionsA: reserve what's possible · B: all or nothing. Consultant picks B
CodeOne new method, in the same transaction: any error rolls back every reservation
Patch 2Built on patch 1's output, not on the local model. 4 files, no DB sync
Re-testNew scenarios on dev (that day in the browser pane). Updated document

Why patch 2 is safe

  • Its base is what dev really has: patch 1's output
  • The class file must match patch 1's hash on dev, otherwise it stops
  • Labels merge by ID; a conflict stops it, even when forced

New scenarios

  • Stock 1, ordered 5 → error, no reservation left on any line
  • Quantity fixed to 1 → lines reserved, work created
  • 1 of 2 reserved by hand → only the missing 1 reserved

Request → decision → patch 2 → dev → tests → updated delivery document: the same day.

How we work

Who does what

Claude Code does the reading, writing and checking. People keep the decisions, the environment and the responsibility.

Consultant
  • Writes the spec
  • Answers open decisions
  • Accepts the delivery document
  • Raises follow-up requests
Claude Code
  • Reads the spec and the standard source
  • Impact analysis, code, metadata
  • Checks, review, patch
  • Test script, delivery document
Developer
  • Guides and reviews the work
  • Applies the patch, builds, syncs
  • Signs in for tests
  • Is accountable for what ships

House rules

  • Only the target scenario changes
  • No design before impact analysis
  • Verify against the standard, never guess
  • Claude never takes a consultant decision
  • One patch, one build round
  • "Not built" or "not tested" is said plainly
What one job leaves behind

Every step leaves a file someone else can pick up

The job does not live in a chat. Another developer, or the next session, can continue from these.

StepArtifactWho uses it
1–2CLAUDE.md, DECISIONS.md, CONTINUE.mdConsultant checks decisions; next session resumes from here
3Impact analysis notes + "must not change" listReviewer; becomes the regression scenarios
4Check results for every edited XMLReviewer before packaging
5Patch zip: patch.json, apply script, readme, backup on devDeveloper on dev; rollback if needed
5Build result zipClaude reads all errors in one round
6<job>.test.js + result file + failure screenshotsRe-run after any change, including the follow-up patch
7Word test & delivery documentConsultant and customer

New pitfalls found on the way are proposed to the team library, so the next job starts with them.

Click a sentence for details