Bulk Unapplication of Customer Invoices in Business Central
4/16/2025
A friend on a Business Central-focused WhatsApp group I belong to asked this question:
"Hi guys. Hoping y'all are doing great. Just a question... Is there anyone here who knows how to do bulk unapplication of customer invoices that are already tied to customer payments?"
My thoughts:
Yes! In Microsoft Dynamics 365 Business Central, you can unapply customer invoices that are already applied to payments — even in bulk — but the system doesn't natively support bulk unapplication through the standard UI. You’ll have to get creative. Here are a few approaches I shared:
Option 1: Manual Unapplication (Standard Way)
This is straightforward, but tedious if you’re dealing with more than a handful of records:
- Go to Customer > Ledger Entries.
- Find the payment or invoice you want to unapply.
- Click Process > Unapply Entries.
- Confirm unapplication.
It works, but it’s slow for high volumes.
Option 2: Use a Configuration Package (Advanced)
If you're feeling bold, you can technically unapply entries using a config package and Excel.
- Create a config package and add table
379 - Detailed Cust. Ledg. Entry. - Export entries where
Applied Entry to Entry No.> 0. - In Excel, clear the following fields:
Applied Entry to Entry No.,Application Type,Application Date. - Import the Excel file back and apply the package.
Important: This method can cause inconsistencies if related fields (like Amount Remaining) are not recalculated properly. Use only in sandbox or with full data backups.
Option 3: AL Code for Safe Bulk Unapply
This is the cleanest way to handle bulk unapplication if you’re a developer or working with one.
Here’s the core AL procedure I used:
procedure BulkUnapplyCustomerEntries(CustomerNo: Code[20])
var
CustLedgerEntry: Record "Cust. Ledger Entry";
CustUnapply: Codeunit "CustUnapplyPostedEntries";
UnappliedCount: Integer;
begin
UnappliedCount := 0;
CustLedgerEntry.Reset();
CustLedgerEntry.SetRange("Customer No.", CustomerNo);
CustLedgerEntry.SetRange(Open, false);
if CustLedgerEntry.FindSet() then
repeat
if CustLedgerEntry."Applied Entries Exist" then begin
CustUnapply.Unapply(CustLedgerEntry);
UnappliedCount += 1;
end;
until CustLedgerEntry.Next() = 0;
Message('Successfully unapplied %1 entries for customer %2', UnappliedCount, CustomerNo);
end;
I then wrapped this in a custom page so I could run it from the BC UI:
AL Page: Bulk Unapply from Customer List
This allows you to click a button on a customer and unapply all entries for that customer.
page 50100 "Bulk Customer Unapply"
{
PageType = List;
SourceTable = Customer;
ApplicationArea = All;
UsageCategory = Tasks;
layout
{
area(content)
{
repeater(Group)
{
field("No."; "No.") { }
field(Name; Name) { }
}
}
}
actions
{
area(processing)
{
action(BulkUnapply)
{
Caption = 'Bulk Unapply Entries';
trigger OnAction()
var
CustLedgerEntry: Record "Cust. Ledger Entry";
CustUnapply: Codeunit "CustUnapplyPostedEntries";
UnappliedCount: Integer;
begin
if not Confirm('Are you sure?', false) then exit;
CustLedgerEntry.SetRange("Customer No.", Rec."No.");
CustLedgerEntry.SetRange(Open, false);
if CustLedgerEntry.FindSet() then
repeat
if CustLedgerEntry."Applied Entries Exist" then begin
CustUnapply.Unapply(CustLedgerEntry);
UnappliedCount += 1;
end;
until CustLedgerEntry.Next() = 0;
Message('Unapplied %1 entries.', UnappliedCount);
end;
}
}
}
}
Bonus: Report for Background Execution
This report version can be scheduled or filtered for multiple customers and date ranges:
report 50101 "Bulk Unapply Customer Entries"
{
ProcessingOnly = true;
dataset
{
dataitem(Customer; Customer)
{
RequestFilterFields = "No.";
dataitem("Cust. Ledger Entry"; "Cust. Ledger Entry")
{
DataItemLink = "Customer No." = Customer."No.";
DataItemTableView = WHERE(Open = CONST(false), "Applied Entries Exist" = CONST(true));
RequestFilterFields = "Posting Date";
trigger OnAfterGetRecord()
var
CustUnapply: Codeunit "CustUnapplyPostedEntries";
begin
CustUnapply.Unapply(Rec);
TotalUnapplied += 1;
end;
}
}
}
trigger OnPostReport()
begin
Message('Unapplied %1 entries in total.', TotalUnapplied);
end;
var
TotalUnapplied: Integer;
}
Closing Thoughts
If you only have a few entries, stick with the standard UI. For power users or mass cleanup, the AL extension route is safest. The config package trick should only be used if you know what you're doing — and never on production data without testing.
Hope this helps someone else facing the same challenge! Cheers!