microsoft / microsoft/BCApps

[Bug]: [Subscription Billing] Create Billing Documents assigns the document no. to Usage Data Billing rows outside the billing line period

Open
#11,326 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Approved Team: Finance
Dominant language
AL
Stars
683
Forks
459
Avg merge
3d 26m
Merged PRs (30d)
633

Description

Describe the issue

When a contract invoice is created for a usage-based Subscription Line, all open Usage Data Billing records of that contract line are assigned to the new document, not only the records whose charge period lies inside the billing line's period.

This happens whenever usage data for more than one period is open at document creation time, for example:

  • usage for January and February has been imported, the user shortens the proposal line to 31 January with the standard Change Billing To Date action, and creates the document, or
  • a later month's usage is imported between creating the billing proposal and creating the documents.

The invoice amount is correct — it contains only the usage of the billed period — but the records of the later period are stamped with that invoice's document no. anyway. They are then treated as invoiced, skipped by every later billing proposal, and their amount is never invoiced at all. Nothing indicates this to the user; the loss surfaces only when imported usage is reconciled against invoiced usage.

Where it breaks. In codeunit 8060 Create Billing Documents, InsertSalesLineFromTempBillingLine stamps usage data with no date filter:

if ServiceCommitment."Usage Based Billing" then begin
    UsageDataBilling.SetRange(Partner, Enum::"Service Partner"::Customer);
    UsageDataBilling.SetRange("Subscription Contract No.", CustomerContractLine."Subscription Contract No.");
    UsageDataBilling.SetRange("Subscription Contract Line No.", CustomerContractLine."Line No.");
    UsageDataBilling.SetRange("Document Type", Enum::"Usage Based Billing Doc. Type"::None);
    UsageDataBilling.SetRange("Document No.", '');
    if UsageDataBilling.FindSet() then begin
        BillingLineNoByTempEntryNo.Get(TempBillingLine."Entry No.", BillingLineNo);
        repeat
            UsageDataBilling.SaveDocumentValues(UsageBasedDocTypeConv.ConvertSalesDocTypeToUsageBasedBillingDocType(SalesLine."Document Type"), SalesLine."Document No.",
                                                                       SalesLine."Line No.", BillingLineNo);
        until UsageDataBilling.Next() = 0;
    end;
end;

The price of that same sales line is computed a few lines earlier by SetInvoicePriceFromUsageDataBilling, which goes through Subscription Line.IsUsageDataBillingFoundSetUsageDataBillingFilters and is period-aware:

UsageDataBilling.SetFilter("Charge Start Date", '>=%1', BillingFromDate);
UsageDataBilling.SetFilter("Charge End Date", '<=%1', CalcDate('<1D>', BillingToDate));

So the invoice amount covers Billing from..Billing to while the stamping marks every open row of the contract line as invoiced. The proposal step is period-aware too — Billing Proposal.UpdateUsageDataBillingLineNoWhenBillingProposalIsCreated uses the same SetUsageDataBillingFilters to set Billing Line Entry No. — so the document step is the only place without the date filter.

Once stamped, the rows are excluded from every later proposal: Billing Proposal.ProcessServiceCommitment filters Document Type = None and skips the Subscription Line when nothing is left.

InsertPurchaseLineFromTempBillingLine has the identical defect for Partner = Vendor.

Expected behavior

Only Usage Data Billing records whose charge period lies inside the billing line's Billing from / Billing to receive the document values. Records outside that period stay open (Document Type = None, Document No. = '') so the next billing proposal picks them up.

Proposed fix — apply the date part of SetUsageDataBillingFilters to the stamping loop in both procedures:

UsageDataBilling.SetFilter("Charge Start Date", '>=%1', TempBillingLine."Billing from");
UsageDataBilling.SetFilter("Charge End Date", '<=%1', CalcDate('<1D>', TempBillingLine."Billing to"));

Deliberately not the Usage Base Pricing range of that filter. Enum 8007 is Extensible = true, and a bounded range would drop extension values from the stamping altogether — the same class of problem as #9223.

Steps to reproduce

Clean company from Evaluation – Contoso Sample Data, Subscription Billing module enabled.

  1. Create a Subscription Package with one customer Subscription Line: Usage Based Billing = Yes, Usage Based Pricing = Usage Quantity, Billing Rhythm = 1M, Invoicing via = Contract.
  2. Sell the item to a customer and create the Customer Subscription Contract so the Subscription Line has Next Billing Date = 01/01/2026.
  3. Create a Usage Data Supplier of type Generic and import two usage records for that subscription: Billing Period Start/End 01/01/2026–31/01/2026 (quantity 10) and 01/02/2026–28/02/2026 (quantity 20). Run the processing steps until two Usage Data Billing rows exist with Document Type = None and Document No. empty.
  4. Open Recurring Billing, pick a customer billing template, run Create Billing Proposal with Billing Date = 28/02/2026. One billing line appears, Billing from 01/01/2026, Billing to 28/02/2026.
  5. Select the line, run Change Billing To Date, enter 31/01/2026. The line now shows Billing to 31/01/2026 and a unit price based on quantity 10 only.
  6. Run Create Documents.
  7. Open Usage Data Billings for the contract line.
    • Expected: only the January row has Document Type = Invoice and the new Document No.; the February row is still Document Type = None.
    • Actual: both rows carry the January invoice's Document No. and Document Line No., although the invoice line contains the January quantity only.
  8. Run Create Billing Proposal again with Billing Date = 28/02/2026.
    • Expected: a billing line for 01/02/2026–28/02/2026.
    • Actual: no billing line is created; the February usage is never invoiced.

Variant: skip step 5 and instead import the February usage after step 4 and before step 6. Same result.

Additional context

Verified on Subscription Billing 28.5.54151.54450 and on main.

Affected objects:

  • src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al (InsertSalesLineFromTempBillingLine, InsertPurchaseLineFromTempBillingLine)
  • src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al (SetUsageDataBillingFilters, for the filter this should mirror)

Related: #9223 / PR #9225 change the same filter procedure for the pricing side; this issue concerns the stamping loop, which does not call that procedure at all.

Also related: #11327, item 3, asks for an event to bill one usage period at a time. That request depends on this defect being fixed first.

I will provide a fix for a bug

  • I will provide a fix for a bug

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al, focusing on InsertSalesLineFromTempBillingLine and InsertPurchaseLineFromTempBillingLine. Compare their usage-data stamping filters with SetUsageDataBillingFilters in src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al, then reproduce the January/February scenario. Done means only records within the billing period receive document values and later-period records remain open for the next proposal.

Written by the indexing model from the issue text.

Assessment

Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.