Step-by-Step: Using Copilot to Create, Confirm, and Track Purchase Orders in D365 F&O


 Extending Copilot for Finance & Operations with PO Management



This blog demonstrates how to extend Copilot for Finance and Operations (F&O) by creating a topic that performs Purchase Order (PO) actions such as:

1.     Creating a PO

2.     Confirming a PO

3.     Checking PO status


Prerequisites

1.     Power Platform Integration must be enabled in Microsoft Dynamics Lifecycle Services (LCS).

2.     Dual-write is not required unless you are using it.

3.     Once enabled, most required Copilot features are deployed automatically.

4.     Copilot and AI agents are not supported in developer environments deployed via LCS.

Reference: Enable Copilot

Copilot Studio Concepts

1.     Knowledge

o   Knowledge is the data Copilot uses to answer questions.

o   Examples: SharePoint sites, Dataverse tables, OneDrive files, Power BI datasets,

 websites.

o   Purpose: Instead of scripting every answer, Copilot pulls answers directly from the connected sources.

2.     Topics

o   Topics are predefined conversation flows for handling specific user intents.

o   Examples: “Check PO Status,” “Create a Leave Request,” “Get Invoice Summary.”

o   Purpose: Topics guide multi-step conversations and structure user interaction.

3.     Tools

o   Tools are the extensions Copilot uses to execute actions.

o   Examples: Run an X++ action in D365 F&O, trigger a Power Automate flow, call APIs.

    •   Purpose: Tools allow Copilot not just to answer but also to perform actions.

Demo: Creating a PO Agent in Copilot Studio

We will build a PO Agent Topic in Copilot Studio to:

1.     Create a PO

2.     Confirm a PO

3.     Check PO Status

Step 1: Create a New Topic

1.     Open Copilot Studio and go to Topics.

2.     Select “New Topic” and provide a name (for example, PO Agent).

3.     Add trigger phrases such as:

o   “Talk to a PO agent”

o   “Contact purchase order agent”

o   “I need help with my PO”



Step 2: Add a Greeting Message

1.     Insert a Question node with a greeting message.
Example: “Hi, I am your PO agent. Before we begin, could you provide a few details?”

2.     Save the response in a variable (for example, LE).




Step 3: Provide Action Choices

1.     Add another Question node asking what action the user wants to perform.

2.     Give multiple choice options such as Create PO, Get PO Status, Confirm PO.

3.     Save the response into a variable (for example, POAction).



Step 4: Add Conditional Branching

1.     If POAction = Create PO → Redirect to Create PO subflow.

2.     If POAction = Get PO Status → Redirect to PO Status subflow.

3.     If POAction = Confirm PO → Redirect to Confirm PO subflow.


Subflows

1. Get PO Status Flow

1.     Ask for PO Number and save as PONumber.




2.     Call Power Automate Flow (GetPOStatusV1) with inputs PONumber and LE.

To create a new flow from copilot studio




Power Automate Flow for Get PO Status

Step 1: Trigger

1.     The flow is triggered when an agent calls it.

2.     Inputs expected: PONumber and LE.



Step 2: Get Token

1.     Use HTTP action to request a token from Azure AD.

2.     The token is used to authenticate against the D365FO OData API.

URI : https://login.microsoftonline.com/{TenantId}/oauth2/token

In body you have to give

client_id=********&client_secret=**************&grant_type=client_credentials&resource=https://**********devaos.axcloud.dynamics.com




Step 3: Initialize Variable

1.     Create a variable to hold the token generated



Step 4: Get PO Status from D365FO

1.     Use HTTP action to call D365FO OData API:

2.  https://<env>.cloud.dynamics.com/data/PurchaseOrderHeaders?
3.  $filter=PurchaseOrderNumber eq 'PONumber' and dataAreaId eq 'LE' 
4.  &$select=PurchaseOrderStatus

5.     Retrieve the PurchaseOrderStatus field for the given PO number and legal entity.



Step 5: Run a Prompt

1.     Convert the response into a user-friendly message using AI builder.

We can create a custom prompt



Give the prompt






Step 6: Respond to Agent

1.     Send the final response generated from the prompt back to Copilot.

outputs('Run_a_prompt')?['body/responsev2/predictionOutput/text']


2. Get PO Status Flow

Back to copilot flow show the response to user




I have shown the complete flow for PO Status. For the other two flows (PO Create and PO Confirm), I followed a similar pattern. The only differences are:

  • For PO Confirm, I created a custom service in D365FO and called that service from Power Automate.

Service class
 public DMPOConfirmResponseContract confirmPO(DMPOConfirmRequestContract _request)
 {
     var response = new DMPOConfirmResponseContract();
     changecompany(_request.parmDataAreaId())
     {
         try
         {
             
             PurchFormLetter purchFormLetter;
             PurchTable      purchTable;

             purchTable      = PurchTable::find(_request.parmPoNum());
             if (purchTable.RecId)
             {
                 purchFormLetter = PurchFormLetter::construct(DocumentStatus::PurchaseOrder);
                 PurchaseOrderId purchOrderDocNum = strFmt('%1-%2', purchTable.PurchId, VendPurchOrderJour::numberOfPurchaseOrderVersions(purchTable)+1);
                 purchFormLetter.update(purchTable, purchOrderDocNum);
                 response.parmSuccess(true);
                 response.parmPoNum(purchTable.PurchId);
                 response.parmDebugMessage(strFmt("Purchase order %1 confirmed successfully.", purchTable.PurchId));
             }
             else
             {
                 response.parmSuccess(false);
                 response.parmErrorMessage(strFmt("Purchase order %1 not found.", _request.parmPoNum()));
             }
         }
         catch (Exception::Error)
         {
             response.parmSuccess(false);
             response.parmErrorMessage(this.getException());
         }
         catch (Exception::CLRError)
         {
             System.Exception interopException = CLRInterop::getLastException();
             response.parmSuccess(false);
             response.parmErrorMessage(interopException.ToString());
         }
         return response;
     }
 }
  • For PO Create, I used the available data entities. First, I created the PO header using the Purchase Order Header entity, and then I added the lines using the Purchase Order Line entity.

PO Header create Odata API

https://d365fodevv266998e305230d431devaos.axcloud.dynamics.com/data/PurchaseOrderHeaders

Body
{ "dataAreaId": "", "OrderVendorAccountNumber": "@{triggerBody()?['text']}",// Ask the qustion to user for which vendor acccount number and store in a variable "AccountingDate": "@{formatDateTime(utcNow(), 'yyyy-MM-ddTHH:mm:ssZ')}", "RequestedDeliveryDate": "@{formatDateTime(utcNow(), 'yyyy-MM-ddTHH:mm:ssZ')}" }

PO Line create Odata API

https://d365fodevv266998e305230d431devaos.axcloud.dynamics.com/data/PurchaseOrderLinesV2

{ "dataAreaId": "@{triggerBody()?['text_3']}", "PurchaseOrderNumber": "@{triggerBody()?['text_2']}", "LineNumber": @{triggerBody()?['number_1']}, "OrderedPurchaseQuantity": @{triggerBody()?['number']}, "ItemNumber": "@{triggerBody()?['text']}", "RequestedDeliveryDate": "@{formatDateTime(utcNow(), 'yyyy-MM-ddTHH:mm:ssZ')}" }

Publish this topic to your FnO and you can access this in the side car. Feel free to try this out, and reach out to me if you have any questions.





Post a Comment

0 Comments