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.