diff --git a/src/Apps/W1/Subscription Billing/App/Base/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Base/docs/CLAUDE.md new file mode 100644 index 0000000000..44a7bd00c1 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Base/docs/CLAUDE.md @@ -0,0 +1,55 @@ +# Base + +Foundation layer for the entire Subscription Billing app. Provides global +configuration, shared utility codeunits, and base-app extensions that wire +subscription billing into standard Business Central. + +## How it works + +`SubscriptionContractSetup` (table 8051) is the single-row configuration +record. It stores number series for customer/vendor contracts and +subscriptions, default billing periods and rhythms, invoice text layout +rules, and deferral journal settings. `SubBillingInstallation` seeds the +record on install and on company initialization; `UpgradeSubscriptionBilling` +handles schema migrations across versions. + +The `ServicePartner` enum (Customer | Vendor) is the fundamental +discriminator used across the entire app -- contract tables, subscription +lines, billing logic, and import codeunits all branch on this value. + +Utility codeunits provide shared logic consumed throughout: + +- `DateFormulaManagement` -- validates date formulas are non-negative and + non-empty, checks integer ratios between billing base period and rhythm +- `SubContractsItemManagement` -- enforces item subscription option rules + on sales/purchase lines, prevents billing items and subscription items + from being used outside their intended document types, calculates unit + prices via the pricing interface +- `SubContractsGeneralMgt` -- partner-aware navigation (opens the right + contract card or partner card based on ServicePartner) +- `DimensionMgt`, `ContactManagement`, `CustomerManagement`, + `VendorManagement` -- thin wrappers for partner-specific lookups + +Table extensions on `GLEntry`, `GenJournalLine`, `SourceCodeSetup`, and +`GeneralPostingSetup` add fields needed for contract deferral posting. +Page extensions on role center pages (Business Manager, Order Processor, +etc.) surface subscription billing activity cues. + +`FieldTranslation` (table 8000) stores per-language overrides for any +text field on any table, keyed by table ID + field number + language code ++ source record SystemId. Used for multi-language contract descriptions. + +## Things to know + +- Changing "Default Period Calculation" on setup prompts to bulk-update + ALL existing Subscription Package Lines, Sales Subscription Lines, and + Subscription Lines via `ModifyAll`. This is a database-wide change. +- "Default Billing Base Period" and "Default Billing Rhythm" must both be + set for manual contract line creation to work. Clearing either shows a + warning and blocks manual line creation. +- `SubContractsItemManagement` is a `SingleInstance` codeunit. The + `AllowInsertOfInvoicingItem` flag persists for the session -- callers + must reset it after use to avoid leaking state. +- The `ContractLineType` enum is transitioning: the old "Service + Commitment" value is obsolete (CLEAN26/29), replaced by Item and + G/L Account values. diff --git a/src/Apps/W1/Subscription Billing/App/Billing/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Billing/docs/CLAUDE.md new file mode 100644 index 0000000000..c9acfff3e1 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Billing/docs/CLAUDE.md @@ -0,0 +1,233 @@ +# Billing module + +The Billing subfolder is the execution engine of Subscription Billing. It +turns contract subscription lines into sales invoices, purchase invoices, +sales credit memos, and purchase credit memos. Everything upstream +(subscriptions, contracts, subscription lines) defines *what* should be +billed; this module decides *when* and *how* it becomes a real document. + +## Core tables + +**BillingLine (8061)** is the central transaction record. Each row links a +subscription line to an in-progress document. The `Partner` enum +(Customer/Vendor) drives conditional `TableRelation` throughout the table +-- partner no., contract no., contract line no., and document no./type all +resolve to different tables based on this enum. Key fields: + +- `Billing from` / `Billing to` -- the period this line covers +- `Document Type` / `Document No.` / `Document Line No.` -- the generated + sales/purchase document, populated only after "Create Documents" +- `Update Required` -- set to true by subscription line changes after the + billing proposal was created; blocks document creation until the + proposal is refreshed +- `Correction Document Type` / `Correction Document No.` -- populated on + credit memo billing lines, pointing back to the original posted invoice +- `Billing Template Code` -- links back to the template that generated + this line (empty when created from a contract card) +- `Rebilling` -- flags lines created from usage data rebilling scenarios + +Deletion is constrained: only the last billing line for a given +subscription line can be deleted (sorted by `Billing to` descending), and +lines already linked to a document cannot be deleted at all. On delete, +the subscription line's `Next Billing Date` is reset. + +**BillingLineArchive (8064)** is a near-identical copy of BillingLine, +created when a billing document is posted. The `Document No.` field points +to the *posted* document (Sales Invoice Header, Sales Cr.Memo Header, +etc.) rather than the unposted one. This table is the source of truth for +billing corrections -- credit memos are built by reading archive records +for the original invoice. + +**BillingTemplate (8060)** is the automation configuration record. Each +template defines: + +- `Partner` -- Customer or Vendor (vendor templates cannot be automated) +- `Billing Date Formula` / `Billing to Date Formula` -- date formulas + applied to WorkDate (or Today for background jobs) to derive billing + parameters +- `Filter` -- a Blob storing a RecordRef view filter against + Customer/Vendor Subscription Contract, with default filter fields for + Billing Rhythm, Assigned User, Contract Type, and Salesperson/Purchaser +- `Group by` -- how to group the proposal view (None, Contract, Contract + Partner) +- `Customer Document per` -- how customer billing lines are grouped into + documents (per Contract, per Sell-to Customer, per Bill-to Customer) +- `Automation` -- None or "Create Billing Proposal and Documents" +- `Minutes between runs` / `Batch Recurrent Job Id` -- Job Queue + scheduling fields + +**ContractBillingErrLog (8022)** captures errors during automated billing. +Each record stores the billing template code, error text, and contract +context (subscription, contract no., contract type, assigned user, +salesperson). Linked to billing lines via `Billing Error Log Entry No.`. + +## How billing works + +The workflow has three phases: + +### 1. Create billing proposal + +`BillingProposal.CreateBillingProposal()` iterates contracts matching the +template's filter and partner type. For each contract, it finds +subscription lines where `Next Billing Date <= BillingDate`. Lines are +skipped when: + +- The subscription line has ended (`Next Billing Date > End Date`) +- A billing line with a document already exists for that subscription line +- For usage-based lines, no unbilled usage data exists +- An unposted credit memo exists for the subscription line (the user is + warned; in automated mode, an error log entry is created) + +For each qualifying subscription line, the codeunit calculates the billing +period using the line's `Billing Rhythm` and `Next Billing Date`, creates +a BillingLine record, computes amounts via `UnitPriceAndCostForPeriod`, +then advances `Next Billing Date` on the subscription line. If the billing +period hasn't reached the Billing to Date yet, the process recurses to +create additional billing lines for subsequent periods. + +Each subscription line can appear in a billing proposal only once per +period -- the existence check on BillingLine prevents duplicate billing. + +### 2. Create documents + +`CreateBillingDocuments` (codeunit 8060) runs against the billing lines. +It first validates: only one partner type at a time, no "Update Required" +lines, consistency checks (all billing lines for a subscription line must +be included -- no gaps). It then creates temporary billing lines that +consolidate multiple period lines per subscription line into a single +document line. + +Document grouping depends on the template configuration: + +- **Per contract** -- one document per contract, document type (invoice + vs. credit memo) determined by the net amount sign across the contract +- **Per customer/vendor** -- one document per partner, with description + lines separating each contract's lines within the document +- Currency code also forces new documents (different currencies cannot mix) + +The sign logic is notable: positive amounts produce invoices, negative +amounts produce credit memos. Discount lines invert this logic (negative +discount = invoice, positive discount = credit memo). + +Sales header creation transfers fields from the contract record (bill-to, +ship-to, currency, dimensions) and sets `Recurring Billing = true`. This +flag triggers the `DocumentChangeManagement` codeunit's protection -- +most header and line fields become read-only after creation. + +Invoice line descriptions are configured through `Subscription Contract +Setup` fields (`Contract Invoice Description`, `Contract Invoice Add. Line +1` through `5`), supporting text types like Service Object description, +Subscription Line description, billing period, serial number, customer +reference, and primary attribute. + +At least one "Additional Line" must be configured in Setup for the billing +period; otherwise the description text for the invoice line will be empty. + +### 3. Post + +If `PostDocuments` is true (set from the request page or automated +billing), the created sales documents are posted immediately. For a +single document, `Sales-Post` runs directly. For multiple documents, +`Sales Batch Post Mgt.` is used with error message handling. Purchase +document posting is not automated from this module. + +On posting, `SalesDocuments` and `PurchaseDocuments` codeunits subscribe +to `OnBeforeDeleteAfterPosting` events to move billing lines to +BillingLineArchive with the posted document number, then delete the +original billing lines. They also copy contract reference fields +(Contract No., Contract Line No.) onto the posted document lines via +`OnAfterInitFromSalesLine` / `OnAfterInitFromPurchLine` events. + +## Billing correction + +`BillingCorrection` (codeunit 8061) handles credit memos. It subscribes +to `Copy Document Mgt.` events (`OnAfterInsertToSalesLine`, +`OnAfterInsertToPurchLine`) that fire when a credit memo is created from +a posted invoice using "Create Corrective Credit Memo". + +The correction process: + +1. Finds the BillingLineArchive for the original invoice line +2. Checks that no newer invoices exist for the subscription line (credit + memos must be chronological -- most recent invoice first) +3. Checks that no other unposted document exists for the same contract + line +4. Creates a new BillingLine with negated amount, referencing the original + invoice as the correction document +5. Resets the subscription line's `Next Billing Date` to the start of the + credited period + +Copying a recurring billing document for any purpose other than creating +a credit memo from a posted invoice raises an error. + +## Automation + +The `AutoContractBilling` codeunit (8014) is a Job Queue entry handler. It +receives the BillingTemplate record ID from the Job Queue Entry, calls +`BillingTemplate.BillContractsAutomatically()`, which: + +1. Calculates billing dates using `Today()` (not WorkDate) +2. Creates the billing proposal +3. Creates documents (with the customer grouping from the template) + +The `SubBillingBackgroundJobs` codeunit manages the Job Queue lifecycle: +creating, updating, or removing entries as the template's `Automation` and +`Minutes between runs` fields change. + +Vendor contract invoicing is NOT automated -- `BillingTemplate.Partner` +validation enforces `TestField(Automation, None)` when Partner is Vendor. + +Errors during automated billing are caught and written to +`ContractBillingErrLog` rather than raising user-facing errors. This +includes credit memo conflicts, consistency errors, and unit of measure +mismatches. + +## Document change management + +`DocumentChangeManagement` (codeunit 8074) is a SingleInstance codeunit +that subscribes to `OnBeforeValidateEvent` on dozens of fields across +Sales Header, Sales Line, Purchase Header, and Purchase Line. When the +document has `Recurring Billing = true`, it blocks changes to customer/ +vendor identity, addresses, currency, posting groups, dimensions, +quantities, prices, and discount fields. The intent is that contract +billing documents are fully controlled by the contract data -- manual +edits to the generated documents would create inconsistencies. + +The codeunit exposes `SetSkipContractSalesHeaderModifyCheck` and +`SetSkipContractPurchaseHeaderModifyCheck` for internal callers that need +to modify these protected fields programmatically (e.g., during document +creation itself or when creating credit memos via Copy Document). + +## Table extensions on posted documents + +The module adds `Subscription Contract No.` and `Subscription Contract +Line No.` fields to four posted document line tables +(SalesInvoiceLine, SalesCrMemoLine, PurchInvLine, PurchCrMemoLine) and +header tables (SalesInvoiceHeader, SalesCrMemoHeader, PurchInvHeader, +PurchCrMemoHdr). The header extensions add the `Recurring Billing` +boolean. These fields establish traceability from posted documents back to +contracts. + +The `UserSetup` table extension adds an `Auto Contract Billing Allowed` +field that gates who can configure automated billing templates and +clear/delete billing proposals when automation templates exist. + +## Page structure + +The **RecurringBilling** page (8067) is the central hub. It is a Worksheet +page backed by a temporary BillingLine table, displayed as a collapsible +tree (grouped by contract or partner). The workflow is: + +1. Select a Billing Template (sets partner filter, date formulas, grouping) +2. Set Billing Date and optional Billing to Date +3. Run "Create Billing Proposal" -- populates billing lines +4. Review and optionally adjust (Change Billing To Date, Delete lines) +5. Run "Create Documents" -- generates sales/purchase documents +6. Optionally navigate to the created documents and post + +Other pages provide list/subpage views of billing lines +(BillingLines, BillingLinesList) and archived billing lines +(ArchivedBillingLines, ArchivedBillingLinesList), the billing template +list (BillingTemplates), error log (ContractBillingErrLog), and document +creation request pages (CreateCustomerBillingDocs, CreateVendorBillingDocs, +CreateBillingDocument). diff --git a/src/Apps/W1/Subscription Billing/App/Billing/docs/business-logic.md b/src/Apps/W1/Subscription Billing/App/Billing/docs/business-logic.md new file mode 100644 index 0000000000..a160999bf8 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Billing/docs/business-logic.md @@ -0,0 +1,364 @@ +# Business logic + +This document covers the key business processes implemented in the Billing +module's codeunits. + +## Billing proposal creation + +**Codeunit: `BillingProposal.Codeunit.al` (8062)** + +The billing proposal is the first step in the billing pipeline. It scans +contract subscription lines and creates BillingLine records for periods +that are due for invoicing. + +### Entry point + +`CreateBillingProposal(BillingTemplateCode, BillingDate, BillingToDate)` +is the main entry point, called from the RecurringBilling page or from +`BillingTemplate.BillContractsAutomatically()` for automated runs. + +### Flow + +The process starts by deleting any billing lines marked `Update Required` +for the template (these are stale lines where the underlying subscription +line changed after the proposal was created). It then reads the template's +stored filter and applies it to Customer or Vendor Subscription Contract +records. + +For each matching contract, `ProcessContractServiceCommitments` finds +subscription lines where `Next Billing Date <= BillingDate` and the +billing rhythm matches the template's filter. Each qualifying subscription +line enters `ProcessServiceCommitment`, which applies a series of skip +checks: + +- **Ended subscription line** -- `Next Billing Date > End Date` means the + line has been fully billed +- **Existing billing line with a document** -- the subscription line + already has an unposted invoice or credit memo; skip it (and mark the + credit memo for display if running interactively) +- **Usage-based billing** -- the subscription line is usage-based but no + unbilled UsageDataBilling records exist; skip it +- **Extension point** -- `OnCheckSkipSubscriptionLineOnElse` lets + subscribers add custom skip logic + +### Period calculation + +`CalculateBillingPeriod` determines the billing window: + +- `BillingPeriodStart` is the subscription line's `Next Billing Date` +- If a `BillingToDate` was specified, that becomes the period end +- For usage-based lines, the period is derived from the charge start/end + dates in UsageDataBilling records +- Otherwise, the period end is calculated by advancing through billing + rhythm intervals until the end date passes `BillingDate` + +### Billing line creation (recursive) + +`UpdateBillingLine` is the core logic and it recurses. For a single +subscription line, it may create multiple billing lines if the billing +period spans several rhythm intervals. For example, a monthly billing +rhythm with a 3-month billing-to date produces three billing lines. + +Each iteration: + +1. Sets `Billing from` to the current start date +2. Calculates `Billing to` as the lesser of the next rhythm boundary and + the period end +3. Populates line fields from the subscription line and contract +4. Calculates unit amounts via `CalculateBillingLineUnitAmountsAndServiceAmount` +5. Inserts the billing line +6. Advances `Next Billing Date` on the subscription line +7. If the period end hasn't been reached and the subscription line hasn't + ended, recurses for the next interval + +### Amount calculation + +For standard (non-usage) lines, `ServiceCommitment.UnitPriceAndCostForPeriod` +prorates the subscription line's price/cost for the billing period relative +to the billing rhythm. The result is rounded per the currency's precision +settings. + +For usage-based lines, amounts come from aggregating `UsageDataBilling` +records. The billing line's amount is the sum of usage amounts (or cost +amounts for vendor lines), with the subscription line's discount +percentage applied on top. + +### Harmonized billing + +When a customer contract type has harmonized billing enabled, the billing +to date is capped at the contract's `Next Billing To` date. This aligns +billing periods across all subscription lines in the contract so they can +be invoiced together. + +## Document creation + +**Codeunit: `CreateBillingDocuments.Codeunit.al` (8060)** + +This codeunit transforms billing lines into actual sales or purchase +documents. It runs as a `TableNo = "Billing Line"` codeunit, receiving +filtered billing lines. + +### Validation + +Before creating documents, the codeunit runs several checks: + +- **Single partner type** -- all billing lines must be for the same + partner type (Customer or Vendor) +- **No "Update Required" lines** -- forces the user to refresh the + proposal first +- **Consistency check** -- for each subscription line entry, the filtered + billing line count must match the total count; partial selections that + create gaps in the billing period are rejected +- **Item Unit of Measure** -- verifies that the subscription's unit of + measure exists on the invoicing item + +In automated mode, validation failures are logged to +`ContractBillingErrLog` instead of raising errors. The affected billing +lines get the error log entry number stamped on them. + +### Temporary billing line consolidation + +`CreateTempBillingLines` consolidates multiple billing lines for the same +subscription line into a single temporary line for document creation. +This means if a subscription line has three monthly billing lines, they +produce one document line with aggregated amounts and a combined billing +period (earliest `Billing from` to latest `Billing to`). + +The temporary line also determines the document type: positive net amount += Invoice, negative = Credit Memo. Discount lines invert this logic. + +### Document grouping strategies + +The grouping depends on the template's `Customer Document per` (or Vendor +equivalent): + +**Per contract** (`CreateSalesDocumentsPerContract` / +`CreatePurchaseDocumentsPerContract`): +- One document per contract +- Document type (Invoice vs Credit Memo) is determined by net amount + across all billing lines for that contract +- Contract description lines are inserted before the billing lines + +**Per customer** (`CreateSalesDocumentsPerCustomer`): +- One document per unique combination of Partner No. + Detail Overview + + Currency Code +- When a document spans multiple contracts, the posting description + changes to "Multiple Customer Subscription Contracts" +- Address information from the contract can be inserted in collective + invoices (`Contractor Name in coll. Inv.`, `Recipient Name in coll. + Inv.` settings on the contract) + +**Per vendor** (`CreatePurchaseDocumentsPerVendor`): +- One document per unique combination of Partner No. + Currency Code + +### Invoice text generation + +Sales lines get their description from a configurable pipeline. The +`Subscription Contract Setup` defines what text appears in the main +invoice line description and up to five additional description lines. Each +slot can be set to show: Service Object description, Subscription Line +description, Customer Reference, Serial No., Billing Period, Primary +Attribute, or blank. The `OnGetAdditionalLineTextElseCase` event allows +adding custom text types. + +Purchase lines use a simpler scheme: the subscription line description +plus a billing period description line. + +### Posting + +If `PostDocuments` is enabled, sales documents are posted after creation. +A single document posts directly via `Sales-Post`. Multiple documents use +`Sales Batch Post Mgt.` with error message management so failures don't +block the entire batch. + +Purchase document posting is not supported from this codeunit. + +## Sales and purchase document lifecycle + +**Codeunits: `SalesDocuments.Codeunit.al` (8063), +`PurchaseDocuments.Codeunit.al` (8066)** + +These SingleInstance codeunits manage the relationship between billing +lines and sales/purchase documents throughout their lifecycle. + +### On document/line deletion + +When a sales or purchase document is deleted, the codeunits determine +the cleanup strategy based on context: + +- **Credit memo with Applies-to Doc. No.** -- the billing lines are + deleted and the subscription line's `Next Billing Date` is reset (the + credit memo was correcting a specific invoice) +- **Document created from contract (Billing Template Code = '')** -- the + billing lines are deleted and Next Billing Date is reset (this was a + direct contract invoice that should be fully unwound) +- **Document created from Recurring Billing page** -- the document + reference is cleared from the billing lines but the lines themselves + remain (the user can regenerate documents from the proposal) + +### On document posting + +When a sales/purchase document is posted, the billing lines are moved to +BillingLineArchive with the posted document number, then deleted. The +posted document lines receive the contract reference fields +(`Subscription Contract No.`, `Subscription Contract Line No.`) from the +billing lines. + +The `Recurring Billing` flag is also transferred to Customer/Vendor +Ledger Entries via `OnBeforeCustLedgEntryInsert` / +`OnBeforeVendLedgEntryInsert` subscribers. + +### Subscription item handling (SalesDocuments only) + +`SalesDocuments` has extensive logic for "Subscription Items" -- items +whose value comes entirely from subscription billing rather than the +sales order. When posting sales orders containing these items: + +- Unit prices and discount amounts are zeroed before posting (so no + revenue is recognized on the order) +- Quantities are restored after posting to preserve the order line state +- `Qty. to Invoice` is always forced to zero +- Posted invoice headers/lines are suppressed if *all* lines are + subscription items +- Subscriptions (Service Objects) are created from shipped item lines with + associated subscription lines + +## Billing correction + +**Codeunit: `BillingCorrection.Codeunit.al` (8061)** + +Credit memos for subscription billing are created exclusively through the +"Create Corrective Credit Memo" function on posted invoices, which uses +Copy Document Management internally. + +### Chronological enforcement + +The codeunit enforces strict chronological ordering of corrections. Before +creating a credit memo billing line, it checks: + +``` +if ServiceCommitment."Next Billing Date" - 1 > RecurringBillingToDate then + Error('...cancel the newer invoices first.'); +``` + +This means you must credit the most recent invoice first, then work +backwards. The reason: crediting an older invoice without crediting newer +ones would leave a gap in the billing timeline, making Next Billing Date +inconsistent. + +### What the correction does + +1. Reads BillingLineArchive records for the original posted invoice line +2. Creates new BillingLine records with negated amounts +3. Sets `Correction Document Type` = Invoice and `Correction Document No.` + to the posted invoice number +4. Resets the subscription line's `Next Billing Date` to `Billing from - 1` + (the day before the credited period started) +5. For usage-based lines, duplicates the UsageDataBilling records as credit + memo entries + +### Copy restrictions + +The codeunit blocks copying recurring billing documents for any purpose +other than creating a credit memo from a posted invoice. Attempting to +copy to an invoice or from a non-posted document raises the error +"Copying documents with a link to a contract is not allowed." + +## Billing automation + +**Codeunits: `AutoContractBilling.Codeunit.al` (8014), +`SubBillingBackgroundJobs.Codeunit.al` (8034)** + +Automated billing uses the BC Job Queue. The flow is: + +1. A `BillingTemplate` has `Automation` set to "Create Billing Proposal + and Documents" +2. `SubBillingBackgroundJobs.ScheduleAutomatedBillingJob` creates a + recurring Job Queue Entry running codeunit 8014 with the template's + RecordId +3. At the scheduled interval, the Job Queue runs + `AutoContractBilling.OnRun`, which loads the template and calls + `BillContractsAutomatically` +4. This method creates the billing proposal, then creates documents + (without posting, without request page interaction) + +Key constraints: + +- Only Customer templates can be automated (Vendor templates raise an + error on `Automation` validation) +- The user must have `Auto Contract Billing Allowed` in User Setup to + modify automation settings, clear proposals, or delete documents when + any automation template exists +- Errors during automated runs go to `ContractBillingErrLog` rather than + raising errors +- Date calculations use `Today()` instead of `WorkDate()` for background + processes + +## End-to-end flow + +```mermaid +flowchart TD + A[Start: Create Billing Proposal] --> B{Template has filter?} + B -->|Yes| C[Apply filter to contracts] + B -->|No| C2[All contracts for partner type] + C --> D[For each contract] + C2 --> D + D --> E[Find subscription lines where\nNext Billing Date <= Billing Date] + E --> F{Subscription line\nqualifies?} + F -->|Ended| G[Skip] + F -->|Existing document| G + F -->|Usage-based, no data| G + F -->|Qualifies| H[Calculate billing period] + H --> I[Create BillingLine\nCalculate amounts] + I --> J[Advance Next Billing Date] + J --> K{Period end reached?} + K -->|No| I + K -->|Yes| L[Next subscription line] + L --> E + + M[Start: Create Documents] --> N{Validation passes?} + N -->|No| O[Error or log] + N -->|Yes| P[Consolidate billing lines\ninto temp lines] + P --> Q{Grouping mode?} + Q -->|Per contract| R[One document per contract] + Q -->|Per customer/vendor| S[One document per partner+currency] + R --> T[Create header from contract] + S --> U[Create header for partner] + T --> V[Insert document lines\nwith invoice text] + U --> V + V --> W{Post documents?} + W -->|Yes| X[Post via Sales-Post\nor Batch Post Mgt] + W -->|No| Y[Documents ready for review] + + X --> Z[BillingLines move to\nBillingLineArchive] + Z --> AA[Contract refs copied to\nposted document lines] + + BB[Start: Credit Memo] --> CC{Newer invoice exists?} + CC -->|Yes| DD[Error: cancel newer first] + CC -->|No| EE[Create billing line\nfrom archive with negated amount] + EE --> FF[Reset Next Billing Date\nto Billing from - 1] + FF --> GG[Post credit memo] + GG --> HH[Credit billing lines\nmove to archive] +``` + +## Document change management + +**Codeunit: `DocumentChangeManagement.Codeunit.al` (8074)** + +This codeunit is the guard that prevents manual edits to billing-generated +documents. It subscribes to `OnBeforeValidateEvent` on most fields of +Sales Header, Sales Line, Purchase Header, and Purchase Line. When the +document is flagged `Recurring Billing = true`, any attempt to change +protected fields raises an error. + +Protected fields include: customer/vendor identity and addresses, currency +code, posting groups, VAT settings, prices including VAT, dimensions, +invoice discount, and on lines: type, no., quantity, unit price/cost, unit +of measure, amounts, discounts, and billing period dates. + +The `OnBeforeModifyEvent` subscribers provide a second layer of protection +by comparing the full record state before and after modification. + +The `OnBeforePreventChangeOnDocumentHeaderOrLine` integration event +provides an escape hatch for extensions that need to allow specific field +changes. diff --git a/src/Apps/W1/Subscription Billing/App/Billing/docs/extensibility.md b/src/Apps/W1/Subscription Billing/App/Billing/docs/extensibility.md new file mode 100644 index 0000000000..d6063f17e5 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Billing/docs/extensibility.md @@ -0,0 +1,259 @@ +# Extensibility + +The Billing module exposes integration events across its codeunits for +customization. This document groups them by what you're trying to achieve. + +## Customizing billing proposal creation + +**Codeunit: `BillingProposal.Codeunit.al` (8062) -- 12 events** + +### Controlling which subscription lines enter the proposal + +`OnBeforeProcessContractSubscriptionLines` fires after the subscription +line record is filtered by contract and next billing date but before the +loop begins. Subscribe to add extra filters or modify the billing date +parameters for a specific contract. + +`OnCheckSkipSubscriptionLineOnElse` fires when a subscription line doesn't +match any of the built-in skip conditions (ended, existing document, +usage-based). Subscribe to implement custom skip logic -- for example, +skipping lines that have a specific custom field value. Set +`SkipSubscriptionLine := true` to exclude the line. + +### Modifying billing lines before they're saved + +`OnBeforeInsertBillingLineUpdateBillingLine` fires after the billing line +fields are populated from the subscription line and amounts are calculated, +but before `Insert`. This is the best place to set custom fields on the +billing line or adjust amounts. + +`OnAfterUpdateBillingLineFromSubscriptionLine` fires after standard +subscription line fields are copied to the billing line but before amount +calculation. Subscribe to override field mappings or add custom field +transfers. + +### Controlling amount calculation + +`OnBeforeCalculateBillingLineUnitAmountsAndServiceAmount` fires before +the standard amount calculation. Set `IsHandled := true` to completely +replace the built-in price/cost calculation -- useful if you have custom +pricing logic. + +### Controlling the billing period end date + +`OnAfterCalculateNextBillingToDateForSubscriptionLine` fires after the +next billing-to date is calculated. Modify `NextBillingToDate` to change +the billing period end -- for example, to align with fiscal periods +instead of calendar months. + +### Modifying the billing template filter + +`OnCreateBillingProposalBeforeApplyFilterToContract` fires before the +template's filter is applied to the contract table. You can modify +`FilterText` to add or override filter criteria programmatically, or +adjust the billing dates. + +### Overriding proposal creation from a contract card + +`OnBeforeCreateBillingProposalFromContract` fires when billing is +initiated from a contract card rather than the Recurring Billing page. +Set `IsHandled := true` to replace the standard Create Billing Document +dialog with custom logic. + +## Customizing document creation + +**Codeunit: `CreateBillingDocuments.Codeunit.al` (8060) -- 27 events** + +This codeunit has the most events because document creation involves many +steps. The most useful ones are grouped by purpose. + +### Controlling the overall process + +`OnBeforeCreateBillingDocuments` fires at the very start, before +validation. Subscribe to modify the billing line filters or perform +pre-processing. + +`OnBeforeProcessBillingLines` fires after validation passes, with all +process parameters available as var parameters: `DocumentDate`, +`PostingDate`, `CustomerRecBillingGrouping`, +`VendorRecBillingGrouping`, `PostDocuments`. This is the best place to +override document creation settings programmatically. + +`OnAfterProcessBillingLines` fires after all documents are created. +Subscribe for post-processing, notifications, or logging. + +### Customizing document headers + +`OnAfterCreateSalesHeaderFromContract` fires after a sales header is +created from a customer contract (per-contract grouping). The +`SalesHeader` is a var parameter -- modify it to set custom header +fields before lines are inserted. + +`OnAfterCreateSalesHeaderForCustomerNo` fires after a sales header is +created for a customer (per-customer grouping). + +### Customizing document lines + +`OnBeforeInsertSalesLineFromContractLine` fires after the sales line is +fully populated (item, quantity, price, dimensions, description) but +before `Insert`. This is the primary hook for modifying invoice line +content -- adding custom fields, changing descriptions, or adjusting +amounts. + +`OnBeforeInsertPurchaseLineFromContractLine` is the equivalent for +purchase lines. + +`OnAfterInsertSalesLineFromBillingLine` and +`OnAfterInsertPurchaseLineFromBillingLine` fire after the line is +inserted and billing line references are updated. Subscribe for +post-insert processing like creating related records. + +### Customizing invoice text + +`OnGetAdditionalLineTextElseCase` fires when the configured +`ContractInvoiceTextType` doesn't match any built-in option. Subscribe to +implement custom invoice text types beyond the standard set (Service +Object, Service Commitment, Customer Reference, Serial No., Billing +Period, Primary Attribute). + +`OnAfterGetAdditionalLineText` fires after the description text is +determined for any text type. Modify `DescriptionText` to append, +prepend, or replace the standard text. + +### Customizing contract description lines + +`OnBeforeInsertContractDescriptionSalesLines` fires before the contract +header description lines are inserted on collective invoices. Set +`IsHandled := true` to completely replace the standard description block. + +`OnAfterInsertContractDescriptionSalesLines` fires after the description +lines are inserted. Subscribe to add extra descriptive lines. + +`OnBeforeInsertAddressInfoForCollectiveInvoice` and +`OnAfterInsertAddressInfoForCollectiveInvoice` control the address +information block inserted in collective invoices for each contract. + +### Controlling document grouping + +`OnAfterIsNewSalesHeaderNeeded` fires during per-customer document +creation to determine if a new sales header should be created. +Modify `CreateNewSalesHeader` to force or prevent header breaks beyond +the standard logic (partner no. + detail overview + currency code). + +`OnAfterIsNewHeaderNeededPerContract` is the equivalent for per-contract +document creation. + +### Controlling temporary billing line consolidation + +`OnBeforeInsertTempBillingLine` fires before the first temporary billing +line for a subscription line is inserted. Modify the temp line to set +custom fields. + +`OnCreateTempBillingLinesBeforeSaveTempBillingLine` fires each time a +billing line is accumulated into the temp line (after amounts are summed). +Modify the temp line for custom consolidation logic. + +### Controlling sort order before document creation + +`OnCreateSalesDocumentsPerContractBeforeTempBillingLineFindSet`, +`OnCreateSalesDocumentsPerCustomerBeforeTempBillingLineFindSet`, +`OnCreatePurchaseDocumentsPerContractBeforeTempBillingLineFindSet`, and +`OnCreatePurchaseDocumentsPerVendorBeforeTempBillingLineFindSet` all fire +after the temp billing lines are keyed but before iteration. Modify the +temp line filters or sort order to control document creation sequence. + +## Customizing sales document handling + +**Codeunit: `SalesDocuments.Codeunit.al` (8063) -- 15 events** + +Most events in this codeunit relate to subscription creation from sales +orders rather than billing. The billing-relevant ones are: + +### Controlling billing line archival + +`OnAfterInsertBillingLineArchiveOnMoveBillingLineToBillingLineArchive` +fires after each billing line is copied to the archive during posting. +Subscribe to set custom fields on the archive record. + +`OnBeforeMoveBillingLineToBillingLineArchiveForPostingPreview` fires +during posting preview. Set `IsHandled := true` to skip archival in +preview mode if your custom logic doesn't support it. + +### Controlling subscription item invoicing behavior + +`OnCheckResetValueForSubscriptionItems` fires when determining whether a +sales line's amounts should be zeroed during posting (because it's a +subscription item). Set `IsHandled := true` and control +`ResetValueForSubscriptionItems` to change which lines get this treatment. + +`OnBeforeClearQtyToInvoiceOnForSubscriptionItem` fires before Qty. to +Invoice is cleared on subscription items. Set `IsHandled := true` to +allow invoicing of subscription items through standard sales flow. + +`OnAfterSalesLineShouldSkipInvoicing` fires after the standard check for +whether a sales line should skip invoicing. Modify `Result` to override +the decision. + +### Controlling subscription creation from sales + +`OnBeforeCreateSubscriptionHeaderFromSalesLine` fires before a +Subscription Header is created from a shipped sales line. Set +`IsHandled := true` to suppress automatic subscription creation or +replace it with custom logic. + +`OnCreateSubscriptionHeaderFromSalesLineBeforeInsertSubscriptionHeader` +and the `AfterInsert` variant let you modify the subscription header +before/after it's saved. + +`OnCreateSubscriptionHeaderFromSalesLineBeforeInsertSubscriptionLine` +and the `AfterInsert` variant let you modify subscription lines +before/after they're created from sales subscription lines. + +## Customizing purchase document handling + +**Codeunit: `PurchaseDocuments.Codeunit.al` (8066) -- 1 event** + +`OnAfterInsertBillingLineArchiveOnMoveBillingLineToBillingLineArchive` +fires after each billing line is archived during purchase document +posting. This mirrors the sales equivalent and is useful for the same +custom field transfer scenarios. + +## Customizing billing corrections + +**Codeunit: `BillingCorrection.Codeunit.al` (8061) -- 3 events** + +`OnBeforeCreateBillingLineFromBillingLineArchiveAfterInsertToSalesLine` +fires before the correction logic begins for a sales credit memo line. +Set `IsHandled := true` to bypass the standard correction process +entirely -- useful if you need custom credit memo handling for specific +contract types. + +`OnAfterCreateBillingLineFromBillingLineArchive` fires after the +correction billing line is created from the archive. The `RRef` +(RecordRef) parameter gives you access to the target credit memo line. +Subscribe to create additional records or modify the correction line. + +`OnBeforeUpdateNextBillingDateInCreateBillingLineFromBillingLineArchive` +fires before the subscription line's Next Billing Date is reset. Modify +the subscription line record to override the date calculation -- for +example, to preserve the current Next Billing Date in rebilling +scenarios. + +## Customizing document change protection + +**Codeunit: `DocumentChangeManagement.Codeunit.al` (8074) -- 1 event** + +`OnBeforePreventChangeOnDocumentHeaderOrLine` fires before any field +change is blocked on a recurring billing document. Set +`IsHandled := true` to allow the change through. This is the primary +escape hatch for extensions that need to modify billing document fields +that are normally locked -- for example, adding a custom field to the +sales header that should be editable even on contract invoices. + +## Recurring Billing page + +**Page: `RecurringBilling.Page.al` (8067) -- 1 event** + +`OnAfterApplyBillingTemplateFilter` fires after the billing template's +settings are applied to the page filters. Subscribe to add extra page +filters based on custom template fields or user context. diff --git a/src/Apps/W1/Subscription Billing/App/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/CLAUDE.md new file mode 100644 index 0000000000..82d9af20f7 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/CLAUDE.md @@ -0,0 +1,61 @@ +# Subscription Billing + +Subscription Billing enables selling and managing recurring subscriptions in Business Central. It decouples the physical service being delivered (a Subscription) from the billing agreement (a Contract), allowing flexible combinations of subscription-based, usage-based, and hybrid pricing models. The app integrates deeply with BC sales documents, posting, and G/L, acting as a parallel invoicing engine that creates and posts standard BC invoices/credit memos from contract billing lines. + +## Quick reference + +- **ID range**: 8000-8113 +- **Dependencies**: Power BI Report embeddings for Dynamics 365 Business Central + +## How it works + +The core abstraction separates three concepts that are often conflated. A **Subscription** (table `Subscription Header`, internally still called "Service Object") represents the physical thing being delivered -- a software license, a maintenance agreement, a piece of equipment. A **Subscription Line** (table `Subscription Line`, internally "Service Commitment") represents a single billing rule attached to that subscription -- its price, billing rhythm, term dates, and contract assignment. A **Contract** (either `Customer Subscription Contract` or `Vendor Subscription Contract`) is the billing vehicle that groups subscription lines for invoicing. One subscription can have lines billed through different contracts, and one contract can contain lines from many subscriptions. + +The setup hierarchy flows from templates down to actual billing. A **Subscription Package Line Template** defines billing defaults (invoicing method, calculation base, billing period, usage-based pricing). A **Subscription Package** groups multiple package lines. Packages are assigned to Items, and when an item is sold via a sales document, **Sales Subscription Lines** are auto-created from the item's packages. When the sales order is posted, these become actual Subscription Lines on a Subscription, linked to contracts. + +The **Partner enum** (`Service Partner`: Customer or Vendor) is the fundamental branching mechanism. Throughout the app, tables use conditional `TableRelation` based on Partner to point billing lines, contract lines, and usage data at either customer or vendor records. This is not inheritance -- it is conditional logic applied consistently across every table that touches both sides. + +Billing follows a pipeline: contract lines feed a **Billing Proposal** (codeunit `Billing Proposal`) which creates `Billing Line` records based on "Next Billing Date". These billing lines are then turned into actual sales/purchase invoices by `Create Billing Documents`. The process can run manually, semi-automatically via billing templates, or fully automated through `Auto Contract Billing` using job queue entries. The `Billing Template` controls grouping (per contract, per customer/vendor), date formulas, and automation level. + +**Deferrals** are optional per contract type and per subscription line template. When enabled, posting a contract invoice credits a deferral account instead of revenue. A monthly report (`Contract Deferrals Release`) then releases the deferred amounts to the actual revenue/cost accounts based on a "Post Until Date". **Usage-based billing** is an alternative model where metered consumption data flows in from external suppliers through a multi-stage pipeline (Supplier -> Import -> Generic Import -> Usage Data Billing) and feeds into the normal billing proposal. **Price updates** use template-driven methods (percentage of calculation base, percentage of price, or recent item prices) through a `Contract Price Update` interface, and create **Planned Subscription Lines** when changes cannot take effect immediately. **Contract renewals** generate sales quotes from expiring contract lines, and posting those quotes extends the subscription terms. + +## Structure + +- Base/ -- Setup tables (`Subscription Contract Setup`, `Subscription Contract Type`), enums (`Service Partner`, `Contract Line Type`), utility codeunits +- Service Objects/ -- `Subscription Header` table, item integration, attribute factboxes +- Service Commitments/ -- `Subscription Line`, `Subscription Package`, `Subscription Package Line`, `Sub. Package Line Template`, archives +- Sales Service Commitments/ -- `Sales Subscription Line` table, posting integration with sales documents, sales report extensions +- Customer Contracts/ -- `Customer Subscription Contract`, `Cust. Sub. Contract Line`, extend-contract workflows, dimension management +- Vendor Contracts/ -- `Vendor Subscription Contract`, `Vend. Sub. Contract Line` +- Billing/ -- `Billing Line`, `Billing Template`, `Billing Proposal` codeunit, `Create Billing Documents`, `Auto Contract Billing`, correction logic +- Deferrals/ -- `Cust. Sub. Contract Deferral`, `Vend. Sub. Contract Deferral`, release report, analysis reports +- Contract Price Update/ -- `Price Update Template`, `Sub. Contr. Price Update Line`, `Contract Price Update` interface, three method implementations +- Contract Renewal/ -- `Sub. Contract Renewal Line`, `Planned Subscription Line`, renewal codeunits, sales quote generation +- Usage Based Billing/ -- `Usage Data Supplier`, `Usage Data Import`, `Usage Data Billing`, generic connector processing, `Usage Data Processing` interface +- Import/ -- Creating subscriptions and contract lines from imported/unassigned subscription lines +- ContractAnalysis/ -- `Sub. Contr. Analysis Entry` for reporting +- Overdue Service Commitments/ -- Query and page for overdue subscription lines +- APIs/ -- API pages for external integration +- Power BI/ -- Embedded Power BI reports + +## Documentation + +- [docs/data-model.md](docs/data-model.md) -- How tables relate across subscriptions, contracts, and billing +- [docs/business-logic.md](docs/business-logic.md) -- Key processes: billing, price updates, renewals, usage data +- [docs/extensibility.md](docs/extensibility.md) -- 320 integration events organized by customization goal +- [docs/patterns.md](docs/patterns.md) -- Partner-polymorphism, conditional FKs, template hierarchy + +## Things to know + +- The code still uses "Service Object" and "Service Commitment" in many table and codeunit names, while the official Microsoft docs now use "Subscription" and "Subscription Line". The field captions have been updated but the object names lag behind (e.g., `LookupPageId = "Service Objects"` on `Subscription Header`). +- The `Service Partner` enum (Customer/Vendor) drives conditional `TableRelation` throughout -- `Billing Line`, `Usage Data Billing`, contract line references, and price update lines all switch their foreign keys based on this enum rather than using separate tables. +- A Subscription is decoupled from contracts -- one Subscription can have lines billed through multiple contracts, and a single contract can aggregate lines from many different Subscriptions. +- Subscription items auto-show as "Invoiced on Shipment" even though no sales invoice exists at ship time -- invoicing happens later through the contract billing pipeline, not through the sales document. +- Usage-based billing requires contract invoicing (`Invoicing via` = Contract) and cannot have discounts. It flows through a multi-stage pipeline: Supplier -> Import -> Blob -> Generic Import -> Usage Data Billing -> Billing Line. +- Vendor contract invoicing is NOT automated -- the `Billing Template.Automation` field enforces `Partner = Customer`, so only customer contracts support job-queue-driven billing. +- When a price update cannot take effect immediately (because the update date is after the next billing date, or an unposted document exists), the system creates a `Planned Subscription Line` that stores the future values and activates when the current period ends. +- Deferrals are controlled at three levels: the `Subscription Contract Type` sets the default, the `Sub. Package Line Template` can override it, and the `Create Contract Deferrals` enum (`No`, `Contract-dependent`) determines the final behavior per subscription line. +- The `Billing Template` controls automation level: `None` is manual, `Create Billing Proposal and Documents` is fully automated via job queue. There is no "partial" automation -- it is all or nothing per template. +- Harmonized billing aligns all subscription lines on a customer contract to a common billing date. It is enabled per `Subscription Contract Type` and only applies to customer contracts. +- Contract lines use a "Closed" boolean flag for soft-deletion. Closed lines appear on a separate "Closed Lines" FastTab rather than being deleted, preserving the audit trail and preventing re-billing. +- The `Billing Line."Update Required"` flag detects stale proposals -- when contract data changes after billing lines were created, this flag forces regeneration before documents can be created. diff --git a/src/Apps/W1/Subscription Billing/App/Contract Price Update/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Contract Price Update/docs/CLAUDE.md new file mode 100644 index 0000000000..7772d83f94 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Contract Price Update/docs/CLAUDE.md @@ -0,0 +1,70 @@ +# Contract price update + +Contract price updates let you adjust subscription prices in bulk using +configurable templates. The system supports both customer and vendor +subscription lines. + +## Templates and methods + +**PriceUpdateTemplate (8003)** stores the configuration: which partner +(customer or vendor), filter criteria for contracts/subscriptions/lines, +the update method, percentage value, effective date formula, and price +binding period. Filters are stored as BLOBs containing serialized +RecordRef views, editable through a FilterPageBuilder UI. + +Three price update methods implement the `ContractPriceUpdate` interface +(`ContractPriceUpdate.Interface.al`): + +- **CalculationBaseByPerc** -- increases the Calculation Base Amount by the + template percentage, then recalculates Price from the new base. The + percentage cannot be negative. +- **PriceByPercent** -- increases the Price directly by the template + percentage, leaving Calculation Base unchanged. +- **RecentItemPrice** -- fetches the current item sales price (customer) or + unit cost (vendor) and uses that as the new Calculation Base. The + `Update Value %` must be zero since the price comes from item pricing, + not a percentage. + +## Proposal workflow + +The process follows four steps: + +1. **Select template** on the Contract Price Update page +2. **Create proposal** -- `PriceUpdateManagement.CreatePriceUpdateProposal` + applies default filters (excludes usage-based, closed, and already-planned + lines; respects `Next Price Update` date and `Exclude from Price Update` + flag), then runs the template's interface to generate + `SubContrPriceUpdateLine` records +3. **Review** -- the proposal page shows old vs new pricing, grouped by + contract or contract partner. Lines where the new price would be zero or + negative are skipped with a notification. +4. **Perform update** -- `PriceUpdateManagement.PerformPriceUpdate` runs + `ProcessPriceUpdate` (codeunit 8013) per line, wrapped in + `Codeunit.Run` for error isolation + +## Immediate vs deferred updates + +`ProcessPriceUpdate` checks whether the update can take effect immediately. +If any of these conditions are true, it creates a **Planned Subscription +Line** instead of updating the Subscription Line directly: + +- The `Perform Update On` date is after the line's `Next Billing Date` +- An unposted sales/purchase document exists for the line +- The `Next Price Update` date has not yet been reached by `Next Billing Date` + +Planned Subscription Lines activate automatically when the current billing +period is fully invoiced (triggered by `PostSubContractRenewal` subscribers +on sales/purchase posting events). + +## Price binding period + +The template's `Price Binding Period` date formula sets the `Next Price +Update` date on the Subscription Line after the update executes. This +prevents another price update from affecting the line before that date. + +## Credit memo handling + +When a credit memo is posted for a previously invoiced billing period, +`PostSubContractRenewal.ProcessPlannedServCommsForPostedSalesCreditMemo` +restores the Subscription Line to its archived state and re-creates a +Planned Subscription Line so the price update can re-activate later. diff --git a/src/Apps/W1/Subscription Billing/App/Contract Renewal/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Contract Renewal/docs/CLAUDE.md new file mode 100644 index 0000000000..21bdfff832 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Contract Renewal/docs/CLAUDE.md @@ -0,0 +1,76 @@ +# Contract renewal + +Contract renewal extends customer subscription contracts by creating sales +quotes that, when shipped, extend or replace the subscription terms. + +## Renewal workflow + +1. **Select lines** -- `SubContractRenewalMgt.StartContractRenewalFromContract` + opens a selection page filtered to renewable contract lines (must have a + Subscription Line End Date, not be closed, and not have a pending Planned + Subscription Line). +2. **Create renewal lines** -- `SelectContractRenewal` (report) calls + `SubContractRenewalLine.InitFromServiceCommitment` for each selected + line. The renewal line copies the Renewal Term from the Subscription + Line and calculates `Agreed Sub. Line Start Date` as one day after the + current end date. +3. **Generate sales quote** -- `CreateSubContractRenewal` (codeunit 8002) + creates a Sales Quote header from the customer contract fields, then + adds sales lines of type `Service Object` with pricing from the current + Subscription Line. Each line gets a `SalesSubscriptionLine` marked + with `Process::Contract Renewal`. +4. **Convert to order and ship** -- when the quote becomes an order and is + shipped, `PostSubContractRenewal` (codeunit 8004) runs via the + `OnBeforePostSalesLines` subscriber. It creates Planned Subscription + Lines and, if pricing hasn't changed, immediately applies them to + extend the Subscription Line End Date. + +## Key tables + +**SubContractRenewalLine (8001)** is the planning table. It links to a +Subscription Line and its contract, stores the `Renewal Term` and +`Agreed Sub. Line Start Date`, and carries FlowFields for current pricing. +Primary key is `Subscription Line Entry No.` -- one renewal line per +subscription line. The `Linked to Sub. Contract No.` field groups renewal +lines by the originating customer contract. + +**PlannedSubscriptionLine (8002)** holds future subscription terms. +It mirrors the Subscription Line field structure plus `Type Of Update` +(Contract Renewal or Price Update), `Perform Update On`, and references +to the source Sales Order. `ProcessPlannedServiceCommitment` applies the +planned values to the real Subscription Line when conditions are met. + +## When updates apply immediately vs are deferred + +`CheckPerformServiceCommitmentUpdate` determines this. The Planned +Subscription Line is applied immediately when either: + +- The Subscription Line's `Next Billing Date` has reached or passed its + `Subscription Line End Date` (meaning the current term is fully invoiced) +- All pricing fields are identical (only the end date is changing) + +Otherwise the planned line waits until the current billing period is +fully invoiced (triggered by posting events in +`SubContrRenewalSubcribers`). + +## Batch processing + +`BatchCreateContractRenewal` groups renewal lines by contract number and +creates one sales quote per contract. Errors on individual contracts are +captured and written to the `Error Message` field on the renewal line +rather than stopping the batch. + +## Gotchas + +- You cannot create a renewal if a sales quote or order already exists for + that contract line (`ExistsInSalesOrderOrSalesQuote` check). +- Renewal is customer-side only. Vendor subscription lines can be included + as linked lines (via `SetAddVendorServices`), but the renewal quote is + always a sales document. +- Contract renewal lines are excluded from standard document copying + (`ExcludeRenewalForSalesLine` subscriber) and from document totals. +- Quote-to-invoice conversion is blocked for contract renewal quotes -- + they must go through quote-to-order-to-ship. +- When a credit memo is posted against a previously invoiced period, + the system restores the Subscription Line from its archive and + re-creates a Planned Subscription Line. diff --git a/src/Apps/W1/Subscription Billing/App/Customer Contracts/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Customer Contracts/docs/CLAUDE.md new file mode 100644 index 0000000000..b9ab88543d --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Customer Contracts/docs/CLAUDE.md @@ -0,0 +1,64 @@ +# Customer contracts + +Customer contracts are the sell-side contract entity in Subscription Billing. +Each contract groups subscription lines that share billing terms for a single +customer. + +## Core tables + +**CustomerSubscriptionContract (8052)** is the header. It stores sell-to and +bill-to customer addresses, ship-to address, payment terms, currency, salesperson, +and dimensions. It follows the standard BC sell-to / bill-to pattern where +changing the bill-to customer cascades payment, currency, and dimension defaults. +The table publishes roughly 50 IntegrationEvents -- by far the largest +extensibility surface in the app. See `extensibility.md` for a grouped +breakdown. + +**CustSubContractLine (8062)** links to exactly one Subscription Line (via +`Subscription Line Entry No.`). Key behaviors: + +- Lines are **soft-deleted** using the `Closed` flag, not physically removed. + Deletion is blocked when unreleased contract deferrals exist + (`ErrorIfUnreleasedCustSubContractDeferralExists`). +- The FlowField `Planned Sub. Line exists` checks for pending renewal or + price-update entries in `Planned Subscription Line`. +- `CreateServiceObjectWithServiceCommitment()` auto-creates a Subscription + Header + Subscription Line when a user enters an Item or G/L Account on + a manual contract line. +- Merging contract lines consolidates quantities into a new Subscription + Header, closes the old lines, and re-assigns the new Subscription Line. + Merge requires matching dimensions, Next Billing Date, and Customer Reference. + +## Contract types and harmonized billing + +The `Contract Type` field references `Subscription Contract Type`, which +controls two defaults: whether contract deferrals are created, and whether +harmonized billing is enabled. When harmonized billing is on, the contract +maintains `Billing Base Date` and `Default Billing Rhythm` so all lines +align to the same billing cycle (`RecalculateHarmonizedBillingFieldsBasedOnNextBillingDate`). + +## Extending contracts + +`ExtendSubContractMgt` (codeunit 8075) handles adding new subscription lines +to existing contracts via the **Extend Contract** page. The flow: select +an item, pick subscription packages, set provision start date, then +`ExtendContract()` creates a Subscription Header from the item and assigns +its Subscription Lines to the chosen customer and/or vendor contract. + +## Dimension management + +`CustSubContrDimMgt` (codeunit 8054) auto-creates a dimension value for the +contract number when `Aut. Insert C. Contr. DimValue` is set in +Subscription Contract Setup. Dimension changes on the contract header +cascade to all contract lines after user confirmation. + +## Gotchas + +- Deletion of a contract line is blocked if a billing proposal line exists + for it -- delete the billing proposal first. +- Usage-based billing lines cannot be deleted if `Usage Data Billing` + records reference the contract line. +- Manual contract lines (Item or G/L Account typed directly) require that + default billing periods are configured in Subscription Contract Setup. +- Currency changes on the contract header force recalculation of all + linked Subscription Line amounts via exchange rate selection. diff --git a/src/Apps/W1/Subscription Billing/App/Customer Contracts/docs/extensibility.md b/src/Apps/W1/Subscription Billing/App/Customer Contracts/docs/extensibility.md new file mode 100644 index 0000000000..0264f60c4f --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Customer Contracts/docs/extensibility.md @@ -0,0 +1,127 @@ +# Customer contracts extensibility + +The customer contracts area is the richest extensibility surface in the +Subscription Billing app. The `CustomerSubscriptionContract` table alone +publishes roughly 50 IntegrationEvents, and `CustSubContractLine` adds 6 more. +The `ExtendContract` page publishes 3 events. + +## Customizing contract creation and initialization + +These events let you intercept contract setup, number series selection, and +initial field population. + +- `OnBeforeInitInsert` -- override the number series or auto-number logic +- `OnValidateSellToCustomerNoAfterInit` -- adjust fields after a new + sell-to customer reinitializes the contract +- `OnAfterGetSubscriptionContractSetup` -- modify the setup record before + it is used (e.g. override number series per context) +- `OnInitFromContactOnAfterInitNoSeries` -- hook contact-based creation +- `OnBeforeAssistEdit` -- replace the standard assist-edit / no-series lookup + +## Customizing customer and contact validation + +Events for controlling how sell-to, bill-to, and contact relationships resolve. + +- `OnBeforeValidateSellToCustomerName`, `OnBeforeValidateBillToCustomerName` + -- intercept name-to-number resolution +- `OnBeforeConfirmSellToContactNoChange`, `OnBeforeConfirmBillToContactNoChange` + -- suppress or customize the change-confirmation dialog +- `OnBeforeUpdateSellToCust`, `OnBeforeUpdateBillToCust` -- skip or + replace the contact-to-customer resolution logic +- `OnUpdateSellToCustOnBeforeContactIsNotRelatedToAnyCustomerErr`, + `OnUpdateBillToCustOnBeforeContactIsNotRelatedToAnyCustomerErr` -- + handle unrelated contacts without erroring +- `OnUpdateSellToCustOnBeforeFindContactBusinessRelation`, + `OnUpdateBillToCustOnBeforeFindContactBusinessRelation` -- replace the + business-relation lookup +- `OnAfterUpdateSellToCont`, `OnAfterUpdateBillToCont` -- post-process + contact updates +- `OnAfterUpdateSellToCust`, `OnAfterUpdateBillToCust` -- post-process + customer-from-contact updates +- `OnValidateBillToCustomerNoOnAfterConfirmed` -- runs after user confirms + bill-to customer change + +## Customizing address handling + +Events for controlling address copy and sync behavior across sell-to, bill-to, +and ship-to. + +- `OnAfterCopySellToCustomerAddressFieldsFromCustomer` -- modify fields + after sell-to address is copied from the Customer card +- `OnAfterSetFieldsBilltoCustomer` -- post-process bill-to fields from + Customer +- `OnAfterCopySellToAddressToBillToAddress`, + `OnAfterCopySellToAddressToShipToAddress` -- hook address cascading +- `OnBeforeCopyShipToCustomerAddressFieldsFromCustomer`, + `OnBeforeCopyShipToCustomerAddressFieldsFromShipToAddr` -- skip or + replace ship-to copy logic +- `OnAfterCopyShipToCustomerAddressFieldsFromCustomer`, + `OnAfterCopyShipToCustomerAddressFieldsFromShipToAddr` -- post-process + ship-to +- `OnAfterIsShipToAddressEqualToSellToAddress`, + `OnAfterIsShipToAddressEqualToSubscriptionHeaderShipToAddress` -- + override the address-equality check +- Post code lookups: `OnBeforeLookupBillToPostCode`, + `OnBeforeLookupSellToPostCode`, `OnBeforeLookupShipToPostCode`, + `OnBeforeValidateBillToPostCode`, `OnBeforeValidateSellToPostCode`, + `OnBeforeValidateShipToPostCode` + +## Customizing dimensions + +- `OnBeforeCreateDim`, `OnAfterCreateDimDimSource` -- control which + dimension sources feed into the default dimension set +- `OnCreateDimOnBeforeModify` -- inspect the new dim set before the + contract header is modified +- `OnBeforeValidateShortcutDimCode`, `OnAfterValidateShortcutDimCode` -- + hook shortcut dimension changes +- `OnBeforeUpdateAllLineDim`, `OnBeforeConfirmUpdateAllLineDim` -- skip + or customize the "update all lines?" confirmation and logic +- `OnAfterUpdateHarmonizedBillingFields` -- react after harmonized + billing fields are recalculated from dimension/billing changes + +## Customizing contract line management + +Events on `CustSubContractLine` (table 8062). + +- `OnAfterInitFromSubscriptionLine` -- modify the contract line after it + is initialized from a Subscription Line +- `OnAfterCheckAndDisconnectContractLine` -- post-process after a line is + disconnected from its Subscription Line (on delete or type change) +- `OnAfterCheckSelectedContractLinesOnMergeContractLines` -- add custom + merge validations +- `OnAfterUpdateSubscriptionDescription`, + `OnAfterUpdateSubscriptionLineDescription` -- react to description edits + flowing to the Subscription Header / Line +- `OnAfterLoadAmountsForContractLine` -- adjust amounts after loading from + the underlying Subscription Line + +## Customizing contract line creation from subscriptions + +Events on the contract header governing how Subscription Lines become +contract lines. + +- `OnBeforeCreateCustomerContractLineFromSubscriptionLine` -- skip or + replace the standard line-creation logic +- `OnAfterCreateCustomerContractLineFromSubscriptionLine` -- post-process + the new contract line +- `OnBeforeModifySubscriptionLineOnCreateCustomerContractLineFromSubscriptionLine` + -- adjust the Subscription Line before it is linked +- `OnBeforeCreateCustomerContractLineFromTempSubscriptionLine` -- intercept + batch assignment from temporary Subscription Lines +- `OnBeforeUpdateServicesDates` -- hook before subscription date refresh + +## Customizing contract extension + +Events on the **Extend Contract** page (page 8002). + +- `OnBeforeExtendContract` -- validate or block the extension before it runs +- `OnAfterGetAdditionalServiceCommitments` -- modify the selectable + subscription packages +- `OnAfterValidateItemNo` -- react to item selection on the extension page + +## Customizing the extend contract codeunit + +`ExtendSubContractMgt` (codeunit 8075) publishes one event: + +- `OnAfterAssignSubscriptionLineToContractOnBeforeModify` -- adjust the + Subscription Line before it is saved with its new contract assignment diff --git a/src/Apps/W1/Subscription Billing/App/Deferrals/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Deferrals/docs/CLAUDE.md new file mode 100644 index 0000000000..749e7adc2a --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Deferrals/docs/CLAUDE.md @@ -0,0 +1,123 @@ +# Deferrals + +Revenue/cost recognition module for subscription contract billing. When +enabled, posted contract invoice amounts go to balance sheet accrual +accounts instead of income/expense. A separate release process moves +amounts to income/expense accounts monthly. + +## How it works + +When a contract invoice is posted and deferrals are enabled, the posting +codeunits (`CustomerDeferralsMngmt.Codeunit.al`, +`VendorDeferralsMngmt.Codeunit.al`) intercept the posting pipeline via +event subscribers: + +1. **Account redirection**: `OnPrepareLineOnBeforeSetAccount` swaps the + sales/purchase account from `"Cust. Sub. Contract Account"` to + `"Cust. Sub. Contr. Def Account"` (or the vendor equivalents) in General + Posting Setup. This sends the posting to the balance sheet deferral + account instead of the income/expense account. + +2. **Deferral record creation**: `InsertContractDeferrals` creates one + `"Cust. Sub. Contract Deferral"` (table 8066) or + `"Vend. Sub. Contract Deferral"` (table 8072) record per month in the + billing period. The billing period comes from `"Recurring Billing from"` + / `"Recurring Billing to"` on the document line. + +3. **Period splitting methodology**: The billing period amount is divided + among calendar months. Partial months (first and last) are + day-proportioned using a daily rate = total amount / total days. Full + months in between get an equal share of the remaining amount. The last + period absorbs rounding differences. Each deferral record stores the + `"Number of Days"` and `Amount` for its month. + +4. **Release**: `ContractDeferralsRelease.Report.al` (Report 8051) posts + G/L entries that move amounts from the deferral account to the + income/expense account. It takes a "Post Until Date" parameter and + releases all unreleased deferrals with posting dates up to that date. + Each release creates two G/L entries per deferral: debit the + income/expense account, credit the deferral account (or vice versa for + vendor). The report can be scheduled via job queue. + +## Credit memo handling + +When a credit memo is posted against an invoice that has deferrals: + +- The original invoice's deferral records are located by document no. and + contract line +- Mirror deferral records are created with negated amounts +- Any unreleased invoice deferrals are force-released at the credit memo's + posting date +- The credit memo deferrals are then also released immediately +- If the invoice was already fully released, only the credit memo deferrals + are created and released + +This ensures the net effect is zero once both the invoice and credit memo +deferrals are released. + +## Configuration + +**Opt-in per contract type**: The `"Create Contract Deferrals"` enum +(`CreateContractDeferrals.Enum.al`) has three values: + +- `Contract-dependent` -- inherits the setting from the contract type +- `Yes` -- always create deferrals +- `No` -- never create deferrals + +The `CreateContractDeferrals()` function on Sales Line / Purchase Line +evaluates this hierarchy. + +**G/L accounts** in General Posting Setup: + +- `"Cust. Sub. Contract Account"` / `"Vend. Sub. Contract Account"` -- + income/expense accounts (where amounts end up after release) +- `"Cust. Sub. Contr. Def Account"` / `"Vend. Sub. Contr. Def. Account"` -- + balance sheet deferral/accrual accounts (where amounts park during + deferral) + +**Source Code Setup**: `"Sub. Contr. Deferrals Release"` identifies +deferral release G/L entries. + +**Journal template/batch**: When `"Journal Templ. Name Mandatory"` is +enabled in General Ledger Setup, the Subscription Contract Setup must +specify `"Def. Rel. Jnl. Template Name"` and `"Def. Rel. Jnl. Batch Name"`. + +## Things to know + +- Deferrals are optional and controlled at the contract type level. The + three-value enum allows both global defaults and per-line overrides. +- Line discounts are tracked separately in `"Discount Amount"` on the + deferral record. Whether discount amounts are posted to a separate + line discount account depends on Sales/Purchase Setup + `"Discount Posting"` configuration. +- `"Deferral Base Amount"` is the total amount being deferred (net of VAT + if prices include VAT). It does not include the discount amount. +- `"Release Posting Date"` is the date the release G/L entry was posted, + which can differ from `"Posting Date"` (the deferral period date). This + separation enables posting releases for past periods on a current date. +- `"Document Posting Date"` is the original invoice/credit memo posting + date. `"Posting Date"` is the first day of the deferral period month. +- The `Released` boolean controls whether a deferral has been posted to + G/L. The release report filters on `Released = false`. +- Each release G/L entry carries the `"Subscription Contract No."` for + reporting and traceability. +- Dimensions from the original document line are preserved on deferral + records and passed through to G/L entries via `"Dimension Set ID"`. +- The Navigate page integration (`OnAfterNavigateFindRecords` / + `OnBeforeShowRecords`) allows finding deferral records from the standard + document navigation. +- Deferral preview codeunits (`DeferralPostPreviewBinding`, + `DeferralPostPreviewHandler`, `DeferralPostPreviewSubscr`) support the + standard posting preview framework, so users can preview deferral + entries before posting. + +## Extension points + +- `OnBeforeInsertCustomerContractDeferral` -- modify deferral records + before insert (e.g., adjust amounts, add custom fields) +- `OnBeforeInsertVendorContractDeferral` -- same for vendor deferrals +- `OnBeforeReleaseCustomerContractDeferral` -- skip or customize release + per deferral record +- `OnBeforeReleaseVendorContractDeferral` -- same for vendor +- `OnAfterInitFromSalesLine` / `OnAfterInitFromPurchaseLine` -- extend + deferral initialization from document lines diff --git a/src/Apps/W1/Subscription Billing/App/Import/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Import/docs/CLAUDE.md new file mode 100644 index 0000000000..34c0189736 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Import/docs/CLAUDE.md @@ -0,0 +1,57 @@ +# Import + +Bulk creation of subscriptions, subscription lines, contract lines, and +customer contracts from staging tables. Despite the name, this is not a +data import framework -- it is a processing pipeline that converts +imported staging records into live subscription billing entities. + +## How it works + +Three staging tables hold data to be processed: + +- **ImportedSubscriptionHeader** (table 8008) -- one row per subscription + to create. Requires item number, quantity, and optionally customer, + serial number, and provision dates. +- **ImportedSubscriptionLine** (table 8009) -- one row per subscription + line. Carries the full billing agreement: partner, package code, + template, start/end dates, pricing fields, billing rhythm, and the + target contract number. +- **ImportedCustSubContract** (table 8010) -- one row per customer + contract to create. Carries sell-to/bill-to customer, currency, + contract type, dimensions, and payment terms. + +Three reports drive the processing in sequence: + +1. **Create Service Objects** (report 8001) runs `CreateSubscriptionHeader` + for each unprocessed imported subscription header. Validates the item + has a subscription option, quantity is positive, and the number series + allows manual numbers if a specific number is provided. +2. **Create Customer Contracts** (report 8003) runs `CreateCustSubContract` + for each unprocessed imported contract. Validates customer exists and + number series is configured. +3. **Cr. Serv. Comm. And Contr. L.** (report 8002) processes imported + subscription lines in two steps per record: first runs + `CreateSubscriptionLine` to create the subscription line on the + subscription header, then runs `CreateSubContractLine` to assign it + to a customer or vendor contract line. If the subscription line + creation fails, the contract line step is skipped. + +Each report uses `Codeunit.Run` with error trapping -- failures are +recorded in the staging record's `Error Text` field and processing +continues. Every record is committed individually so partial runs retain +their progress. + +## Things to know + +- The reports must run in the correct order: subscriptions first, then + contracts, then subscription lines + contract lines. The third report + requires both the subscription and contract to exist. +- Lines with `Invoicing via` = Sales automatically mark the contract line + as created (they do not need a contract assignment). +- Comment lines (`IsContractCommentLine`) skip subscription line creation + and create only a contract line with a description. +- `CreateSubContractLine` validates that the contract's customer matches + the subscription's End-User Customer and that currency codes align. + Mismatches error. +- All four codeunits publish integration events for extensibility + (OnAfterInsert, OnAfterModify, OnAfterTest patterns). diff --git a/src/Apps/W1/Subscription Billing/App/Sales Service Commitments/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Sales Service Commitments/docs/CLAUDE.md new file mode 100644 index 0000000000..be465db9c8 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Sales Service Commitments/docs/CLAUDE.md @@ -0,0 +1,186 @@ +# Sales Service Commitments + +This module bridges the standard BC sales process and the Subscription Billing +engine. When a user sells an item that carries subscription packages, this +module auto-generates Sales Subscription Lines on the sales document, carries +them through archiving and document-copy workflows, and ultimately hands them +off to the subscription/contract system at shipment time. + +## Core objects + +**`SalesSubscriptionLine.Table.al`** (table 8068) stores subscription lines +attached to a sales document line. Each record captures billing terms +(Initial Term, Extension Term, Notice Period, Billing Base Period, Billing +Rhythm), pricing details (Calculation Base Type/Amount/%, Price, Discount), +and a Partner enum indicating whether the line bills a customer or a vendor. +The table prevents edits while the parent sales order is released. It also +holds `Subscription Header No.` and `Subscription Line Entry No.` fields +that get populated after shipment, linking back to the created subscription. + +**`SalesSubscriptionLineMgmt.Codeunit.al`** (codeunit 8069) orchestrates the +full lifecycle. It is `SingleInstance` so a `SalesLineRestoreInProgress` +flag can suppress auto-creation during archive-restore. The codeunit +subscribes to events on Sales Line insert, Sales-Quote to Order, +Blanket Sales Order to Order, Copy Document, Explode BOM, Archive +Management, Sales-Post, and Undo Sales Shipment Line. + +**`SalesSubLineArchive.Table.al`** (table 8069) mirrors the +SalesSubscriptionLine structure with added `Version No.` and +`Doc. No. Occurrence` fields for archive versioning. + +**`SalesServiceCommitmentBuff.Table.al`** (table 8020) is a temporary buffer +used exclusively by `CalcVATAmountLines` in SalesSubscriptionLine to +aggregate subscription amounts by billing rhythm and VAT setup for report +totals. + +## How subscription lines are created + +When an item with Subscription Option set to "Service Commitment Item" or +"Sales with Service Commitment" is entered on a sales line, +`AddSalesServiceCommitmentsForSalesLine` fires via `SalesLineOnAfterInsertEvent`. +It queries `Item Subscription Package` records filtered by the item and the +header's Customer Price Group, selecting packages marked `Standard = true`. +For each matching package, it walks the package's `Subscription Package Line` +records and inserts a `Sales Subscription Line` per package line. + +After standard packages are applied, `AddAdditionalSalesServiceCommitmentsForSalesLine` +runs. This opens the "Assign Service Commitments" dialog showing non-standard +(optional) packages. The dialog only appears if optional packages exist and +`GuiAllowed()` returns true. Contract renewal lines block this dialog +entirely and raise an error if attempted. + +The item number written to each Sales Subscription Line depends on the item's +Subscription Option. For "Service Commitment Item", the package line's +Invoicing Item No. is used if present, otherwise the item itself. For +"Sales with Service Commitment" invoiced via Contract, the package line must +have an Invoicing Item No. + +## Calculation base types + +The Calculation Base Type on each subscription line controls how Price is +derived: + +- **Item Price** -- uses the item's list price (via `UpdateUnitPrice`) for + customer lines, or `CalculateUnitCost` for vendor lines. +- **Document Price** -- uses `Unit Price` from the sales line (customer) or + `Unit Cost` (vendor). Price tracks the document. +- **Document Price And Discount** -- same as Document Price but also copies + `Line Discount %` from the sales line into the subscription line's + Discount %. This is the only mode where sales line discounts flow through. + +A notification warns when the user changes a sales line discount and +the subscription line uses a Calculation Base Type other than +"Document Price And Discount", since the discount will not propagate. + +## Subscription line recalculation + +Changes to Quantity, Unit Price, Unit Cost, Line Discount %, or +Line Discount Amount on the sales line trigger +`UpdateSalesServiceCommitmentCalculationBaseAmount`. This recalculates +every attached Sales Subscription Line by calling +`CalculateCalculationBaseAmount`, keeping subscription pricing in sync +with the document. + +## Document lifecycle events + +**Quote to Order / Blanket Order to Order** -- subscription lines transfer +via `TransferServiceCommitments`, which copies each line to the new document +type while re-validating `Calculation Base Amount`. + +**Copy Document** -- if `RecalculateLines` is true, subscription lines are +regenerated from item packages. Otherwise they are transferred field-by-field +from the source document. Copy from archive works the same way. + +**Archive** -- `StoreSalesServiceCommitmentLines` copies every subscription +line to `Sales Sub. Line Archive` on each archive action. +`RestoreSalesServiceCommitment` reverses this, and the `SingleInstance` +flag `SalesLineRestoreInProgress` prevents the insert-event from re-adding +packages during restore. + +**Post (Sales-Post)** -- the `OnBeforeSalesLineDeleteAll` subscriber deletes +subscription lines from the sales document after posting, since at that point +the subscription has been created. + +**Undo Shipment** -- `RemoveQuantityInvoicedForServiceCommitmentItems` zeros +out `Quantity Invoiced` and `Qty. Invoiced (Base)` on the shipment line for +service commitment items, because the original "invoiced" status was synthetic +(these items do not go through a real sales invoice). + +## Table extensions on standard sales objects + +`SalesHeader.TableExt.al` adds `Recurring Billing` (marks documents created +by the billing engine), `Sub. Contract Detail Overview` (controls whether +billing details print), and `Auto Contract Billing`. It also exposes +`HasOnlyContractRenewalLines` for checking whether a document contains +nothing but renewal lines. + +`SalesLine.TableExt.al` adds `Recurring Billing from/to` date fields, +a `Subscription Lines` FlowField counting attached Sales Subscription Lines, +a `Subscription Option` FlowField from the Item table, a `Discount` boolean, +and `Exclude from Doc. Total`. It prevents manual selection of the +"Service Object" type, enforces that Invoice Discount is not allowed on +service commitment items, and blocks deferral codes when contract deferrals +are active on the billing line. + +`SalesLineArchive.TableExt.al` adds the same `Subscription Lines` FlowField +and `Exclude from Doc. Total` to archived lines, and cleans up archived +subscription lines on delete. + +`SalesShipmentLine.TableExt.al` adds a TableRelation override so the "No." +field resolves to `Subscription Header` when Type is "Service Object". + +The Purchase Header and Purchase Line extensions in this folder add the +vendor-side `Recurring Billing` flag and billing date fields used by +vendor contract billing. + +## Enum extension + +`SubBillingSalesLineType.EnumExt.al` adds value 8000 "Service Object" +(captioned "Subscription") to the Sales Line Type enum, enabling sales lines +that reference a Subscription Header rather than an item or G/L account. + +## Report extensions + +`ContractStandardSalesQuote.ReportExt.al` and +`ContractSalesOrderConf.ReportExt.al` extend the standard quote and order +confirmation reports. They inject per-line subscription detail +(ServiceCommitmentForLine dataitem) and a grouped summary by billing rhythm +(ServiceCommitmentsGroupPerPeriod). Service commitment items are excluded +from document totals via `ExcludeItemFromTotals`. Both provide RDLC and Word +layouts under the `Layouts/` subfolder. + +`ContractBlanketSalesOrder.ReportExt.al` extends the blanket order report, +excluding service commitment items from totals but not adding the per-line +detail. + +## Page extensions and pages + +The subform page extensions for Sales Quote, Sales Order, Blanket Sales +Order, and their archive counterparts all add a `Subscription Lines` column +after Line Amount, plus actions to view and add subscription lines. +The quote and order subforms also show Customer and Vendor Contract No. +fields derived from contract renewal subscription lines. + +`SalesServiceCommitments.Page.al` (page 8082) is the detail editor for +subscription lines on a single sales line. `SalesServiceCommitmentsList.Page.al` +(page 8015) is a read-only global list page. `SalesServCommArchiveList.Page.al` +(page 8083) shows archived subscription lines. + +`SalesLineFactBox.PageExt.al` adds the `Subscription Lines` count to the +Sales Line FactBox. + +## Integration events + +The codeunit publishes eight integration events for extensibility: + +- `OnBeforeAddSalesServiceCommitmentsForSalesLine` -- suppress or replace auto-creation +- `OnBeforeCreateSalesSubscriptionLineFromSubscriptionPackageLine` -- intercept individual line creation +- `OnAfterCreateSalesSubscriptionLineFromSubscriptionPackageLine` -- post-process created lines +- `OnCreateSalesServCommLineFromServCommPackageLineOnAfterInsertSalesSubscriptionLineFromSubscriptionPackageLine` -- runs after insert, before field population +- `OnBeforeModifySalesSubscriptionLineFromSubscriptionPackageLine` -- modify line before final write +- `OnAddAdditionalSalesSubscriptionLinesForSalesLineAfterApplyFilters` -- adjust package filters for optional packages +- `OnBeforeGetItemNoForSalesServiceCommitment` -- override item number resolution +- `OnAfterShowAssignServiceCommitmentsDetermined` -- control whether the optional-package dialog shows + +The SalesSubscriptionLine table publishes additional events around +calculation base amount computation and VAT line creation. diff --git a/src/Apps/W1/Subscription Billing/App/Sales Service Commitments/docs/business-logic.md b/src/Apps/W1/Subscription Billing/App/Sales Service Commitments/docs/business-logic.md new file mode 100644 index 0000000000..7d925ea696 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Sales Service Commitments/docs/business-logic.md @@ -0,0 +1,262 @@ +# Business logic + +## Subscription line creation during sales + +The entry point is `SalesLineOnAfterInsertEvent` in +`SalesSubscriptionLineMgmt.Codeunit.al`. When a sales line is inserted with +`RunTrigger = true`, it calls `AddSalesServiceCommitmentsForSalesLine`, which +first checks that the line is an Item type with a non-empty "No." on a +supported document type (Quote, Order, or Blanket Order) via +`IsSalesLineWithSalesServiceCommitments`. Temporary records and lines with +`Line No. = 0` are skipped. + +The method then loads `Item Subscription Package` records for the item, +filtered to the header's `Customer Price Group` and `Standard = true`. If no +packages exist for that price group, the filter widens first to blank price +group, then drops the price-group filter entirely. This cascading filter +strategy means a customer-price-group-specific package takes priority, but +the system always falls back rather than showing nothing. + +For each matching package, `InsertSalesServiceCommitmentFromServiceCommitmentPackage` +iterates the package's `Subscription Package Line` records and calls +`CreateSalesServCommLineFromServCommPackageLine`. This procedure inserts a +`Sales Subscription Line`, then populates it field by field from the package +line: Invoicing via, Item No., Description, Extension Term, Notice Period, +Initial Term, Partner, Calculation Base Type, Billing Base Period, Usage +Based Billing/Pricing, Calculation Base %, Sub. Line Start Formula, Billing +Rhythm, Discount, Price Binding Period, Period Calculation, and Create +Contract Deferrals. After all fields are set, it calls +`CalculateCalculationBaseAmount` and, for customer partner lines, +`CalculateUnitCost`. The line is then modified. + +After standard packages, `AddAdditionalSalesServiceCommitmentsForSalesLine` +runs the optional-package dialog. It builds a filter of package codes already +assigned (via `GetPackageFilterForItem` with `RemoveExistingPackageFromFilter`), +applies Customer Price Group filtering, and opens the "Assign Service +Commitments" page in lookup mode. If the user selects packages and clicks OK, +those packages are inserted using the same +`InsertSalesServiceCommitmentFromServiceCommitmentPackage` path. + +Contract renewal lines are prohibited from adding additional packages -- +the method errors with "Additional Subscription Lines cannot be added to a +Contract Renewal". + +## Calculation base amount computation + +`CalculateCalculationBaseAmount` in `SalesSubscriptionLine.Table.al` branches +on Partner (Customer vs. Vendor), then on Calculation Base Type. + +For customer lines: + +- **Item Price** creates a temporary copy of the sales line and calls + `UpdateUnitPrice` to get the item's list price independent of any document + discount. The result goes into `Calculation Base Amount`. +- **Document Price** uses the sales line's `Unit Price` directly. +- **Document Price And Discount** uses `Unit Price` and also copies + `Line Discount %` from the sales line into the subscription line's + `Discount %`. This is the only path where sales line discounts propagate + to subscription lines. + +For vendor lines: + +- **Item Price** calls `Sub. Contracts Item Management.CalculateUnitCost` and + converts to document currency if the sales header uses a foreign currency. +- **Document Price** uses the sales line's `Unit Cost`. + +After the base amount is set, `CalculatePrice` derives `Price` as +`Calculation Base Amount * Calculation Base % / 100`. If the line is a +Discount line, the base amount is forced negative. The `CalculateServiceAmount` +method then computes the final `Amount` as +`Price * Quantity - Discount Amount`, enforcing that Amount cannot exceed +`Price * Quantity`. + +## Recalculation on sales line changes + +The `SalesLine.TableExt.al` modify triggers on Quantity, Unit Price, +Unit Cost, Unit Cost (LCY), Line Discount %, and Line Discount Amount all +call `UpdateSalesServiceCommitmentCalculationBaseAmount`. This procedure +compares old and new values and, if anything changed, iterates every attached +Sales Subscription Line and calls `CalculateCalculationBaseAmount` on each. +This keeps subscription pricing synchronized as users edit the sales document. + +When `Line Discount %` or `Line Discount Amount` changes, the table +extension also calls `NotifyIfDiscountIsNotTransferredFromSalesLine` in the +codeunit. This sends a dismissible notification if any customer-side +subscription line uses a Calculation Base Type other than "Document Price And +Discount", alerting the user that the discount they just entered will not +flow through to subscription billing. + +## Item number resolution + +`GetItemNoForSalesServiceCommitment` determines which item number goes on each +Sales Subscription Line. The logic depends on the parent item's Subscription +Option: + +- **Service Commitment Item** -- uses the package line's `Invoicing Item No.` + if set, otherwise the item itself. +- **Sales with Service Commitment** -- requires `Invoicing Item No.` on the + package line when invoicing via Contract (enforced by `TestField`). Uses + the invoicing item. + +This separation matters because Service Commitment Items represent pure +subscriptions (the item is the subscription), while "Sales with Service +Commitment" items are physical goods that also carry a subscription (the +invoicing item captures the recurring charge separately). + +## Shipment and subscription creation + +When a sales order is shipped, for each line with a Subscription Option, the +posting engine (outside this subfolder) creates a `Subscription Header` and +`Subscription Line` records. The Sales Subscription Line's +`Subscription Header No.` and `Subscription Line Entry No.` fields are +populated to link back. + +The start date for each subscription line is determined by a priority: + +1. **Agreed Sub. Line Start Date** -- if the user entered a date here, it + overrides everything. This is the "individually agreed" start. +2. **Shipment Date + Sub. Line Start Formula** -- if no agreed date, the + system applies the date formula from the package line to the shipment + posting date. A common formula is `-CM+1M` which calculates the first + day of the following month (Current Month start, plus one month). +3. **Shipment Date** -- if no formula and no agreed date, the shipment date + itself becomes the start date. + +Cancellation-related dates are derived from the start: the Cancellation +Possible Date is typically calculated as +`Start Date + Initial Term - Notice Period`. The Term Until date equals +`Start Date + Initial Term - 1D` (the last day of the initial term). + +After shipment, `DeleteSalesServiceCommitmentOnBeforeSalesLineDeleteAll` +(subscriber on Sales-Post `OnBeforeSalesLineDeleteAll`) removes the Sales +Subscription Lines from the now-posted sales document. + +## Undo shipment behavior + +`RemoveQuantityInvoicedForServiceCommitmentItems` subscribes to Undo Sales +Shipment Line and zeros out `Quantity Invoiced` and `Qty. Invoiced (Base)` +on the shipment line for service commitment items. This is necessary because +the posting engine marks these items as "invoiced" at shipment even though +no sales invoice exists. Without this reset, the undo-shipment logic would +fail trying to un-invoice quantity that was never invoiced through the normal +sales invoice path. + +## The auto-invoicing illusion + +Service commitment items behave differently from normal items during posting. +When shipped, the system marks them as fully invoiced on the shipment line +even though no sales invoice is generated. The item ledger entries and value +entries created at shipment have `Invoiced Quantity = 0`, `Sale Amount = 0`, +and `Cost Amount = 0`. The item appears "sold" from an inventory perspective +but carries no financial amounts. + +The actual billing happens later through the contract engine: when the +subscription is assigned to a Customer Subscription Contract and billed, +separate item/value ledger entries are created with the real +`Invoiced Quantity` and `Sales Amount`. This split means you cannot look at +the original shipment's value entries to see subscription revenue -- it only +appears on the contract billing entries. + +This design also means `Exclude from Doc. Total` is set to `true` for +service commitment items on the sales line. The item's line amount is +excluded from the document total because the customer will be billed through +the contract, not through the sales invoice. If you sum the sales order +total, service commitment items do not contribute. + +## Renewal lines on sales documents + +When a sales document carries contract renewal lines (Process = "Contract +Renewal"), several special behaviors activate. The `IsContractRenewal` check +on the sales line looks for any attached Sales Subscription Line with +`Process = "Contract Renewal"`. + +Renewal lines cannot be individually deleted if they are the last remaining +renewal line on the document -- the `OnDelete` trigger checks +`IsOnlyRemainingLineForContractRenewalInDocument` and errors, forcing the +user to delete the entire sales line instead. + +The `UpdateHeaderFromContractRenewal` method on the Process field's validate +trigger calls `SetExcludeFromDocTotal` and `UpdateUnitPrice`, which adjusts +the sales line to exclude the renewal amount from document totals. + +`HasOnlyContractRenewalLines` on the Sales Header extension checks whether +every non-blank-type line in the document is a contract renewal, which other +parts of the system use to apply renewal-specific posting logic. + +## Report printing behavior + +The report extensions for Standard Sales Quote +(`ContractStandardSalesQuote.ReportExt.al`) and Order Confirmation +(`ContractSalesOrderConf.ReportExt.al`) follow identical patterns. On the +Header's `OnAfterAfterGetRecord`, they call `FillServiceCommitmentsForLine` +(which populates a temporary `Sales Line` dataset with subscription details +per document line) and `FillServiceCommitmentsGroupPerPeriod` (which +aggregates subscription amounts into groups by billing rhythm). + +On each line's `OnAfterAfterGetRecord`, `ExcludeItemFromTotals` subtracts +service commitment items from the report's running totals. This prevents +subscription amounts from inflating the document subtotals. + +The grouped summary at the bottom of the report shows subscription costs +organized by billing period (e.g., "Monthly: 50.00", "Yearly: 200.00"). For +contract renewal lines, the grouping uses a special "Contract Renewal" +identifier and calculates the amount using +`DateFormulaManagement.CalculateRenewalTermRatioByBillingRhythm` to +prorate across the renewal term. For regular lines, amounts are scaled by +the ratio `RhythmPeriodCount / BasePeriodCount` to normalize everything to +the billing rhythm period. + +The Blanket Sales Order report extension +(`ContractBlanketSalesOrder.ReportExt.al`) is simpler -- it only excludes +service commitment items from totals without adding the per-line detail or +grouped summary. + +## VAT calculation for subscription lines + +`CalcVATAmountLines` in `SalesSubscriptionLine.Table.al` builds a temporary +`Sales Service Commitment Buff.` table aggregating subscription amounts by +billing rhythm and VAT setup. It handles Normal VAT, Reverse Charge VAT, +Full VAT, and Sales Tax. For prices including VAT, it reverse-calculates the +base; for prices excluding VAT, it forward-calculates the VAT amount. The +buffer groups by a `Rhythm Identifier` (a combination of period count and +date formula type), which lets the report extensions show separate VAT +breakdowns per billing rhythm. + +## Archive round-trip + +The archive workflow uses two event subscribers in the codeunit. +`StoreSalesServiceCommitmentLines` fires on +`ArchiveManagement.OnAfterStoreSalesLineArchive` and copies each Sales +Subscription Line to the archive table, setting `Version No.` from the +archive header and `Doc. No. Occurrence` from the sales header. + +`RestoreSalesServiceCommitment` fires on +`ArchiveManagement.OnAfterRestoreSalesLine` and copies archive records back +to the live table. The `SingleInstance` flag `SalesLineRestoreInProgress` is +set before the restore (on `OnRestoreDocumentOnBeforeDeleteSalesHeader`) and +cleared after (on `OnAfterRestoreSalesDocument`). While this flag is true, +the `SalesLineOnAfterInsertEvent` subscriber exits immediately, preventing +the system from re-running package auto-creation on lines that already have +their subscription data being restored from the archive. + +## Sales-to-subscription flow + +```mermaid +flowchart TD + A[Item entered on sales line] --> B{Item has\nSubscription Option?} + B -- No --> Z[Normal sales processing] + B -- Yes --> C[Load standard subscription packages\nfiltered by Customer Price Group] + C --> D[Create Sales Subscription Lines\nfrom each package line] + D --> E{Optional packages\navailable?} + E -- Yes --> F[Show Assign Subscription Lines dialog] + F --> G[User selects packages] + G --> D + E -- No --> H[Sales document ready] + G --> H + H --> I[Ship sales order] + I --> J[Create Subscription Header\n+ Subscription Lines] + J --> K[Calculate start date:\nAgreed date or\nShipment Date + Start Formula] + K --> L[Mark shipment line as invoiced\nwith zero amounts] + L --> M[Assign subscription lines\nto Customer/Vendor Contract] + M --> N[Bill via contract\ncreates real ledger entries] +``` diff --git a/src/Apps/W1/Subscription Billing/App/Service Commitments/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Service Commitments/docs/CLAUDE.md new file mode 100644 index 0000000000..93a330b37e --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Service Commitments/docs/CLAUDE.md @@ -0,0 +1,60 @@ +# Service Commitments + +Defines subscription lines and the template hierarchy that creates them. +A subscription line is the billing agreement attached to a subscription +header -- it carries price, rhythm, terms, and contract assignment. + +## How it works + +The template hierarchy flows top-down: + +1. **SubPackageLineTemplate** (table 8054) -- master formula defining + invoicing method, calculation base type/percent, billing base period, + discount flag, and usage-based billing settings. +2. **SubscriptionPackage** (table 8055) -- a named group of package + lines. Linked to a Customer Price Group. Can be copied with + `CopyServiceCommitmentPackage`. +3. **SubscriptionPackageLine** (table 8056) -- one line per package, + referencing a template. Adds partner (Customer/Vendor), billing + rhythm, initial term, subsequent term, notice period, invoicing item, + and period calculation. Validates ratio between billing base period + and billing rhythm (must be an integer multiple). +4. **ItemSubscriptionPackage** (table 8058) -- many-to-many link between + items and packages. The `Standard` flag determines which packages are + automatically applied when a subscription's Source No. is set. +5. **ItemTemplSubPackage** (table 8005) -- same link but for item + templates, so new items inherit packages from their template. + +When a subscription header validates its Source No., standard packages +expand into `SubscriptionLine` records (table 8059). Each subscription +line stores the full billing agreement: start/end dates, next billing +date, calculation base amount and percent, price, discount, billing base +period, billing rhythm, initial term, subsequent term, notice period, +currency, dimensions, and contract assignment. + +`SubscriptionLineArchive` (table 8073) preserves historical pricing. +Archival happens on subscription quantity/serial changes and price +updates, via `CopyFromServiceCommitment`. This gives period-accurate +records after pricing changes. + +Key pricing fields on a subscription line: `Calculation Base Amount` x +`Calculation Base %` = `Price`. Then `Discount %` / `Discount Amount` +reduce Price to `Amount`. All values exist in both document currency and +LCY. + +## Things to know + +- The `Partner` field on package lines and subscription lines determines + whether the line flows to a customer contract or vendor contract. The + `CalculationBaseType` "Document Price And Discount" is not allowed for + vendors -- the validation silently downgrades to "Document Price" with + a notification. +- Billing Base Period and Billing Rhythm must have an integer ratio (e.g. + 12M base with 1M rhythm = 12 billing cycles). Non-integer ratios error. +- The `Discount` boolean marks a line as a recurring discount. Discount + lines require Invoicing via Contract and a Subscription Item as the + invoicing item. They cannot combine with usage-based billing. +- `PlannedSubscriptionLine` (in the Contract Renewal subfolder, not here) + stores future-dated copies of subscription lines for pending changes. +- Subscription lines are not directly editable once linked to a contract + -- modifications go through the contract line or price update workflow. diff --git a/src/Apps/W1/Subscription Billing/App/Service Objects/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Service Objects/docs/CLAUDE.md new file mode 100644 index 0000000000..966933187c --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Service Objects/docs/CLAUDE.md @@ -0,0 +1,60 @@ +# Service Objects + +Defines the `SubscriptionHeader` (table 8057) -- the central record +representing a physical service or product being tracked under +subscription billing. Every subscription line, contract line, and billing +entry ultimately points back to a subscription header. + +## How it works + +A subscription header records what is being subscribed to: an item (or +G/L account), its quantity, serial number, version, key, and provision +dates. The `Type` + `Source No.` fields identify the source entity. When +`Source No.` is validated for an item, the subscription auto-creates +subscription lines from standard subscription packages assigned to that +item (via `InsertServiceCommitmentsFromStandardServCommPackages`). + +The subscription uses a dual-customer model: + +- **End-User Customer** -- who uses the service. Stored in + `End-User Customer No.` with full address fields. +- **Bill-to Customer** -- who pays. Stored in `Bill-to Customer No.` + with separate address fields. + +When a customer record is changed, the subscription copies address fields +from the customer card and then calls `RecalculateServiceCommitments`, +which recalculates pricing on all linked subscription lines. This applies +to changes on both End-User and Bill-to Customer. + +The Item table extension adds "Subscription Option" +(`ItemServiceCommitmentType` enum) with four values: No Subscription, +Sales with Subscription, Subscription Item, and Invoicing Item. +Subscription Items must be Non-Inventory type. The option controls which +document types the item can appear on and whether subscription packages +can be assigned. + +Item attribute table extensions (`ItemAttributeValue`, +`ItemAttributeValueMapping`, `ItemAttributeValueSelection`) add a +`Primary` flag, surfaced through `ServObjectAttributeValues` and the +factbox page for per-subscription attribute tracking. + +`UpdateSubLinesTermDates` is a job-queue codeunit that iterates all +subscriptions and recalculates term dates (`UpdateServicesDates`), +committing after each record to minimize lock contention. + +## Things to know + +- Changing `End-User Customer No.` or `Bill-to Customer No.` triggers + `RecalculateServiceCommitments`, which recalculates prices for every + linked subscription line. On subscriptions with many lines this is + expensive. The subscription also checks whether lines are linked to + contracts and warns if so. +- `Quantity` changes also trigger recalculation. Setting a serial number + forces quantity to 1. +- `Type` and `Source No.` cannot be changed while subscription lines + exist -- the subscription errors to prevent orphaned billing data. +- The old `Item No.` field (field 20) is obsolete; use `Source No.` + (field 51) with the `Type` field instead. +- The table publishes 43+ integration events for extensibility. + `HideValidationDialog` suppresses confirmation prompts during + programmatic operations (import, posting). diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/docs/CLAUDE.md new file mode 100644 index 0000000000..4c4c3b1124 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/docs/CLAUDE.md @@ -0,0 +1,102 @@ +# Usage based billing + +Sub-module for metered/consumption-based billing that extends the core +subscription billing model. Instead of fixed recurring amounts, charges are +based on actual usage data imported from external suppliers (cloud providers, +IoT platforms, etc.). + +## Core concepts + +**Usage Data Supplier** (`UsageDataSupplier.Table.al`) -- master record for +who provides usage data. Each supplier has a Type enum that drives which +`"Usage Data Processing"` interface implementation handles its import +pipeline. The built-in type is `Generic`, backed by Data Exchange +Definitions for CSV parsing. + +**Usage Data Import** (`UsageDataImport.Table.al`) -- batch header for a +single import run. Tracks the current Processing Step and Processing Status, +with FlowFields counting dependent Blobs, Generic Import lines, Billing +records, and their respective error counts. The `ProcessUsageDataImport` +procedure is the central dispatcher: it routes each step to the correct +codeunit via `Codeunit.Run`. + +**Three pricing models** (defined in `UsageBasedPricing.Enum.al`): + +- **Usage Quantity** -- actual usage quantity x price. The subscription line + quantity is updated to match total usage, and `GetSalesPriceForItem` looks + up the customer-specific unit price. Cost is summed from the import data. +- **Fixed Quantity** -- subscription line quantity stays at its original + value. Billing only occurs if usage data exists for the period. Price is + calculated using the fixed quantity from the subscription line, not the + imported quantity. +- **Unit Cost Surcharge** -- unit price = imported unit cost x + (1 + surcharge %). The surcharge percentage lives on the subscription line + (`"Pricing Unit Cost Surcharge %"`). Product name can optionally replace + the subscription description on printed invoices (controlled by + `"Invoice Desc. (Surcharge)"` in Subscription Contract Setup). + +## How it works + +1. Set up a Usage Data Supplier with a Data Exchange Definition + (`GenericImportSettings.Table.al` links the two). +2. Upload a CSV/text file -- raw data stored in Usage Data Blob + (`UsageDataBlob.Table.al`), hashed for deduplication. +3. **Create Imported Lines** -- `ProcessUsageDataImport` (CU 8027) delegates + to the supplier's interface, which uses the Data Exchange Definition to + parse blobs into `"Usage Data Generic Import"` rows via + `GenericConnectorProcessing.Codeunit.al`. +4. **Process Imported Lines** -- validates each row: auto-creates Usage Data + Customers and Subscriptions if configured, resolves the Subscription + Header via Supplier References, checks subscription line validity. +5. **Create Usage Data Billing** -- `CreateUsageDataBilling.Codeunit.al` + collects subscription lines for each service object and creates one + `"Usage Data Billing"` record per subscription line per import line. + Metadata records track rebilling state. +6. **Process Usage Data Billing** -- `ProcessUsageDataBilling.Codeunit.al` + calculates customer prices per pricing model, updates subscription line + quantities and prices, then the billing records are ready for contract + invoicing. +7. **Create contract invoices** -- Usage Data Import collects affected + contracts and opens the billing document creation pages. Posted invoices + update Usage Data Billing with document references. + +## Supplier-subscription linking + +Supplier References (`UsageDataSupplierReference.Table.al`) map external IDs +to internal records. Three reference types exist: Customer, Subscription, +and Product. During import processing, these references are resolved to find +the correct subscription line. The `"Supplier Reference Entry No."` on +`"Subscription Line"` is the FK that connects external supplier data to +internal subscription lines. + +Subscriptions can be connected in two ways +(`ConnectToSOMethod.Enum.al`): + +- **Existing Service Commitments** -- reuses subscription lines already + marked `"Usage Based Billing" = true` +- **New Service Commitments** -- closes existing subscription lines at a + cutover date and creates new ones from subscription packages via + `ExtendContract` + +## Things to know + +- Usage-based subscription lines REQUIRE contract invoicing, not direct + sales invoicing. The billing integration uses the `"Recurring Billing"` + flag on document headers. +- Usage-based lines cannot have discounts -- `"Discount %" = 100` zeroes + out both price and amount in `CalculateUsageDataPrices`. +- The Processing Status enum (None/Ok/Error/Closed) and Processing Step + enum (None/Create Imported Lines/Process Imported Lines/Create Usage Data + Billing/Process Usage Data Billing) together track multi-stage progress. + Errors at each stage are independently trackable. +- Rebilling is supported: if usage data overlaps a previously invoiced + period, `UpdateRebilling` sets the Rebilling flag. On posting a credit + memo, `CreateAdditionalUsageDataBilling` creates new unbilled records so + the period can be re-invoiced. +- When a Sales/Purchase document is deleted, event subscribers in + `UsageBasedContrSubscribers.Codeunit.al` clear the document references + from Usage Data Billing (for invoices) or delete the billing records + entirely (for credit memos). +- The `"Usage Data Processing"` interface (`UsageDataProcessing.Interface.al`) + makes the import pipeline extensible. Custom connector types can implement + the five interface methods to integrate with non-CSV sources. diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/docs/business-logic.md b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/docs/business-logic.md new file mode 100644 index 0000000000..b2bdc9c7e6 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/docs/business-logic.md @@ -0,0 +1,177 @@ +# Business logic -- usage based billing + +## Import-to-bill pipeline + +```mermaid +flowchart TD + A[Upload CSV file] --> B[Usage Data Blob created] + B --> C{Processing Step} + C -->|Create Imported Lines| D[Parse via Data Exchange Definition] + D --> E[Usage Data Generic Import rows] + C -->|Process Imported Lines| F[Validate and resolve references] + F --> G{All rows valid?} + G -->|Yes| H[Status = Ok] + G -->|No| I[Status = Error per row] + C -->|Create Usage Data Billing| J[Create billing records per subscription line] + J --> K{Subscription lines found?} + K -->|Yes| L[Usage Data Billing records created] + K -->|No| M[Error: no contract / no usage-based lines] + C -->|Process Usage Data Billing| N[Calculate prices per pricing model] + N --> O[Update subscription line qty and prices] + O --> P[Ready for contract invoicing] + P --> Q[Create billing documents] + Q --> R[Posted invoice updates document refs] +``` + +## Import flow + +`ProcessUsageDataImport.Codeunit.al` (CU 8027) is the entry point. It +wraps `ImportAndProcessUsageData.Codeunit.al` (CU 8025) in +`Codeunit.Run` for error isolation -- if the inner codeunit fails, the +error is captured in the import's Reason field rather than propagating. + +CU 8025 dispatches based on `"Processing Step"`: + +- **Create Imported Lines**: resolves the supplier's type, gets the + `"Usage Data Processing"` interface, calls `ImportUsageData`. For the + generic connector (`GenericConnectorProcessing.Codeunit.al`), this reads + each Usage Data Blob with status Ok, creates a `"Data Exch."` record + from the blob's InStream, then runs the Data Exchange Definition's + XMLport and column mapping. `GenericImportMappings.Codeunit.al` (CU 8030) + is the mapping codeunit that initializes each Generic Import row from the + current Usage Data Import. + +- **Process Imported Lines**: again dispatches through the interface. + `GenericConnectorProcessing.ProcessUsageData` iterates each Generic + Import row and: + 1. Creates Usage Data Customers if `"Create Customers"` is enabled + 2. Creates Usage Data Subscriptions if `"Create Supplier Subscriptions"` is + enabled + 3. Resolves the subscription line via `GetServiceCommitmentForSubscription` + (looks up Supplier Reference -> Subscription Line with matching + `"Supplier Reference Entry No."`, preferring vendor-partner lines with + no end date) + 4. Validates the subscription line start date against the billing period + 5. Assigns the Subscription Header No. if the subscription is not + deinstalled + 6. Sets `"Service Object Availability"` (Connected / Available / + Not Available) + +## Billing record creation + +`CreateUsageDataBilling.Codeunit.al` (CU 8023) handles the +**Create Usage Data Billing** step. For each Generic Import row: + +1. `CollectServiceCommitments` gathers all subscription lines on the + service object that have `"Usage Based Billing" = true` and an end date + >= the subscription end date (or no end date). +2. `CheckServiceCommitments` verifies at least one line exists and all + have a contract assigned. If not, the Generic Import row gets an error. +3. `CreateUsageDataBillingFromTempServiceCommitments` creates one Usage + Data Billing record per subscription line. For vendor partners or when + `"Unit Price from Import"` is false, unit price and amount are zeroed + (they'll be calculated in the next step). `UpdateRebilling` checks + metadata for overlapping invoiced periods. `InsertMetadata` stores the + original invoiced-to date for future rebilling reversal. + +If billing records already exist for an import (retry scenario), the user +is prompted whether to retry only failed Generic Import lines. + +## Pricing calculation + +`ProcessUsageDataBilling.Codeunit.al` (CU 8026) handles the +**Process Usage Data Billing** step. Two phases: + +### Phase 1 -- calculate customer prices + +`CalculateCustomerUsageDataBillingPrice` runs for each customer-partner +billing record: + +- **Unit Price from Import**: uses the imported unit price and amount + directly. +- **Usage Quantity**: calls `GetSalesPriceForItem` to look up the customer + price list price for the item, then `UnitPriceForPeriod` scales it to the + billing period. Amount = scaled unit price x abs(quantity). +- **Fixed Quantity**: same as Usage Quantity but uses the subscription + line's original quantity (from `CalcFields`) instead of imported quantity. +- **Unit Cost Surcharge**: unit price = unit cost x (1 + surcharge% / 100). + Amount = unit price x quantity. + +The result is rounded to the currency's unit-amount rounding precision. +Negative quantities flip the amount sign. + +### Phase 2 -- update subscription lines + +`ProcessServiceCommitment` runs once per unique subscription line entry no.: + +- **Usage Quantity**: sums all billing record quantities and cost amounts + for the charge end date, updates the service object's quantity to the + total. If rebilling, adds back the original object quantity. +- **Fixed Quantity**: no quantity change (the whole point). +- **Unit Cost Surcharge**: sums cost amounts and calculates average unit + cost. + +For vendor-partner lines, unit price is set equal to unit cost. The service +object quantity is updated (which triggers recalculation of subscription +lines via the validate trigger). Then the subscription line's price, unit +cost, and calculation base amount are updated, with currency exchange +conversion if needed. + +## Error handling + +Errors are tracked at three levels: + +1. **Usage Data Import** -- `"Processing Status"` and `Reason` blob. + FlowFields aggregate error counts from child tables. +2. **Usage Data Generic Import** -- per-row `"Processing Status"` and + `Reason`. Common errors: missing subscription reference, deinstalled + service object, subscription line start date after billing period. +3. **Usage Data Billing** -- per-record `"Processing Status"` and `Reason`. + Common errors: no contract assigned, no billing data found. + +Each level can be corrected independently. After fixing the cause, the +user can re-run the processing step -- the pipeline skips rows with +status Ok or Closed. + +## Billing integration + +Usage Data Billing records feed into the standard contract billing +proposal through event subscribers in +`UsageBasedContrSubscribers.Codeunit.al` (CU 8028): + +- **Document creation**: `CollectCustomerContractsAndCreateInvoices` / + `CollectVendorContractsAndCreateInvoices` on Usage Data Import gather + affected contract nos. and line nos., then open the billing document + creation pages with pre-applied filters. +- **Posting**: `OnAfterPostSalesDoc` / `OnAfterPostPurchaseDoc` subscribers + update each Usage Data Billing record with the posted document no. and + type. For credit memos, `CreateAdditionalUsageDataBilling` clones the + billing record with cleared document fields so the period can be + re-invoiced. `SetMetadataAsInvoiced` marks metadata records. +- **Deletion**: When sales/purchase documents or lines are deleted, + subscribers either clear document values (invoices) or delete billing + records entirely (credit memos). When billing lines are deleted, the + billing line entry no. is zeroed on related usage data billing records. +- **Billing line archival**: When billing lines move to archive (during + posting), the `"Billing Line Entry No."` on Usage Data Billing is updated + to point to the archive entry. + +## Extension points + +Integration events for customization: + +- `OnAfterCollectServiceCommitments` -- add or filter subscription lines + before billing records are created +- `OnAfterCreateUsageDataBillingFromTempSubscriptionLine` -- modify billing + records after creation +- `OnUsageBasedPricingElseCaseOnCalculateCustomerUsageDataBillingPrice` -- + implement custom pricing models +- `OnUsageBasedPricingElseCaseOnProcessSubscriptionLine` -- custom + subscription line update logic for new pricing models +- `OnBeforeProcessUsageDataBilling` / `OnAfterProcessUsageDataBilling` -- + pre/post hooks on the billing processing step +- `OnBeforeCalculateUsageDataUnitPriceForPeriod` / + `OnAfterCalculateUsageDataUnitPriceForPeriod` -- observe or override the + period-scaled unit price calculation +- `OnAfterImportUsageDataBlobToUsageDataGenericImport` -- post-import hook + per blob diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/docs/data-model.md b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/docs/data-model.md new file mode 100644 index 0000000000..317d26edfb --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/docs/data-model.md @@ -0,0 +1,115 @@ +# Data model -- usage based billing + +## Import pipeline + +```mermaid +erDiagram + "Usage Data Supplier" ||--|{ "Usage Data Import" : "provides data" + "Usage Data Import" ||--|{ "Usage Data Blob" : "raw files" + "Usage Data Import" ||--|{ "Usage Data Generic Import" : "parsed lines" + "Usage Data Import" ||--|{ "Usage Data Billing" : "billing records" +``` + +**Usage Data Supplier** (table 8014) -- master record keyed by `No.`. The +`Type` field (enum `"Usage Data Supplier Type"`) drives interface dispatch. +`"Vendor No."` links to a Vendor for cost-side billing. `"Unit Price from +Import"` controls whether customer prices come from the imported data or are +calculated from subscription line pricing. + +**Usage Data Import** (table 8013) -- batch header, auto-increment PK. The +`"Processing Step"` / `"Processing Status"` pair tracks pipeline progress. +FlowFields count dependent records at each stage: `"No. of Usage Data Blobs"`, +`"No. of Imported Lines"`, `"No. of Imported Line Errors"`, +`"No. of Usage Data Billing"`, `"No. of UD Billing Errors"`. Reason is +stored as both a preview Text(80) and a full Blob for long error messages. +The `ProcessUsageDataImport` procedure dispatches each processing step to +the appropriate codeunit. + +**Usage Data Blob** (table 8011) -- raw file storage. The `Data` Blob holds +the uploaded file content. A `"Data Hash Value"` (HMACMD5) enables +deduplication. `"Import Status"` tracks whether the blob was successfully +read. + +**Usage Data Generic Import** (table 8018) -- one row per parsed usage +record. Contains supplier-side identifiers (`"Customer ID"`, +`"Supp. Subscription ID"`, `"Product ID"`), billing period dates, financial +amounts (Cost, Price, Quantity, Amount), and a currency field. The +`"Service Object Availability"` enum indicates whether the row has been +matched to a Subscription Header (Not Available / Available / Connected). +Fields Text1-3 and Decimal1-3 provide extensibility slots for custom data. +The `"Data Exch. Entry No."` links back to the Data Exchange framework +during import. + +**Usage Data Billing** (table 8006) -- final billing record linked to a +contract line. The `Partner` enum (Customer/Vendor) plus conditional +TableRelation on `"Subscription Contract No."` and +`"Subscription Contract Line No."` creates a polymorphic FK to either +Customer or Vendor contract lines. Document tracking fields +(`"Document Type"`, `"Document No."`, `"Document Line No."`, +`"Billing Line Entry No."`) are populated when the billing record is +attached to an invoice. The `Rebilling` flag indicates overlap with a +previously invoiced period. A secondary key on +`(Import Entry No., Subscription Header No., Subscription Line Entry No., Partner, Document Type, Charge End Date)` +with `SumIndexFields = Quantity, Amount` enables efficient aggregation. + +**Usage Data Billing Metadata** (table 8021, Access = Internal) -- +audit/tracking table for rebilling. Stores `"Original Invoiced to Date"` so +the subscription line's `"Next Billing Date"` can be reverted if billing +records are deleted. The `Invoiced` flag is set when the parent document is +posted. `"Billing Document Type"` and `"Billing Document No."` are +FlowFields from Usage Data Billing. + +## Supplier-subscription linking + +```mermaid +erDiagram + "Usage Data Supplier" ||--|{ "Usage Data Supplier Reference" : "maps to" + "Usage Data Supplier" ||--|{ "Usage Data Supp. Subscription" : "has" + "Usage Data Supp. Subscription" }o--|| "Subscription Header" : "links to" +``` + +**Usage Data Supplier Reference** (table 8015) -- flexible mapping between +external IDs and internal records. The `Type` enum (Customer / Subscription +/ Product) categorizes each reference. The `"Supplier Reference"` is +always lowercased on validate for case-insensitive matching. A secondary key +on `(Supplier No., Supplier Reference, Type)` optimizes lookups. + +**Usage Data Supp. Subscription** (table 8016) -- represents a supplier-side +subscription. Links to a BC Subscription Header and Subscription Line via +`"Subscription Header No."` and `"Subscription Line Entry No."`. The +`"Connect to Sub. Header No."` / `"Connect to Sub. Header Method"` / +`"Connect to Sub. Header at Date"` fields drive the service object +connection wizard. When method is `"Existing Service Commitments"`, the code +validates that usage-based subscription lines already exist. When +`"New Service Commitments"`, existing lines are closed at the specified date +and new ones created via `ExtendContract`. + +**Usage Data Supp. Customer** (table 8012) -- maps a supplier's customer ID +to a BC Customer No. When a Customer No. is assigned, it optionally +propagates to all matching Usage Data Subscriptions. The +`"Supplier Reference Entry No."` links back to the reference table. + +**Generic Import Settings** (table 8017) -- per-supplier configuration for +the generic connector. `"Data Exchange Definition"` controls CSV parsing. +`"Create Customers"` and `"Create Supplier Subscriptions"` auto-create +records during processing. `"Additional Processing"` and +`"Process without UsageDataBlobs"` handle edge cases. + +## Key design decisions + +- **Conditional foreign keys on Usage Data Billing**: The `Partner` enum + drives TableRelation to either Customer or Vendor contract lines and + document tables. This avoids splitting into two tables at the cost of + complex conditional TableRelation definitions. +- **Processing Status on multiple tables**: Both Generic Import and Billing + tables carry their own Processing Status. Errors must be checked at both + levels -- the Import header aggregates errors via FlowFields. +- **Blob storage for raw data**: Keeping original files in Usage Data Blob + allows re-processing if the Data Exchange Definition changes. +- **Supplier Reference indirection**: Rather than storing external IDs + directly on subscription lines, the Supplier Reference table provides a + normalized mapping layer that supports multiple reference types per + supplier. +- **Metadata for rebilling**: Usage Data Billing Metadata records the + original invoiced-to date so that deleting a billing record can correctly + revert the subscription line's Next Billing Date. diff --git a/src/Apps/W1/Subscription Billing/App/Vendor Contracts/docs/CLAUDE.md b/src/Apps/W1/Subscription Billing/App/Vendor Contracts/docs/CLAUDE.md new file mode 100644 index 0000000000..ad3aa98d4d --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/Vendor Contracts/docs/CLAUDE.md @@ -0,0 +1,72 @@ +# Vendor contracts + +Vendor contracts mirror customer contracts but for the incoming (cost) side of +subscription billing. They track what you owe vendors for subscriptions you +resell to customers. + +## Core tables + +**VendorSubscriptionContract (8063)** is the header. It follows the standard +BC buy-from / pay-to vendor pattern: setting the buy-from vendor cascades +contact, address, and payment defaults. Changing pay-to triggers currency, +payment terms, payment method, and dimension recalculation. The table +publishes about 20 IntegrationEvents covering vendor validation, address +copy, contact resolution, dimensions, and initialization. + +**VendSubContractLine (8065)** is structurally identical to its customer +counterpart. Each line links to one Subscription Line via +`Subscription Line Entry No.` and uses the same `Closed` flag for +soft-delete. It also has the `Planned Sub. Line exists` FlowField. +Like customer lines, `CreateServiceObjectWithServiceCommitment()` supports +manual line entry. Deletion is blocked by unreleased vendor contract +deferrals and linked usage data billing records. + +## Key differences from customer contracts + +- **No harmonized billing.** The contract type does not control billing + date alignment -- there are no `Billing Base Date` or + `Default Billing Rhythm` fields. +- **No renewal support.** Contract renewal (sales quotes, planned + subscription lines from renewal) is a customer-side concept. Vendor + lines can be *included* in a renewal as linked lines via + `FilterServCommVendFromServCommCust` in `SubContractRenewalMgt`, but + the vendor contract itself has no renewal workflow. +- **No customer price group.** The customer contract has a + `Customer Price Group` field that feeds pricing; the vendor contract + has no equivalent. +- **Simpler line merge.** Vendor contract line merging skips the Customer + Reference and Serial No. checks that the customer side enforces. + +## Contract type and deferrals + +The `Contract Type` field links to `Subscription Contract Type` and +controls the `Create Contract Deferrals` default. The obsolete +`Without Contract Deferrals` field is being replaced by the inverted +`Create Contract Deferrals` boolean (pending removal in v30). + +## Currency handling + +Currency changes on the vendor contract header prompt exchange rate +selection and recalculate all linked Subscription Line amounts via +`UpdateAndRecalculateServiceCommitmentCurrencyData`. When currency is +cleared, `ResetVendorServiceCommitmentCurrencyFromLCY` reverts lines to +LCY values. + +## Dimension cascading + +Dimension changes on the header propagate to all Subscription Lines +assigned to the contract after user confirmation. The dimension update +also flows to unreleased vendor contract deferrals via +`UpdateDimensionsInDeferrals`. + +## Gotchas + +- When assigning Subscription Lines to a vendor contract with a different + currency, the system forces exchange rate selection and recalculates + prices -- this is a one-way conversion. +- Vendor contract lines have no `OnAfterInitFromSubscriptionLine` event + (unlike customer lines), so customizing line initialization requires + subscribing elsewhere. +- Deleting a vendor contract cascades deletion to all its lines + (with trigger), which in turn disconnects or deletes the underlying + Subscription Lines. diff --git a/src/Apps/W1/Subscription Billing/App/docs/business-logic.md b/src/Apps/W1/Subscription Billing/App/docs/business-logic.md new file mode 100644 index 0000000000..5bff207e32 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/docs/business-logic.md @@ -0,0 +1,111 @@ +# Business logic + +## Billing flow + +The billing pipeline has three stages: proposal creation, document creation, and posting. Each stage can run independently or be chained through automation. + +**Billing proposal creation** (`BillingProposal.Codeunit.al`, codeunit 8062). `CreateBillingProposal` takes a billing template code, billing date, and optional billing-to date. It loads the template, applies any stored contract filters, then iterates over matching contracts. For each contract, it finds subscription lines where `Next Billing Date <= BillingDate` and creates `Billing Line` records. The billing line captures the billing period (from/to), amounts calculated from the subscription line's pricing, and a reference back to the contract and subscription. Before creating new billing lines, the process deletes any existing lines flagged with `Update Required`, which handles stale proposals after contract changes. For usage-based subscription lines, the process checks for pending `Usage Data Billing` records rather than using the standard date-based calculation. + +**Document creation** (`CreateBillingDocuments.Codeunit.al`, codeunit 8060). This codeunit runs against billing lines that have no document yet (`Document Type = None`). It branches on Partner (Customer/Vendor) and on grouping (per contract or per customer/vendor). For customer billing, it creates sales invoices -- one per contract or one per customer, depending on the billing template's `Customer Document per` setting. For vendor billing, it creates purchase invoices similarly. Each billing line becomes a sales/purchase line with the subscription line's invoicing item, amounts, and billing period text. The codeunit also supports automatic posting of created documents via the `PostDocuments` flag. + +**Billing automation** (`AutoContractBilling.Codeunit.al`, codeunit 8014). This is a thin job-queue entry handler. It reads the `Billing Template` from the job queue's `Record ID to Process`, then calls `BillingTemplate.BillContractsAutomatically()` which chains proposal creation and document creation. Only customer billing templates support automation -- the `BillingTemplate.Automation` field enforces `Partner = Customer`. + +```mermaid +flowchart TD + A[Billing Template] --> B{Partner?} + B -->|Customer| C[Find Customer Contracts] + B -->|Vendor| D[Find Vendor Contracts] + C --> E[Filter Subscription Lines by Next Billing Date] + D --> E + E --> F{Usage-based?} + F -->|Yes| G[Check Usage Data Billing records] + F -->|No| H[Calculate billing period from dates] + G --> I[Create Billing Lines] + H --> I + I --> J{Create documents?} + J -->|Yes| K{Grouping?} + K -->|Per contract| L[One invoice per contract] + K -->|Per partner| M[One invoice per customer/vendor] + L --> N{Post documents?} + M --> N + N -->|Yes| O[Post sales/purchase invoices] + N -->|No| P[Done - manual posting] +``` + +## Billing corrections + +`BillingCorrection.Codeunit.al` handles credit memos. When a posted contract invoice needs correction, the system creates credit memo billing lines from the archived billing lines, then generates a credit memo document. The credit memo reverses the original billing period and resets the subscription line's `Next Billing Date` backward so the corrected period can be re-billed. + +## Sales document integration + +`SalesDocuments.Codeunit.al` (codeunit 8063) subscribes to sales posting events to bridge between standard BC sales flow and subscription billing. When a sales order with subscription items is posted, it creates `Subscription Header` and `Subscription Line` records from the posted `Sales Subscription Line` records. Subscription items are treated as "invoiced on shipment" -- the shipment creates the subscription, but the actual invoicing happens through the contract billing pipeline, not through the sales invoice. + +`SalesSubscriptionLineMgmt.Codeunit.al` (codeunit 8069) handles the sales-side lifecycle. When a sales line for a subscription item is inserted, it auto-creates `Sales Subscription Line` records from the item's assigned subscription packages. It also handles the "Assign Service Commitments" page for adding non-standard packages to a sales line. + +## Price updates + +Price updates follow a template-driven proposal-and-execute pattern, using the `Contract Price Update` interface. + +`PriceUpdateManagement.Codeunit.al` (codeunit 8009) orchestrates the process. `CreatePriceUpdateProposal` loads the `Price Update Template`, resolves its `Price Update Method` enum to the corresponding interface implementation, and calls three interface methods: `SetPriceUpdateParameters`, `ApplyFilterOnServiceCommitments`, and `CreatePriceUpdateProposal`. The filtering is sophisticated -- it applies default filters (excluding usage-based lines, excluding lines with existing planned subscription lines), then layers on template-specific filters stored as blobs for contracts, subscriptions, and subscription lines. + +Three implementations exist: + +- **Calculation Base by %** (`CalculationBaseByPerc.Codeunit.al`) -- adjusts the `Calculation Base Amount` by the template's `Update Value %`, then recalculates the price from the new base +- **Price by %** (`PriceByPercent.Codeunit.al`) -- adjusts the `Price` directly by the percentage +- **Recent Item Prices** (`RecentItemPrice.Codeunit.al`) -- pulls the current price from BC price lists for the subscription's invoicing item, ignoring the percentage value + +`ProcessPriceUpdate.Codeunit.al` (codeunit 8013) executes the approved proposal. For each price update line, it checks whether the update can take effect immediately or must be deferred. If the "Perform Update On" date is after the subscription line's next billing date, or an unposted document exists, or the next billing date is before the next price update date, it creates a `Planned Subscription Line` instead of updating the current line. Otherwise, it applies the new prices directly to the subscription line. + +```mermaid +flowchart TD + A[Price Update Template] --> B[Resolve interface implementation] + B --> C[Apply filters on subscription lines] + C --> D[Create price update proposal lines] + D --> E{User approves?} + E -->|Yes| F[Process each update line] + F --> G{Can take effect now?} + G -->|Yes| H[Update subscription line directly] + G -->|No| I[Create Planned Subscription Line] + I --> J[Activates when current period ends] +``` + +## Contract renewals + +Renewal is a three-step process: create renewal lines, generate sales quotes, post quotes to extend terms. + +`SubContractRenewalMgt.Codeunit.al` (codeunit 8003) starts from a customer contract. `StartContractRenewalFromContract` opens the `Contract Renewal Selection` page, which shows renewable contract lines (those with end dates and renewal terms). Selected lines feed into `CreateSubContractRenewal.Codeunit.al`. + +`CreateSubContractRenewal.Codeunit.al` (codeunit 8002) validates each renewal line (must have end date, renewal term, no existing planned subscription line, not already in a sales quote), then creates a sales quote. The sales quote contains lines for the subscription items being renewed, with prices and terms from the current subscription lines. + +`PostSubContractRenewal.Codeunit.al` handles what happens when the renewal sales quote is posted (converted to a sales order and shipped). It creates `Planned Subscription Line` records with `Type Of Update = Contract Renewal` that extend the subscription line's term dates. When the current period ends and the planned line activates, the subscription line's dates are updated. + +Renewal only works for customer contracts -- vendor subscription lines do not support the renewal workflow. + +## Usage-based billing + +Usage-based billing imports metered consumption data from external suppliers and feeds it into the standard billing pipeline. + +`ImportAndProcessUsageData.Codeunit.al` (codeunit 8025) dispatches to the supplier's connector via the `Usage Data Processing` interface. It has two processing steps: "Create Imported Lines" (import raw data into the staging table) and "Process Imported Lines" (validate, match to subscriptions, and prepare for billing). The interface allows different supplier types to implement their own import/processing logic. + +`GenericConnectorProcessing.Codeunit.al` implements the generic connector. It reads `Usage Data Generic Import` records, creates `Usage Data Supp. Customer` and `Usage Data Supp. Subscription` mappings, validates that subscription lines exist and dates are correct, and prepares records for billing creation. + +`CreateUsageDataBilling.Codeunit.al` takes processed import records and creates `Usage Data Billing` entries linked to subscription lines and contracts. These billing entries are then picked up by the billing proposal process (which checks for usage-based billing lines with `Document Type = None`) and flow into the standard billing line and document creation pipeline. + +`ProcessUsageDataBilling.Codeunit.al` handles post-processing of usage data billing records after invoices are posted, updating statuses and connecting billing entries to posted documents. + +## Deferral release + +`ContractDeferralsRelease.Report.al` (report 8051) is a processing-only report that releases deferred revenue and cost. It takes a posting date and a "Post Until Date", filters customer and vendor contract deferrals that have not been released yet and fall within the date range, then creates and posts G/L journal entries that move amounts from deferral accounts to revenue/cost accounts. + +The release process handles both customer deferrals (revenue side) and vendor deferrals (cost side) in sequence. It uses `General Journal Template` and `General Journal Batch` from the setup, creates temp journal lines, and posts them through the standard BC general journal posting engine. + +## Subscription creation from sales + +When a sales order with subscription items is posted, the system creates subscriptions automatically. The flow is: + +1. Sales line insert triggers `SalesSubscriptionLineMgmt.AddSalesServiceCommitmentsForSalesLine` which creates `Sales Subscription Line` records from the item's subscription packages +2. On sales order posting, `SalesDocuments.Codeunit.al` intercepts the posting events +3. For each posted line with subscription items, it creates a `Subscription Header` and `Subscription Line` records +4. Subscription lines reference their assigned contracts, completing the link from sales to billing + +Negative quantities (returns) skip subscription creation and show a notification instead. diff --git a/src/Apps/W1/Subscription Billing/App/docs/data-model.md b/src/Apps/W1/Subscription Billing/App/docs/data-model.md new file mode 100644 index 0000000000..91c45094a6 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/docs/data-model.md @@ -0,0 +1,109 @@ +# Data model + +## Subscriptions and subscription lines + +A Subscription (`Subscription Header`, table 8057) represents the physical service or product being tracked -- a software license, a maintenance contract, a leased device. It carries the dual-customer model: an "End-User Customer No." (who uses the service) and a "Bill-to Customer No." (who pays for it). These can differ, enabling scenarios where a parent company pays for subsidiaries. + +Subscription Lines (`Subscription Line`, table 8059) are the billing rules attached to a subscription. Each line belongs to one subscription, references a package and template, and carries its own pricing (calculation base, percentage, price, discount), billing rhythm, term dates (start, end, initial term, extension term, notice period), and contract assignment. The Partner enum on each line determines whether it bills through a customer or vendor contract. + +The template hierarchy flows downward: a `Sub. Package Line Template` (8054) defines default billing parameters. A `Subscription Package` (8055) groups related `Subscription Package Line` records (8056), each referencing a template. Packages are assigned to items via `Item Subscription Package` (which also carries a `Price Group` for customer-specific pricing). When items are sold, the package lines become `Sales Subscription Line` records, and upon posting, become actual `Subscription Line` records on a `Subscription Header`. + +`Planned Subscription Line` (8002) is a future-dated copy of a subscription line that stores pending changes from price updates or contract renewals. It activates (replaces the current line values) when the current billing period ends. + +```mermaid +erDiagram + "Sub. Package Line Template" ||--o{ "Subscription Package Line" : "referenced by" + "Subscription Package" ||--o{ "Subscription Package Line" : "contains" + "Subscription Header" ||--o{ "Subscription Line" : "has" + "Subscription Line" ||--o| "Planned Subscription Line" : "may have" +``` + +## Contracts + +Contracts are the billing vehicles. `Customer Subscription Contract` (8052) and `Vendor Subscription Contract` (8063) are structurally similar but separate tables -- they share no common base table. Each has its own contract line table: `Cust. Sub. Contract Line` (8062) and `Vend. Sub. Contract Line` (8065). + +A contract line links a contract to a specific subscription line. The contract line carries references to both the `Subscription Header No.` and the `Subscription Line Entry No.`, creating a bridge between the physical service and the billing agreement. Contract lines also have a `Closed` boolean field -- the soft-delete pattern that preserves closed lines for audit rather than removing them. + +`Subscription Contract Type` (8053) categorizes contracts and controls two important behaviors: whether harmonized billing is enabled (aligning all lines to a common billing date, customer contracts only) and whether contract deferrals are created by default. + +`Subscription Contract Setup` (8051) is the singleton setup table holding number series, invoice text formatting, dimension configuration, and the overdue date formula. + +```mermaid +erDiagram + "Customer Subscription Contract" ||--o{ "Cust. Sub. Contract Line" : "contains" + "Cust. Sub. Contract Line" }o--|| "Subscription Line" : "bills" + "Subscription Contract Type" ||--o{ "Customer Subscription Contract" : "categorizes" + "Subscription Header" ||--o{ "Cust. Sub. Contract Line" : "referenced by" +``` + +```mermaid +erDiagram + "Vendor Subscription Contract" ||--o{ "Vend. Sub. Contract Line" : "contains" + "Vend. Sub. Contract Line" }o--|| "Subscription Line" : "bills" + "Subscription Contract Type" ||--o{ "Vendor Subscription Contract" : "categorizes" +``` + +## Billing + +The `Billing Line` table (8061) is the central staging area between contracts and posted documents. A billing line is created by the billing proposal process and references a contract, a contract line, and the subscription line being billed. The Partner enum on each billing line drives conditional foreign keys to either customer or vendor contracts, documents, and partners. + +Key fields on billing lines: `Billing from` / `Billing to` define the period, `Document Type` / `Document No.` link to the created sales/purchase document, and `Update Required` flags stale proposals that need regeneration. `Billing Line Archive` (8069) preserves billing lines after posting. + +`Billing Template` (8060) controls the billing proposal: which partner type, contract filters, date formulas, grouping rules, and automation settings. The `Automation` enum supports `None` or `Create Billing Proposal and Documents` -- the latter drives fully automated billing via `Auto Contract Billing` (codeunit 8014) through job queue entries. + +```mermaid +erDiagram + "Billing Template" ||--o{ "Billing Line" : "generates" + "Billing Line" }o--|| "Subscription Line" : "bills for" + "Billing Line" }o--|| "Cust. Sub. Contract Line" : "belongs to" + "Billing Line Archive" }o--|| "Billing Line" : "archives" +``` + +## Deferrals + +`Cust. Sub. Contract Deferral` (8066) and `Vend. Sub. Contract Deferral` (8067) store deferred revenue/cost entries created during invoice posting. When a contract invoice is posted with deferrals enabled, the posting redirects amounts to a deferral G/L account instead of the revenue/cost account. The `Contract Deferrals Release` report (8051) then releases these deferrals monthly by posting journal entries that move amounts from the deferral account to the actual revenue/cost account. + +Deferral enablement cascades: `Subscription Contract Type."Create Contract Deferrals"` sets the default, `Sub. Package Line Template."Create Contract Deferrals"` can override per template (`No` or `Contract-dependent`), and the resulting value flows down through package lines to subscription lines. + +## Price updates + +`Price Update Template` (8003) defines a price update strategy: which partner, which contracts/subscriptions to include (via blob-stored filters), the update method (enum-driven interface), the percentage, and date formulas. `Sub. Contr. Price Update Line` (8004) holds the calculated proposal -- old vs. new prices per subscription line. + +The three update methods implement the `Contract Price Update` interface: `Calculation Base By Perc` adjusts the calculation base amount by a percentage, `Price By Percent` adjusts the price directly, and `Recent Item Price` pulls the current item price from BC price lists. + +## Usage-based billing + +Usage-based billing has a multi-stage data pipeline. `Usage Data Supplier` (8014) defines the external supplier and its connector type. `Usage Data Import` (8013) represents a batch import operation. `Usage Data Blob` (8005) stores raw imported data. `Usage Data Generic Import` (8011) holds parsed line-level data. `Usage Data Billing` (8006) maps processed usage data to subscription lines and contracts for billing. + +Supporting tables track the supplier's customer and subscription mappings: `Usage Data Supp. Customer` maps supplier customer IDs to BC customers, and `Usage Data Supp. Subscription` maps supplier subscription IDs to BC subscriptions. `Usage Data Supplier Reference` provides a generic reference mapping layer. + +The `Usage Data Processing` interface allows different connector types (the `Usage Data Supplier Type` enum) to implement their own import and processing logic. + +```mermaid +erDiagram + "Usage Data Supplier" ||--o{ "Usage Data Import" : "receives" + "Usage Data Import" ||--o{ "Usage Data Blob" : "stores raw data" + "Usage Data Import" ||--o{ "Usage Data Generic Import" : "parsed into" + "Usage Data Generic Import" ||--o{ "Usage Data Billing" : "creates" + "Usage Data Billing" }o--|| "Subscription Line" : "bills for" +``` + +## Contract analysis and renewal + +`Sub. Contr. Analysis Entry` (8019) provides a denormalized view of contract data for analytical reporting. + +`Sub. Contract Renewal Line` (8070) stages contract lines for renewal. The renewal process creates sales quotes from expiring contract lines, and posting those quotes extends subscription terms by creating planned subscription lines that activate when the current period ends. + +## Key design decisions + +**Subscription-contract decoupling.** A subscription can exist without any contract, and one subscription can be billed through multiple contracts. This enables scenarios like splitting a subscription's vendor cost and customer revenue into separate contracts. + +**Partner-driven conditional foreign keys.** Rather than creating separate customer and vendor billing tables, a single `Billing Line` table uses the `Service Partner` enum to switch its `TableRelation` for partner numbers, contract numbers, and document references. This keeps the billing engine unified but means all queries must include a Partner filter. + +**Template hierarchy with override at each level.** Templates set defaults, package lines can override, and subscription lines can be further adjusted. This cascade means the "source of truth" for any given subscription line's billing parameters is the line itself, not any template. + +**Dual-customer model.** The `Subscription Header` has both an End-User Customer (who uses the service, drives address/ship-to) and a Bill-to Customer (who receives invoices). This is distinct from the contract's Sell-to/Bill-to, which controls the billing document's customer. + +**Soft-delete on contract lines.** The `Closed` boolean on `Cust. Sub. Contract Line` and `Vend. Sub. Contract Line` hides lines from active views without deleting them. This preserves billing history and prevents gaps in the audit trail. + +**Multi-stage usage data pipeline.** Raw supplier data goes through import, parsing, validation, and billing stages with explicit status tracking at each step. This allows manual intervention and error correction at any stage before billing lines are created. diff --git a/src/Apps/W1/Subscription Billing/App/docs/extensibility.md b/src/Apps/W1/Subscription Billing/App/docs/extensibility.md new file mode 100644 index 0000000000..9947f61d61 --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/docs/extensibility.md @@ -0,0 +1,112 @@ +# Extensibility + +The app publishes 320 integration events across 62 files. Rather than listing every event, this document groups them by what you would want to customize. The most event-dense objects are `Customer Subscription Contract` (50 events), `Subscription Header` (43 events), `Create Billing Documents` (27 events), `Vendor Subscription Contract` (20 events), `Sales Documents` (15 events), and `Billing Proposal` (12 events). + +## Customizing billing document creation + +`CreateBillingDocuments.Codeunit.al` (27 events) is the most extensible codeunit. Key hook points: + +- **Before/after the entire process**: `OnBeforeCreateBillingDocuments`, `OnAfterProcessBillingLines` -- intercept or modify the billing line set before documents are generated +- **Document header creation**: `OnCreateSalesDocumentsPerContractBeforeTempBillingLineFindSet`, `OnCreateSalesDocumentsPerCustomerBeforeTempBillingLineFindSet`, and equivalent purchase events -- filter or reorder billing lines before they become document lines +- **Line insertion**: events around `InsertSalesLineFromTempBillingLine` and `InsertPurchaseLineFromTempBillingLine` -- modify amounts, descriptions, or dimensions on the created document lines +- **Grouping decisions**: events on `IsNewSalesHeaderNeeded` / `IsNewPurchaseHeaderNeeded` -- override when a new document header is created vs. appending to an existing one +- **Post-creation**: events after document creation completes, useful for logging, notifications, or chaining to other processes + +## Customizing billing proposals + +`BillingProposal.Codeunit.al` (12 events) exposes hooks at key decision points: + +- **Before processing contract subscription lines**: `OnBeforeProcessContractSubscriptionLines` -- add extra filters or skip specific subscription lines +- **After processing**: `OnAfterProcessContractSubscriptionLines` -- post-process created billing lines +- **Skip logic**: `OnCheckSkipSubscriptionLineOnElse` -- custom logic for whether to skip a subscription line during proposal creation +- **Line calculation**: `OnBeforeInsertBillingLineUpdateBillingLine` -- modify billing line amounts or periods before insertion +- **Billing-to date changes**: events around `OnCreateBillingProposalBeforeApplyFilterToContract` -- adjust filtering before contracts are enumerated + +## Customizing sales document handling + +`SalesDocuments.Codeunit.al` (15 events) hooks into sales posting to control subscription creation: + +- **Subscription creation from posted sales**: events around when subscriptions and subscription lines are created from posted sales lines +- **Delete handling**: events when sales invoices/credit memos linked to billing lines are deleted +- **Contract renewal integration**: events for the renewal-specific posting flow + +`SalesSubscriptionLineMgmt.Codeunit.al` (8 events) controls how sales subscription lines are generated: + +- `OnBeforeAddSalesServiceCommitmentsForSalesLine` -- prevent or modify auto-creation of subscription lines on sales lines +- Events around package selection and insertion -- customize which packages are applied to which sales lines + +## Customizing subscription header behavior + +`SubscriptionHeader.Table.al` (43 events) publishes events on virtually every field validation and key operations: + +- **Customer changes**: `OnValidateEndUserCustomerNoAfterInit`, `OnValidateBillToCustomerNoOnAfterConfirmed` -- customize behavior when End-User or Bill-to customer changes +- **Address synchronization**: events around copying address fields from customer records +- **Subscription line recalculation**: events triggered when header changes cascade to subscription lines (e.g., customer change, currency change) +- **Insert/modify/delete lifecycle**: standard lifecycle events for custom validation or side effects + +## Customizing subscription line behavior + +`SubscriptionLine.Table.al` (11 events) covers pricing and contract assignment: + +- **Price calculation**: events around `CalculatePrice` and `CalculateServiceAmount` -- override pricing logic +- **Contract assignment**: events when subscription lines are linked to or unlinked from contracts +- **Date calculations**: events around billing date, term date, and cancellation date computation + +## Customizing customer contracts + +`CustomerSubscriptionContract.Table.al` (50 events) is the most event-dense table, covering: + +- **Customer field changes**: Sell-to and Bill-to customer validation chains with events at each step +- **Address field synchronization**: events for copying and updating address fields from customer records +- **Dimension management**: events around dimension creation and defaulting +- **Contract lifecycle**: events on insert, modify, delete, and status changes +- **Contact integration**: events around updating sell-to and bill-to contact references + +`CustSubContractLine.Table.al` (6 events) covers contract line operations including subscription line connection/disconnection. + +## Customizing vendor contracts + +`VendorSubscriptionContract.Table.al` (20 events) follows the same pattern as customer contracts but with vendor-specific fields: + +- **Vendor field changes**: Buy-from and Pay-to vendor validation events +- **Address synchronization**: events for vendor address fields +- **Lifecycle events**: insert, modify, delete + +## Customizing price updates + +The `Contract Price Update` interface (`ContractPriceUpdate.Interface.al`) is the primary extensibility point for custom pricing methods. Implement the four interface methods (`SetPriceUpdateParameters`, `ApplyFilterOnServiceCommitments`, `CreatePriceUpdateProposal`, `CalculateNewPrice`) and add a new value to the `Price Update Method` enum. + +Each of the three built-in implementations publishes one event: `OnAfterFilterSubscriptionLineOnAfterGetAndApplyFiltersOnSubscriptionLine` in `PriceUpdateManagement.Codeunit.al` for post-filter customization. + +## Customizing usage-based billing + +The `Usage Data Processing` interface (`UsageDataProcessing.Interface.al`) allows custom connector implementations. Implement the five interface methods (`ImportUsageData`, `ProcessUsageData`, `TestUsageDataImport`, `FindAndProcessUsageDataImport`, `SetUsageDataImportError`) and add a new value to the `Usage Data Supplier Type` enum. + +`CreateUsageDataBilling.Codeunit.al` (3 events) and `ProcessUsageDataBilling.Codeunit.al` (8 events) provide hooks into the billing creation and post-processing stages. `UsageDataImport.Table.al` (3 events) and `UsageDataBilling.Table.al` (3 events) cover data-level customization. + +## Customizing contract renewals + +`CreateSubContractRenewal.Codeunit.al` (12 events) is heavily extensible: + +- **Validation**: `OnAfterCheckSubContractRenewalLine`, `OnAfterRunCheck` -- add custom validation rules +- **Sales quote creation**: events around sales header and line creation from renewal lines +- **Batch processing**: events for batch renewal operations + +`PostSubContractRenewal.Codeunit.al` (5 events) covers the posting side -- events around planned subscription line creation and term extension. + +## Customizing deferrals + +`CustomerDeferralsMngmt.Codeunit.al` (3 events) and `VendorDeferralsMngmt.Codeunit.al` (1 event) hook into the deferral creation during invoice posting. `ContractDeferralsRelease.Report.al` (2 events) allows customization of the monthly release process. + +## Customizing imports + +The import codeunits (`CreateSubscriptionHeader`, `CreateSubscriptionLine`, `CreateCustSubContract`, `CreateSubContractLine`) each publish 3-4 events around their creation and validation logic, allowing customization of the bulk import workflow. + +## Customizing UI and reports + +Several pages publish events for adding custom actions or modifying behavior: + +- `CustomerContract.Page.al` (3 events), `ExtendContract.Page.al` (3 events) -- customer contract page customization +- `ContractRenewalSelection.Page.al` (4 events) -- renewal selection customization +- `RecurringBilling.Page.al` (1 event) -- billing page customization +- Sales report extensions (2 events each) -- customize subscription data on sales order confirmations and quotes diff --git a/src/Apps/W1/Subscription Billing/App/docs/patterns.md b/src/Apps/W1/Subscription Billing/App/docs/patterns.md new file mode 100644 index 0000000000..d2a57a27ae --- /dev/null +++ b/src/Apps/W1/Subscription Billing/App/docs/patterns.md @@ -0,0 +1,123 @@ +# Patterns + +## Partner-polymorphism via enum + +The `Service Partner` enum (8053) has two values: `Customer` and `Vendor`. This single enum drives the entire app's dual-nature behavior. Rather than using inheritance or separate table hierarchies for customer vs. vendor operations, every shared table carries a Partner field and uses conditional `TableRelation` to point at the correct target. + +For example, in `BillingLine.Table.al`: + +``` +field(10; "Partner No."; Code[20]) +{ + TableRelation = if (Partner = const(Customer)) Customer else + if (Partner = const(Vendor)) Vendor; +} +field(20; "Subscription Contract No."; Code[20]) +{ + TableRelation = if (Partner = const(Customer)) "Customer Subscription Contract" else + if (Partner = const(Vendor)) "Vendor Subscription Contract"; +} +``` + +This pattern repeats in `Billing Line`, `Usage Data Billing`, `Sub. Contr. Price Update Line`, `Sub. Contract Renewal Line`, `Subscription Line`, and `Subscription Package Line`. The practical consequence is that all queries against these tables must always include a Partner filter -- without it, foreign key lookups become ambiguous. + +The enum is explicitly `Extensible = false`, which means third-party extensions cannot add new partner types. The Customer/Vendor duality is baked into the architecture. + +This pattern trades simplicity of schema (one table instead of two) for complexity of logic (every operation must branch on Partner). The billing codeunits (`CreateBillingDocuments`, `BillingProposal`) have explicit `case BillingLine.Partner of` blocks that duplicate logic for each partner type. + +## Conditional foreign keys + +A natural consequence of partner-polymorphism. The `Billing Line` table is the best example -- a single table serves both customer and vendor billing. Its `Document No.` field has a four-way conditional `TableRelation`: + +``` +TableRelation = + if ("Document Type" = const(Invoice), Partner = const(Customer)) "Sales Header" + else if ("Document Type" = const("Credit Memo"), Partner = const(Customer)) "Sales Header" + else if ("Document Type" = const("Credit Memo"), Partner = const(Vendor)) "Purchase Header" + else if ("Document Type" = const(Invoice), Partner = const(Vendor)) "Purchase Header"; +``` + +This is a common BC pattern but used more extensively here than in most apps. The `Subscription Line` table also uses it for `Subscription Contract No.` and `Subscription Contract Line No.`, switching between customer and vendor contract tables. + +## Template hierarchy + +The configuration flows through four levels, each adding context and allowing overrides: + +1. **Sub. Package Line Template** (8054) -- defines billing defaults: invoicing via (Sales/Contract), invoicing item, calculation base type and percentage, billing base period, discount flag, deferral settings, usage-based billing settings +2. **Subscription Package Line** (8056) -- references a template and adds Partner, billing rhythm, and can override any template field +3. **Sales Subscription Line** -- created from package lines when an item is sold, adds sales-specific context (customer pricing, quantities, line-specific overrides) +4. **Subscription Line** (8059) -- the final record, carries all accumulated values plus runtime state (next billing date, contract assignment, term dates) + +The cascade works by copying fields down. `SubscriptionPackageLine.Template.OnValidate` copies all fields from the template. `SalesSubscriptionLineMgmt.InsertSalesServiceCommitmentFromServiceCommitmentPackage` copies fields from the package line. Each level can modify the copied values. + +The key design decision: the subscription line is the source of truth, not the template. Once values cascade down, changes to the template do not propagate to existing subscription lines. This is intentional -- live billing data must be stable. + +## Planned subscription lines + +When a change to a subscription line cannot take effect immediately (because an unposted document exists, the change date is after the next billing date, or the next billing date is before the next price update), the system creates a `Planned Subscription Line` (8002) instead of modifying the current line. + +`PlannedSubscriptionLine` has the same field structure as `Subscription Line` (created via `TransferFields`). It stores the future values along with a `Type Of Update` enum (`Price Update` or `Contract Renewal`) and a `Perform Update On` date. + +The planned line activates when the current billing period completes. `ProcessPriceUpdate.Codeunit.al` checks three conditions in `ShouldPlannedServiceCommitmentBeCreated`: + +- Is the "Perform Update On" date after the subscription line's next billing date? +- Does an unposted document exist for this subscription line? +- Is the next billing date before the next price update date? + +If any are true, a planned line is created rather than direct modification. This prevents mid-period pricing changes that would create inconsistencies between billed and unbilled amounts. + +The `Subscription Line` table has a `Planned Sub. Line exists` FlowField (CalcFormula exists check against `Planned Subscription Line`) that acts as a guard -- many operations check this flag and refuse to proceed if a planned line already exists. + +## Soft-delete pattern + +Contract lines (`Cust. Sub. Contract Line`, `Vend. Sub. Contract Line`) are never physically deleted during normal operation. Instead, they have a `Closed` boolean field. When a subscription line's billing is complete or the line is terminated, the contract line is marked as Closed. + +Closed lines appear on a separate "Closed Lines" FastTab on the contract page (via `ClosedCustContLineSubp.Page.al`) and are filtered out of the active lines subpage. This preserves the complete billing history and prevents gaps in audit trails. + +The `Cust. Sub. Contract Line` table's `CheckAndDisconnectContractLine` method handles the disconnection logic when line type or item changes, but the physical record persists. + +## Dual-customer model + +`Subscription Header` carries two customer references: + +- **End-User Customer No.** (field 2) -- the customer who uses the service. Drives the subscription's address, ship-to code, and salesperson. Displayed as "Customer No." in the UI. +- **Bill-to Customer No.** (field 4) -- the customer who receives invoices. Copied from the End-User Customer's `Bill-to Customer No.` if one is set. + +This mirrors BC's sales document pattern (Sell-to vs. Bill-to) but is independent of it. The contract also has its own Sell-to/Bill-to pair, creating a three-level customer chain: Subscription End-User -> Contract Sell-to -> Contract Bill-to. In practice, these are often the same customer, but the model supports scenarios where they differ. + +When the End-User Customer changes, the system checks whether subscription lines are linked to contracts and forces confirmation. It also recalculates pricing since customer price groups may differ. + +## Hash/update-required pattern + +The `Billing Line` table has an `Update Required` boolean field. This flags billing lines that have become stale because the underlying contract or subscription data changed after the billing proposal was created. + +When `CreateBillingProposal` runs, it first calls `DeleteUpdateRequiredBillingLines` to clear any flagged lines, then recreates them with current data. The `Update Required` flag prevents users from creating documents from stale billing lines -- the flag must be cleared (by regenerating the proposal) before `CreateBillingDocuments` will process them. + +This pattern avoids the complexity of detecting what changed and patching billing lines. Instead, it invalidates and regenerates. The UI shows flagged lines prominently so users know to regenerate. + +## Interface-based strategy pattern + +Two interfaces enable pluggable algorithms: + +**Contract Price Update** (`ContractPriceUpdate.Interface.al`) -- implemented by the `Price Update Method` enum (8003) with three implementations: `Calculation Base By Perc`, `Price By Percent`, and `Recent Item Price`. The enum uses AL's `implements` keyword to bind interface methods to specific codeunits. This is clean and follows BC's recommended pattern for extensible business logic. + +**Usage Data Processing** (`UsageDataProcessing.Interface.al`) -- implemented by the `Usage Data Supplier Type` enum. Allows different supplier connectors to define how raw usage data is imported and processed. The `Generic` type provides a built-in implementation via `GenericConnectorProcessing.Codeunit.al`. + +Both interfaces use the enum-implements pattern rather than subscriber-based extensibility. This means extending the set of implementations requires extending the enum (which is extensible, unlike `Service Partner`). + +## Legacy terminology + +Throughout the codebase, internal object names use the old terminology while captions and external-facing text use the new: + +- Table name `Subscription Header`, old page name `"Service Objects"`, caption `'Subscription'` +- Table name `Subscription Line`, old page name `"Service Commitments List"`, caption `'Subscription Line'` +- Template table `Sub. Package Line Template`, old page name `"Service Commitment Templates"` +- Package table `Subscription Package`, old page name `"Service Commitment Packages"` + +This split happened because table names in AL cannot change after publishing (they are the object identity), but captions and documentation can. Anyone searching the codebase should search for both naming conventions. + +## Event parameter conventions + +Most events pass the full record by reference (`var`), allowing subscribers to modify it. Some events also pass an `IsHandled` boolean that, when set to true, skips the publisher's default logic. This follows the standard BC event pattern but is used inconsistently -- some operations have comprehensive IsHandled support while others do not. + +Several large tables (`Customer Subscription Contract`, `Subscription Header`) publish events on nearly every field validation, which is thorough but creates a large surface area. In contrast, some important operations (like billing line amount calculation) have fewer hook points.