D365 Finance & Operations - Complete Debugging Framework v6
FORENSICS CORE (The Kernel)
These rules apply to EVERY playbook, EVERY time.
Anti-Hallucination Rules (NEVER violate)
| Rule | Description |
|------|-------------|
| SCHEMA FIRST | NEVER guess columns. Run SELECT TOP 1 * or sys.columns before any filtered query |
| NO HARD-CODED FILTERS | NEVER hard-code DATAAREAID, PARTITION, or database names without validation |
| PLATINUM RULE | ALWAYS find a WORKING example and DIFF it. 2 queries beats 15 |
| DIAMOND RULE | Check FULL STACK: UI/Form -> Business Logic -> Config -> Batch/Report/Integration |
| LINE CONTAMINATION | One bad LINE can override the entire HEADER classification |
| STOP CONDITIONS | If not found after 3 queries -> pivot upstream/downstream |
Universal Inputs
REQUIRED (at least one):
- businessKey: The document/record ID (SO number, PO, Invoice, Voucher, WorkId, etc.)
- symptom: What's wrong (missing, wrong amount, can't post, slow, etc.)
OPTIONAL (validate before using):
- legalEntity / company / DATAAREAID
- partition
- environment (DEV/UAT/PROD)
- user (who experienced it)
- timeWindow (when it happened)
- documentType (SalesOrder, PurchaseOrder, TransferOrder, Invoice, etc.)
- journalType (AP/AR/GL/FA)
- reportName / menuItemName
- batchJobId / batchTaskId
- dataEntityName / integrationName
- errorMessage (exact text)
- correlationId (for integrations)
Standard Output Format (Every Run)
## D365 Debug: [Symptom] - [BusinessKey]
### 1. Assumptions
- Assuming: [What I'm taking as given]
- NOT assuming: [What I'll verify]
- Routing to: [Playbook name]
### 2. First 3 Safe Checks (Schema-First)
1. [Schema discovery query]
2. [Existence check]
3. [State/status check]
### 3. Platinum Diff Plan
- Good reference: [How to find one]
- Diff query: [Compare bad vs good]
### 4. Diamond Stack Trace
- UI/Form layer: [What to check]
- Business logic: [Classes/methods]
- Configuration: [Setup tables]
- Execution layer: [Batch/report/integration]
### 5. Stop Conditions
- If [X] not found -> Pivot to [Y]
- If data correct but excluded -> Check [Z]
### 6. Resolution
[What fixed it or next steps]
SYMPTOM ROUTER
Start here. Match symptom to playbook:
| Symptom Pattern | Route To | First Check |
|-----------------|----------|-------------|
| "Missing from report" | REPORTING | Data exists? -> DP class filter |
| "Not posted / can't post" | POSTING | Validation errors -> Setup -> Workflow |
| "Wrong amount / calculation" | FINANCE-CALC | Platinum diff -> Line contamination |
| "User can't see / access" | SECURITY | Entry point -> Role -> Privilege |
| "Integration success but no record" | INTEGRATION | Staging -> Entity mapping -> Business events |
| "Batch didn't run / stuck" | BATCH | History -> Dependencies -> Batch group |
| "Slow / hanging / timeout" | PERFORMANCE | Locks -> SQL plan -> AOS load |
| "Invoice missing lines" | SCM-ORDER | SO/PO lines -> Packing slip -> Invoice match |
| "Work stuck / not released" | WMS | Wave -> Work -> Location directives |
| "Voucher wrong / missing" | FINANCE-GL | Journal -> Posting profile -> Dimensions |
CAPABILITY PACK: FINANCE
GL (General Ledger)
Key identifiers: Voucher, JournalNum, AccountingDate, MainAccount
Canonical lineage:
LedgerJournalTable (header)
-> LedgerJournalTrans (lines)
-> GeneralJournalEntry (posted)
-> GeneralJournalAccountEntry (account splits)
Schema discovery:
SELECT TOP 1 * FROM LedgerJournalTable;
SELECT TOP 1 * FROM GeneralJournalEntry;
Common contaminators:
- Wrong posting profile on line
- Dimension mismatch (MainAccount vs DefaultDimension)
- Currency/exchange rate at line level
- Blocked account or suspended dimension
Voucher prefix decoder: | Prefix | Source | Trace To | |--------|--------|----------| | INT | External integration | Staging table | | INV | AP Invoice | VendInvoiceJour | | PAY | Payment | VendPaymJournalTrans | | GJ | GL Journal | LedgerJournalTrans | | CXI | Customer Invoice | CustInvoiceJour |
AP (Accounts Payable)
Key identifiers: InvoiceId, VendAccount, Voucher, PurchId
Canonical lineage:
VendInvoiceInfoTable (pending)
-> VendInvoiceInfoLine
-> VendInvoiceJour (posted header)
-> VendInvoiceTrans (posted lines)
-> VendTrans (subledger)
-> GeneralJournalEntry (GL)
Common issues:
-- Invoice not posting: Check validation
SELECT * FROM VendInvoiceInfoTable
WHERE ParmId = 'INVOICE_ID' AND Posted = 0;
-- Match failures
SELECT PurchId, InvoiceId, MatchStatus, MatchVariance
FROM VendInvoiceInfoTable
WHERE InvoiceAccount = 'VENDOR';
AR (Accounts Receivable)
Key identifiers: InvoiceId, CustAccount, SalesId, Voucher
Canonical lineage:
SalesTable -> SalesLine
-> CustPackingSlipJour -> CustPackingSlipTrans
-> CustInvoiceJour -> CustInvoiceTrans
-> CustTrans -> GeneralJournalEntry
CAPABILITY PACK: SUPPLY CHAIN (SCM)
Order-to-Cash
Key identifiers: SalesId, ItemId, InventTransId
Canonical lineage:
SalesTable (header)
-> SalesLine (lines)
-> InventTrans (inventory transactions)
-> CustPackingSlipJour/Trans (packing slip)
-> CustInvoiceJour/Trans (invoice)
Missing invoice lines check:
-- Compare SO lines to invoice lines
SELECT
SL.SalesId, SL.ItemId, SL.LineNum,
SL.SalesQty AS OrderedQty,
ISNULL(IT.InvoicedQty, 0) AS InvoicedQty
FROM SalesLine SL
LEFT JOIN (
SELECT InventTransId, SUM(Qty) AS InvoicedQty
FROM CustInvoiceTrans
GROUP BY InventTransId
) IT ON SL.InventTransId = IT.InventTransId
WHERE SL.SalesId = 'SO_NUMBER'
AND SL.SalesQty <> ISNULL(IT.InvoicedQty, 0);
Procure-to-Pay
Key identifiers: PurchId, ItemId, InventTransId
Canonical lineage:
PurchTable -> PurchLine
-> InventTrans
-> VendPackingSlipJour/Trans (product receipt)
-> VendInvoiceJour/Trans (invoice)
Inventory
Key identifiers: ItemId, InventDimId, InventTransId
On-hand investigation:
-- Current on-hand
SELECT ItemId, InventDimId, PhysicalInvent, AvailPhysical
FROM InventSum
WHERE ItemId = 'ITEM';
-- Transaction history
SELECT * FROM InventTrans
WHERE ItemId = 'ITEM'
ORDER BY DatePhysical DESC;
CAPABILITY PACK: WAREHOUSE (WMS)
Key identifiers: WorkId, LoadId, ShipmentId, WaveId, WHSContainerId
Canonical lineage:
WHSWave
-> WHSLoadTable -> WHSLoadLine
-> WHSWorkTable -> WHSWorkLine
-> WHSContainerTable
-> WHSWorkInventTrans
Work stuck diagnosis:
-- Work status check
SELECT WorkId, WorkStatus, TargetLicensePlateId,
CreateDateTime, UserId
FROM WHSWorkTable
WHERE LoadId = 'LOAD_ID';
-- Wave status
SELECT WaveId, WaveStatus, WaveProcessed
FROM WHSWave
WHERE WaveId = 'WAVE_ID';
Common issues:
- Location directive not finding put location
- Work template missing lines
- Reservation not available
- License plate conflicts
Location directive debug:
SELECT * FROM WHSLocDirTable
WHERE WorkType = 1 -- Put
AND WorkTransType = 1 -- Sales
ORDER BY SeqNum;
CAPABILITY PACK: BATCH & OPERATIONS
Key identifiers: BatchJobId, Caption, Status, StartDateTime
Batch job diagnosis:
-- Job history
SELECT TOP 20
RecId, Caption, Status,
StartDateTime, EndDateTime,
CreatedBy, AlertsProcessed
FROM BatchJob
WHERE Caption LIKE '%JOB_NAME%'
ORDER BY StartDateTime DESC;
-- Status decoder
-- 0=Hold, 1=Waiting, 2=Executing, 3=Error, 4=Finished, 5=Canceling
-- Stuck tasks
SELECT BJ.Caption, BT.ClassNumber, BT.Status, BT.Info
FROM BatchJob BJ
JOIN BatchTask BT ON BJ.RecId = BT.BatchJobId
WHERE BJ.Status = 2 -- Executing
AND BJ.StartDateTime < DATEADD(hour, -2, GETDATE());
Why didn't it run?
- Check
BatchJob.Status- is it on Hold (0)? - Check
BatchGroupassignment - is AOS in the group? - Check dependencies -
BatchConstraintstable - Check recurrence -
BatchJob.RecurrenceData - Check
BatchServerGroup- is batch server enabled?
CAPABILITY PACK: SECURITY
Key identifiers: UserId, SecurityRole, MenuItemName
"User can't see/do X" diagnosis:
-- 1. What roles does user have?
SELECT SR.Name AS RoleName, URS.AssignmentMode
FROM SecurityUserRole URS
JOIN SecurityRole SR ON URS.SecurityRole = SR.RecId
WHERE URS.User = 'USER_ID';
-- 2. What entry point is needed?
SELECT * FROM SecurableObject
WHERE Name = 'MENU_ITEM_NAME';
-- 3. Which roles have that entry point?
SELECT DISTINCT SR.Name AS RoleName
FROM SecurityRole SR
JOIN SecurityRolePermission SRP ON SR.RecId = SRP.SecurityRole
JOIN SecurableObject SO ON SRP.SecurableObject = SO.RecId
WHERE SO.Name = 'MENU_ITEM_NAME';
Stack trace:
Menu Item (entry point)
-> Security Privilege (atomic permission)
-> Security Duty (job function)
-> Security Role (assigned to user)
Common issues:
- Role assigned but duty excluded
- Data security policy filtering out records
- Privilege exists but grant type is Deny
- Legal entity restriction on role
CAPABILITY PACK: INTEGRATION (DMF/OData)
Key identifiers: ExecutionId, EntityName, DefinitionGroupId
DMF import diagnosis:
-- Execution history
SELECT TOP 20
ExecutionId, DefinitionGroupId, StartTime, EndTime,
StagingStatus, TargetStatus, ErrorCount
FROM DMFExecutionSummary
WHERE DefinitionGroupId = 'PROJECT_NAME'
ORDER BY StartTime DESC;
-- Staging table errors
SELECT * FROM [STAGING_TABLE]
WHERE DefinitionGroup = 'PROJECT'
AND TransferStatus = 2; -- Error
-- Error details
SELECT ExecutionId, Field, ErrorCode, ErrorMessage
FROM DMFExecutionErrors
WHERE ExecutionId = 'EXEC_ID';
"Success but no record" diagnosis:
- Check staging table - did data land?
- Check
TransferStatus- did it process? - Check entity mapping - field mappings correct?
- Check target table - record exists but different key?
- Check business validation - rejected by entity logic?
Integration lineage:
Source File/API
-> DMF Staging Table (raw data)
-> DMF Execution (processing)
-> Target Table (final data)
-> Business Events (if configured)
CAPABILITY PACK: REPORTING (SSRS)
Key identifiers: ReportName, DesignName, MenuItemName
CRITICAL: Logic is in Data Provider (DP), NOT Controller!
Controller Class: Parameters, dialog, print destination
-> DP Class (*DP): Query building, data population
-> TMP Table (*Tmp): Holds processed data
-> SSRS Design: Rendering only
Report not showing records diagnosis:
-- 1. Does data exist?
SELECT TOP 10 * FROM [SourceTable]
WHERE [Filter] = 'VALUE';
-- 2. Find the DP class
-- Look for [ReportName]DP in AOT
-- 3. Check DP.processReport() method
-- Look for QueryRun, while select, insertTmp()
Common issues:
- Query range in DP excludes records
- Print management setup missing
- Report parameters not passed to DP
- TMP table insert logic has additional filters
CAPABILITY PACK: PERFORMANCE
Key identifiers: SPID, WaitType, QueryHash
Blocking/locking diagnosis:
-- Current blocks
SELECT
blocking.session_id AS BlockingSession,
blocked.session_id AS BlockedSession,
blocked.wait_type,
blocked.wait_time,
OBJECT_NAME(locked.object_id) AS LockedTable
FROM sys.dm_exec_requests blocked
JOIN sys.dm_exec_sessions blocking ON blocked.blocking_session_id = blocking.session_id
JOIN sys.dm_tran_locks locked ON blocked.session_id = locked.request_session_id;
-- Long running queries
SELECT TOP 10
r.session_id,
r.start_time,
r.status,
SUBSTRING(t.text, 1, 200) AS QueryText
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.status = 'running'
ORDER BY r.total_elapsed_time DESC;
Missing indexes:
SELECT TOP 20
OBJECT_NAME(mid.object_id) AS TableName,
mid.equality_columns,
mid.inequality_columns,
migs.user_seeks,
migs.avg_user_impact
FROM sys.dm_db_missing_index_details mid
JOIN sys.dm_db_missing_index_groups mig ON mid.index_handle = mig.index_handle
JOIN sys.dm_db_missing_index_group_stats migs ON mig.index_group_handle = migs.group_handle
ORDER BY migs.user_seeks * migs.avg_user_impact DESC;
TEST PROMPTS (Validate Coverage)
Use these to verify the skill works:
- Finance: "Voucher INT-123456 exists but amount is wrong"
- SCM: "Sales order SO-001234 invoice missing 3 lines"
- WMS: "Work ID 5678 created but stuck, not releasing"
- Batch: "Vendor invoice posting batch didn't run last night"
- Security: "User JSMITH can't see Post button on vendor invoice"
- Integration: "DMF import shows success but no purchase orders created"
KEY PRINCIPLE
Don't debug blind. ROUTE -> SCHEMA -> PLATINUM -> DIAMOND -> PIVOT.
The symptom tells you WHERE to look. The working example tells you WHAT's wrong.