Business Central and the case of the missing approvals ︎
In absence of “water cooler” style chats I’ve found me and colleagues having Teams channel based chats on all sorts of BC related topics. One of those was to do with an instance where approvals go missing . Seems to be if using BC standard workflows and quote documents. What’s the full story here? Well a quote document might not stay like that forever – all being well it will be pushed through the “Make Order” function. Let’s use purchase quotes as an example. The purchase header table stores all the different purchase documents. Once you make a purchase quote into a purchase order, it merely moves it in the purchase header table to a different document type and document no. etc. How does this involve approvals? Well approvals use the RecordId and the quote moving to an order does not alter that. Below images show the original purchase quote approval entry and then how it looks after the “Make Order” function:


Why does this matter? Well in this case it looks like the approval took place at order stage, but what if you don’t do PO approvals. Could be a little confusing, and we don’t like confusing, where possible
The other thing that was noticed is that you can’t rely on archive records to save the day . You just don’t get any access to approvals relating to the original record. Furthermore, if you tried to add a page action how can you access the record because the values are now for the Purchase Order

What can be done to solve this? Well in my colleagues case it was decided that the deletion of the approval entry for the purchase quote could be skipped. In standard BC it recreates the approval entry record for the PO after the quote has been converted, and if an approval exists. I’m not writing this to talk about that solution though… If that solution is of more interest then checkout codeunit 96 “Purch.-Quote to Order” and this section of code:
ApprovalsMgmt.CopyApprovalEntryQuoteToOrder(RecordId, PurchOrderHeader."No.", PurchOrderHeader.RecordId);
IsHandled := false;
OnBeforeDeletePurchQuote(Rec, PurchOrderHeader, IsHandled);
if not IsHandled then begin
ApprovalsMgmt.DeleteApprovalEntries(RecordId);
PurchCommentLine.DeleteComments("Document Type".AsInteger(), "No.");
DeleteLinks();
Delete();
PurchQuoteLine.DeleteAll();
end;
Naturally, I wanted to know does this circumstance also take place with Power Automate BC approvals! Happy to say that things play out a little differently but still require a solution. The core difference is that the “Make Order” function does not alter the approval entry. Granted, if you’re using Power Automate BC approvals then a different table stores the approval entry values. The behavior works in the users favour though. The below shows the same purchase quote as used in the first example but after it has become an order. The drawback is a user cannot navigate to the record because it has now changed (“Record” page action allows this usually):

Just add a page action on the archive purchase quote pages and job done? Well I certainly thought so. However, navigating from a RecordId causes a few complications. One of the nice things about the standard BC approvals table is that you get the below fields included:
table 454 "Approval Entry"
{
Caption = 'Approval Entry';
ReplicateData = true;
fields
{
field(1; "Table ID"; Integer)
{
Caption = 'Table ID';
}
field(2; "Document Type"; Enum "Approval Document Type")
{
Caption = 'Document Type';
}
field(3; "Document No."; Code[20])
{
Caption = 'Document No.';
}
Sensible things to have included and additions that the “Workflow Webhook Entry” table (one used for Power Automate BC approvals) could benefit from. That was therefore my starting point. In addition to those fields being added the standard BC function for populating those fields also came in handy as part of a an event subscriber. Yes, the subscriber is handling many more possible records, but that gives you the chance to pick and choose the code for your circumstance:
[EventSubscriber(ObjectType::Table, Database::"Workflow Webhook Entry", 'OnAfterInsertEvent', '', false, false)]
local procedure AddRecordDetails(var Rec: Record "Workflow Webhook Entry")
var
RecRef: RecordRef;
WFWHEntry: Record "Workflow Webhook Entry";
Customer: Record Customer;
GenJournalBatch: Record "Gen. Journal Batch";
GenJournalLine: Record "Gen. Journal Line";
PurchaseHeader: Record "Purchase Header";
SalesHeader: Record "Sales Header";
IncomingDocument: Record "Incoming Document";
Vendor: Record Vendor;
EnumAssignmentMgt: Codeunit "Enum Assignment Management";
begin
RecRef.get(Rec."Record ID");
WFWHEntry := Rec;
WFWHEntry."Table ID" := RecRef.Number;
case RecRef.Number of
DATABASE::"Purchase Header":
begin
RecRef.SetTable(PurchaseHeader);
WFWHEntry."Doc. Type" := EnumAssignmentMgt.GetPurchApprovalDocumentType(PurchaseHeader."Document Type");
WFWHEntry."No." := PurchaseHeader."No.";
end;
DATABASE::"Sales Header":
begin
RecRef.SetTable(SalesHeader);
WFWHEntry."Doc. Type" := EnumAssignmentMgt.GetSalesApprovalDocumentType(SalesHeader."Document Type");
WFWHEntry."No." := SalesHeader."No.";
end;
DATABASE::Customer:
begin
RecRef.SetTable(Customer);
WFWHEntry."No." := Customer."No.";
end;
DATABASE::"Gen. Journal Batch":
RecRef.SetTable(GenJournalBatch);
DATABASE::"Gen. Journal Line":
begin
RecRef.SetTable(GenJournalLine);
case GenJournalLine."Document Type" of
GenJournalLine."Document Type"::Invoice:
WFWHEntry."Doc. Type" := WFWHEntry."Doc. Type"::Invoice;
GenJournalLine."Document Type"::"Credit Memo":
WFWHEntry."Doc. Type" := WFWHEntry."Doc. Type"::"Credit Memo";
else
WFWHEntry."Doc. Type" := GenJournalLine."Document Type";
end;
WFWHEntry."No." := GenJournalLine."Document No.";
end;
DATABASE::"Incoming Document":
begin
RecRef.SetTable(IncomingDocument);
WFWHEntry."No." := Format(IncomingDocument."Entry No.");
end;
DATABASE::Vendor:
begin
RecRef.SetTable(Vendor);
WFWHEntry."No." := Vendor."No.";
end;
end;
WFWHEntry.Modify();
end;
}

pageextension 50154 "Archived PQ List" extends "Purchase Quote Archives"
{
actions
{
addLast(navigation)
{
action("Archived Approvals")
{
CaptionML = ENU = 'Archived Approvals';
ApplicationArea = All;
Image = Archive;
Promoted = true;
PromotedCategory = Process;
PromotedIsBig = true;
PromotedOnly = true;
ToolTipML = ENU = 'Review approvals for this purchase quote';
trigger OnAction()
var
FlowEntries: Record "Workflow Webhook Entry";
ApprovalEntries: Record "Approval Entry";
begin
FlowEntries.setrange("Table ID", DATABASE::"Purchase Header");
FlowEntries.setrange("Doc. Type", Rec."Document Type");
FlowEntries.setrange("No.", Rec."No.");
RunModal(830, FlowEntries);
end;
}
}
}
}
It wasn’t quite the open and shut case I hoped for but I think we can conclude that the case of the missing approval is SOLVED! Case closed
This was originally posted here.
*This post is locked for comments