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:

  1. Go to Customer > Ledger Entries.
  2. Find the payment or invoice you want to unapply.
  3. Click Process > Unapply Entries.
  4. 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.

  1. Create a config package and add table 379 - Detailed Cust. Ledg. Entry.
  2. Export entries where Applied Entry to Entry No. > 0.
  3. In Excel, clear the following fields: Applied Entry to Entry No., Application Type, Application Date.
  4. 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!

← Back to Blog