diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingProposal.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingProposal.Codeunit.al index 7c99bb7681..f00cf2b49c 100644 --- a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingProposal.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingProposal.Codeunit.al @@ -390,11 +390,17 @@ codeunit 8062 "Billing Proposal" UsageDataBilling.FindLast(); if UsageDataBilling.Rebilling or (UsageDataBilling."Usage Base Pricing" = Enum::"Usage Based Pricing"::"Usage Quantity") then BillingLine."Service Object Quantity" := UsageDataBilling.Quantity; - BillingLine."Unit Price" := BillingLine.Amount / BillingLine."Service Object Quantity"; + if BillingLine."Service Object Quantity" <> 0 then + BillingLine."Unit Price" := BillingLine.Amount / BillingLine."Service Object Quantity" + else + BillingLine."Unit Price" := UsageDataBilling."Unit Price"; BillingLine."Discount %" := ServiceCommitment."Discount %"; // Apply discount from Subscription Line BillingLine.Amount := BaseAmount * (1 - ServiceCommitment."Discount %" / 100); - BillingLine."Unit Cost" := UsageDataBilling."Cost Amount" / UsageDataBilling.Quantity; + if UsageDataBilling.Quantity <> 0 then + BillingLine."Unit Cost" := UsageDataBilling."Cost Amount" / UsageDataBilling.Quantity + else + BillingLine."Unit Cost" := UsageDataBilling."Unit Cost"; Currency.Initialize(ServiceCommitment."Currency Code"); Currency.TestField("Unit-Amount Rounding Precision"); BillingLine."Unit Cost (LCY)" := Round(CurrExchRate.ExchangeAmtFCYToLCY(ServiceCommitment."Currency Factor Date", ServiceCommitment."Currency Code", BillingLine."Unit Cost", ServiceCommitment."Currency Factor"), Currency."Unit-Amount Rounding Precision"); @@ -421,15 +427,9 @@ codeunit 8062 "Billing Proposal" BillingLine."Unit Cost" := Round(BillingLine."Unit Cost", Currency."Unit-Amount Rounding Precision"); BillingLine."Unit Cost (LCY)" := Round(BillingLine."Unit Cost (LCY)", GLSetup."Unit-Amount Rounding Precision"); - BillingLine.Amount := CalculateBillingLineServiceAmount(BillingLine); - BillingLine.Amount := Round(BillingLine.Amount, Currency."Amount Rounding Precision"); + BillingLine.Amount := Round(BillingLine."Unit Price" * BillingLine."Service Object Quantity" * (1 - BillingLine."Discount %" / 100), Currency."Amount Rounding Precision"); end; - internal procedure CalculateBillingLineServiceAmount(var BillingLine: Record "Billing Line") ServiceAmount: Decimal - begin - BillingLine.TestField("Service Object Quantity"); - ServiceAmount := BillingLine."Unit Price" * BillingLine."Service Object Quantity" * (1 - BillingLine."Discount %" / 100); - end; local procedure UpdateBillingLineFromServiceCommitment(var BillingLine: Record "Billing Line"; ServiceCommitment: Record "Subscription Line") var diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al index d1ebe6ad08..560cce7b09 100644 --- a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al @@ -317,8 +317,6 @@ codeunit 8060 "Create Billing Documents" var UsageDataBilling: Record "Usage Data Billing"; ServiceCommitment: Record "Subscription Line"; - NewSalesLineQuantity: Decimal; - NewSalesLineAmount: Decimal; begin if not ServiceCommitment.Get(BillingLine."Subscription Line Entry No.") then exit; @@ -328,15 +326,21 @@ codeunit 8060 "Create Billing Documents" if not ServiceCommitment.IsUsageDataBillingFound(UsageDataBilling, BillingLine."Billing from", BillingLine."Billing to") then exit; - UsageDataBilling.CalcSums(Amount, Quantity); - NewSalesLineQuantity := SalesLine.Quantity; - NewSalesLineAmount := UsageDataBilling.Amount; UsageDataBilling.FindLast(); if UsageDataBilling.Rebilling then - NewSalesLineQuantity := UsageDataBilling.Quantity; + SalesLine.Validate(Quantity, UsageDataBilling.Quantity); + if SalesLine.Quantity = 0 then begin + UsageDataBilling.SetFilter(Quantity, '<>0'); + if UsageDataBilling.FindLast() then + SalesLine.Validate(Quantity, UsageDataBilling.Quantity); + end; - SalesLine.Validate(Quantity, NewSalesLineQuantity); - SalesLine.Validate("Unit Price", SalesLine.GetSalesDocumentSign() * NewSalesLineAmount / NewSalesLineQuantity); + UsageDataBilling.SetRange(Quantity); + UsageDataBilling.CalcSums(Amount); + if SalesLine.Quantity <> 0 then + SalesLine.Validate("Unit Price", SalesLine.GetSalesDocumentSign() * UsageDataBilling.Amount / SalesLine.Quantity) + else + SalesLine.Validate("Unit Price", UsageDataBilling."Unit Price"); SalesLine.Validate("Line Discount %", ServiceCommitment."Discount %"); end; @@ -419,8 +423,6 @@ codeunit 8060 "Create Billing Documents" var UsageDataBilling: Record "Usage Data Billing"; ServiceCommitment: Record "Subscription Line"; - NewPurchaseLineQuantity: Decimal; - NewPurchaseLineAmount: Decimal; begin if not ServiceCommitment.Get(BillingLine."Subscription Line Entry No.") then exit; @@ -430,15 +432,21 @@ codeunit 8060 "Create Billing Documents" if not ServiceCommitment.IsUsageDataBillingFound(UsageDataBilling, BillingLine."Billing from", BillingLine."Billing to") then exit; - UsageDataBilling.CalcSums("Cost Amount", Quantity); - NewPurchaseLineQuantity := PurchLine.Quantity; - NewPurchaseLineAmount := UsageDataBilling."Cost Amount"; UsageDataBilling.FindLast(); if UsageDataBilling.Rebilling then - NewPurchaseLineQuantity := UsageDataBilling.Quantity; + PurchLine.Validate(Quantity, UsageDataBilling.Quantity); + if PurchLine.Quantity = 0 then begin + UsageDataBilling.SetFilter(Quantity, '<>0'); + if UsageDataBilling.FindLast() then + PurchLine.Validate(Quantity, UsageDataBilling.Quantity); + end; - PurchLine.Validate(Quantity, NewPurchaseLineQuantity); - PurchLine.Validate("Direct Unit Cost", PurchLine.GetPurchaseDocumentSign() * NewPurchaseLineAmount / NewPurchaseLineQuantity); + UsageDataBilling.SetRange(Quantity); + UsageDataBilling.CalcSums("Cost Amount"); + if PurchLine.Quantity <> 0 then + PurchLine.Validate("Direct Unit Cost", PurchLine.GetPurchaseDocumentSign() * UsageDataBilling."Cost Amount" / PurchLine.Quantity) + else + PurchLine.Validate("Direct Unit Cost", 0); PurchLine.Validate("Line Discount %", ServiceCommitment."Discount %"); end; diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/PurchaseDocuments.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/PurchaseDocuments.Codeunit.al index 1f972da5c4..62ebb801c8 100644 --- a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/PurchaseDocuments.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/PurchaseDocuments.Codeunit.al @@ -104,6 +104,41 @@ codeunit 8066 "Purchase Documents" end end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnBeforeCheckHeaderPostingType, '', false, false)] + local procedure SkipInvoiceOrShipFlagCheckForSubscriptionBillingOnBeforeCheckHeaderPostingType(var PurchaseHeader: Record "Purchase Header"; var IsHandled: Boolean) + begin + // Allow posting without Invoice or Ship flags being set for subscription billing documents + if PurchaseHeader."Recurring Billing" then + IsHandled := true; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Release Purchase Document", OnCodeOnAfterPurchLineSetFilters, '', false, false)] + local procedure SkipQuantityCheckForSubscriptionBillingOnCodeOnAfterPurchLineSetFilters(PurchaseHeader: Record "Purchase Header"; var PurchaseLine: Record "Purchase Line"; var IsHandled: Boolean) + begin + // Skip quantity check for subscription billing documents + if PurchaseHeader."Recurring Billing" then + IsHandled := true; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnBeforeCalcInvoice, '', false, false)] + local procedure ForceInvoiceCreationForZeroQtyDocumentOnBeforeCalcInvoice(var PurchHeader: Record "Purchase Header"; var NewInvoice: Boolean; var IsHandled: Boolean) + begin + // For subscription billing documents with zero quantity lines, force invoice creation + // so that the posted invoice header is always generated + if PurchHeader."Recurring Billing" then begin + NewInvoice := true; + IsHandled := true; + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnPostPurchLineOnAfterSetEverythingInvoiced, '', false, false)] + local procedure SetEverythingInvoicedForZeroQtyDocumentOnAfterSetEverythingInvoiced(PurchaseHeader: Record "Purchase Header"; var EverythingInvoiced: Boolean) + begin + // Treat zero-qty subscription billing lines as fully invoiced so BC cleans up the source document + if PurchaseHeader."Recurring Billing" then + EverythingInvoiced := true; + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnBeforeDeleteAfterPosting, '', false, false)] local procedure PurchasePostOnBeforePurchaseLineDeleteAll(var PurchaseHeader: Record "Purchase Header"; var PurchInvHeader: Record "Purch. Inv. Header"; var PurchCrMemoHdr: Record "Purch. Cr. Memo Hdr.") var diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/SalesDocuments.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/SalesDocuments.Codeunit.al index 23b1ed3731..43ae2c71a4 100644 --- a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/SalesDocuments.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/SalesDocuments.Codeunit.al @@ -428,6 +428,41 @@ codeunit 8063 "Sales Documents" TempSalesLine.Validate("Qty. to Invoice", 0); end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnBeforeCheckHeaderPostingType, '', false, false)] + local procedure SkipInvoiceOrShipFlagCheckForSubscriptionBillingOnBeforeCheckHeaderPostingType(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean) + begin + // Allow posting without Invoice or Ship flags being set for subscription billing documents + if SalesHeader."Recurring Billing" then + IsHandled := true; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Release Sales Document", OnBeforeSalesLineFind, '', false, false)] + local procedure SkipQuantityCheckForSubscriptionBillingOnBeforeSalesLineFind(var SalesHeader: Record "Sales Header"; var SalesLine: Record "Sales Line"; var IsHandled: Boolean) + begin + // Skip quantity check for subscription billing documents + if SalesHeader."Recurring Billing" then + IsHandled := true; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnBeforeCalcInvoice, '', false, false)] + local procedure ForceInvoiceCreationForZeroQtyDocumentOnBeforeCalcInvoice(SalesHeader: Record "Sales Header"; var TempSalesLineGlobal: Record "Sales Line" temporary; var NewInvoice: Boolean; var IsHandled: Boolean) + begin + // For subscription billing documents with zero quantity lines, force invoice creation + // so that the posted invoice header is always generated + if SalesHeader."Recurring Billing" then begin + NewInvoice := true; + IsHandled := true; + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnPostSalesLineOnAfterSetEverythingInvoiced, '', false, false)] + local procedure SetEverythingInvoicedForZeroQtyDocumentOnAfterSetEverythingInvoiced(SalesHeader: Record "Sales Header"; var EverythingInvoiced: Boolean) + begin + // Treat zero-qty subscription billing lines as fully invoiced so BC cleans up the source document + if SalesHeader."Recurring Billing" then + EverythingInvoiced := true; + end; + local procedure CheckResetValueForServiceCommitmentItems(var TempSalesLine: Record "Sales Line") ResetValueForServiceCommitmentItems: Boolean var ContractRenewalMgt: Codeunit "Sub. Contract Renewal Mgt."; diff --git a/src/Apps/W1/Subscription Billing/App/Sales Service Commitments/Tables/SalesSubscriptionLine.Table.al b/src/Apps/W1/Subscription Billing/App/Sales Service Commitments/Tables/SalesSubscriptionLine.Table.al index 770c353ef5..939c4e5252 100644 --- a/src/Apps/W1/Subscription Billing/App/Sales Service Commitments/Tables/SalesSubscriptionLine.Table.al +++ b/src/Apps/W1/Subscription Billing/App/Sales Service Commitments/Tables/SalesSubscriptionLine.Table.al @@ -466,13 +466,17 @@ table 8068 "Sales Subscription Line" if Amount > MaxServiceAmount then Error(ServiceAmountIncreaseErr, FieldCaption(Amount), Format(MaxServiceAmount)); "Discount Amount" := Round(MaxServiceAmount - Amount, Currency."Amount Rounding Precision"); - "Discount %" := Round(100 - (Amount / MaxServiceAmount * 100), 0.00001); + if MaxServiceAmount <> 0 then + "Discount %" := Round(100 - (Amount / MaxServiceAmount * 100), 0.00001); end else begin Amount := Round((Price * SalesLine.Quantity), Currency."Amount Rounding Precision"); if CalledByFieldNo = FieldNo("Discount %") then "Discount Amount" := Round(Amount * "Discount %" / 100, Currency."Amount Rounding Precision"); if CalledByFieldNo = FieldNo("Discount Amount") then - "Discount %" := Round("Discount Amount" / Amount * 100, 0.00001); + if Amount <> 0 then + "Discount %" := Round("Discount Amount" / Amount * 100, 0.00001) + else + "Discount %" := 0; Amount := Round((Price * SalesLine.Quantity) - "Discount Amount", Currency."Amount Rounding Precision"); if Amount > MaxServiceAmount then Error(ServiceAmountIncreaseErr, FieldCaption(Amount), Format(MaxServiceAmount)); diff --git a/src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al b/src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al index 65a22de399..40f4b62d41 100644 --- a/src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al +++ b/src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al @@ -857,7 +857,6 @@ table 8059 "Subscription Line" if MaxServiceAmount <> 0 then "Discount %" := Round(100 - (Amount / MaxServiceAmount * 100), 0.00001); end else begin - ServiceObject.TestField(Quantity); Amount := Price * ServiceObject.Quantity; if not "Usage Based Billing" then Amount := Round(Amount, Currency."Amount Rounding Precision"); @@ -867,7 +866,10 @@ table 8059 "Subscription Line" "Discount Amount" := Round("Discount Amount", Currency."Amount Rounding Precision"); end; if CalledByFieldNo = FieldNo("Discount Amount") then - "Discount %" := Round("Discount Amount" / Amount * 100, 0.00001); + if Amount <> 0 then + "Discount %" := Round("Discount Amount" / Amount * 100, 0.00001) + else + "Discount %" := 0; if ("Discount Amount" > MaxServiceAmount) and ("Discount Amount" <> 0) then Error(CannotBeGreaterThanErr, FieldCaption("Discount Amount"), Format(MaxServiceAmount)); Amount := Amount - "Discount Amount"; diff --git a/src/Apps/W1/Subscription Billing/App/Service Objects/Tables/SubscriptionHeader.Table.al b/src/Apps/W1/Subscription Billing/App/Service Objects/Tables/SubscriptionHeader.Table.al index 48fe4d7096..2ee59e6941 100644 --- a/src/Apps/W1/Subscription Billing/App/Service Objects/Tables/SubscriptionHeader.Table.al +++ b/src/Apps/W1/Subscription Billing/App/Service Objects/Tables/SubscriptionHeader.Table.al @@ -369,14 +369,13 @@ table 8057 "Subscription Header" { Caption = 'Quantity'; InitValue = 1; - NotBlank = true; AutoFormatType = 0; DecimalPlaces = 0 : 5; trigger OnValidate() begin - if Quantity <= 0 then - Error(QtyZeroOrNegativeErr); + if Quantity < 0 then + Error(QtyNegativeErr); if (Quantity <> 1) and ("Serial No." <> '') then Error(SerialQtyErr); Rec.ArchiveServiceCommitments(); @@ -948,7 +947,7 @@ table 8057 "Subscription Header" SkipBillToContact: Boolean; SkipInsertServiceCommitments: Boolean; ConfirmChangeQst: Label 'Do you want to change %1?', Comment = '%1 = a Field Caption like Currency Code'; - QtyZeroOrNegativeErr: Label 'The quantity cannot be zero or negative.'; + QtyNegativeErr: Label 'The quantity cannot be negative.'; EndUserCustomerTxt: Label 'End-User Customer'; BillToCustomerTxt: Label 'Bill-to Customer'; SerialQtyErr: Label 'Only Subscriptions with quantity 1 may have a serial number.'; @@ -2216,14 +2215,18 @@ table 8057 "Subscription Header" local procedure GetRecalculateLinesDialog(ChangedFieldName: Text): Text var RecalculateLinesQst: Label 'If you change %1, the existing Subscription Lines prices will be recalculated.\\Do you want to continue?', Comment = '%1: FieldCaption'; - RecalculateLinesFromQuantityQst: Label 'If you change %1, only the Amount for existing service commitments will be recalculated.\\Do you want to continue?', Comment = '%1= Changed Field Name.'; + RecalculateLinesFromQuantityQst: Label 'If you change the %1, the amount for open subscription lines will be recalculated.\\Do you want to continue?', Comment = '%1: FieldCaption'; + PauseSubscriptionQst: Label 'If you set the %1 to 0, the billing will pause until further notice but the subscription lines are not terminated.\\Do you want to continue?', Comment = '%1: FieldCaption'; RecalculateLinesFromVariantCodeQst: Label 'The %1 has been changed.\\Do you want to update the price?', Comment = '%1= Changed Field Name.'; begin case ChangedFieldName of Rec.FieldName(Rec."Variant Code"): exit(StrSubstNo(RecalculateLinesFromVariantCodeQst, ChangedFieldName)); Rec.FieldName(Rec.Quantity): - exit(StrSubstNo(RecalculateLinesFromQuantityQst, ChangedFieldName)); + if Rec.Quantity = 0 then + exit(StrSubstNo(PauseSubscriptionQst, ChangedFieldName)) + else + exit(StrSubstNo(RecalculateLinesFromQuantityQst, ChangedFieldName)); else exit(StrSubstNo(RecalculateLinesQst, ChangedFieldName)); end; diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/GenericConnectorProcessing.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/GenericConnectorProcessing.Codeunit.al index d7a62d3902..4f53827fa0 100644 --- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/GenericConnectorProcessing.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/GenericConnectorProcessing.Codeunit.al @@ -11,7 +11,6 @@ codeunit 8033 "Generic Connector Processing" implements "Usage Data Processing" ProcessingSetupErr: Label 'You must specify either a reading/writing XMLport or a reading/writing codeunit.'; UsageDataLinesProcessingErr: Label 'Errors were found while processing the Usage Data Lines.'; NoDataFoundErr: Label 'No data found for processing step %1.', Comment = '%1 = Name of the processing step'; - UsageDataWithZeroQuantityCannotBeProcessedErr: Label 'Usage data with Quantity 0 cannot be processed.'; NoServiceObjectErr: Label 'The %1 ''%2'' is not linked to an %3.', Comment = '%1 = Table name, %2 = Entry number, %3 = Table name'; ServiceObjectProvisionEndDateErr: Label 'The %1 ''%2'' is deinstalled.', Comment = '%1 = Table name, %2 = Entry number'; ReferenceNotFoundErr: Label 'For %1 ''%2'' no linked %3 was found.', Comment = '%1 = Field name, %2 = Entry description, %3 = Table name'; @@ -85,7 +84,6 @@ codeunit 8033 "Generic Connector Processing" implements "Usage Data Processing" if UsageDataGenericImport.FindSet() then repeat UsageDataGenericImport.Validate("Processing Status", Enum::"Processing Status"::None); - ErrorIfUsageDataGenericImportQuantityIsZero(UsageDataGenericImport); GenericImportSettings.Get(UsageDataImport."Supplier No."); CreateUsageDataCustomers(GenericImportSettings, UsageDataGenericImport, UsageDataSupplierReference, UsageDataImport."Supplier No."); CreateUsageDataSubscriptions(GenericImportSettings, UsageDataGenericImport, UsageDataSupplierReference, UsageDataImport); @@ -160,14 +158,6 @@ codeunit 8033 "Generic Connector Processing" implements "Usage Data Processing" end; end; - local procedure ErrorIfUsageDataGenericImportQuantityIsZero(var UsageDataGenericImport: Record "Usage Data Generic Import") - begin - if UsageDataGenericImport.Quantity <> 0 then - exit; - UsageDataGenericImport."Processing Status" := UsageDataGenericImport."Processing Status"::Error; - UsageDataGenericImport.SetReason(UsageDataWithZeroQuantityCannotBeProcessedErr); - end; - local procedure CheckServiceCommitment(var UsageDataGenericImport: Record "Usage Data Generic Import"; var UsageDataImport: Record "Usage Data Import"; var ServiceCommitment: Record "Subscription Line") begin if ImportAndProcessUsageData.GetServiceCommitmentForSubscription(UsageDataImport."Supplier No.", UsageDataGenericImport."Supp. Subscription ID", ServiceCommitment) then diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/ProcessUsageDataBilling.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/ProcessUsageDataBilling.Codeunit.al index 6c956be7d1..79a8bab582 100644 --- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/ProcessUsageDataBilling.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/ProcessUsageDataBilling.Codeunit.al @@ -52,9 +52,6 @@ codeunit 8026 "Process Usage Data Billing" SubscriptionLineEntryNoList.Add(UsageDataBilling."Subscription Line Entry No."); ProcessServiceCommitment(UsageDataBilling); end; - - if UsageDataBilling.Partner = "Service Partner"::Customer then - HandleGracePeriod(UsageDataBilling); until UsageDataBilling.Next() = 0; end else begin UsageDataBilling.SetRange("Subscription Contract No."); @@ -163,19 +160,27 @@ codeunit 8026 "Process Usage Data Billing" "Usage Based Pricing"::"Usage Quantity": begin NewServiceObjectQuantity := CalculateTotalUsageBillingQuantity(LastUsageDataBilling, UsageDataImport."Entry No.", ServiceCommitment); - UnitCost := CalculateSumCostAmountFromUsageDataBilling(LastUsageDataBilling, UsageDataImport."Entry No.", ServiceCommitment) / NewServiceObjectQuantity; - if ServiceCommitment.IsPartnerCustomer() then - UnitPrice := CalculateSumAmountFromUsageDataBilling(LastUsageDataBilling, UsageDataImport."Entry No.", ServiceCommitment) / NewServiceObjectQuantity; + if NewServiceObjectQuantity <> 0 then begin + UnitCost := CalculateSumCostAmountFromUsageDataBilling(LastUsageDataBilling, UsageDataImport."Entry No.", ServiceCommitment) / NewServiceObjectQuantity; + if ServiceCommitment.IsPartnerCustomer() then + UnitPrice := CalculateSumAmountFromUsageDataBilling(LastUsageDataBilling, UsageDataImport."Entry No.", ServiceCommitment) / NewServiceObjectQuantity; + end else begin + UnitCost := 0; + UnitPrice := 0; + end; if LastUsageDataBilling.Rebilling then NewServiceObjectQuantity += ServiceObject."Quantity"; end; "Usage Based Pricing"::"Fixed Quantity": ; "Usage Based Pricing"::"Unit Cost Surcharge": - begin + if NewServiceObjectQuantity <> 0 then begin UnitCost := CalculateSumCostAmountFromUsageDataBilling(LastUsageDataBilling, UsageDataImport."Entry No.", ServiceCommitment) / NewServiceObjectQuantity; if ServiceCommitment.IsPartnerCustomer() then UnitPrice := CalculateSumAmountFromUsageDataBilling(LastUsageDataBilling, UsageDataImport."Entry No.", ServiceCommitment) / NewServiceObjectQuantity; + end else begin + UnitCost := 0; + UnitPrice := 0; end; else begin IsHandled := false; @@ -272,32 +277,6 @@ codeunit 8026 "Process Usage Data Billing" ServiceCommitment.Modify(false); end; - local procedure HandleGracePeriod(var UsageDataBilling: Record "Usage Data Billing") - var - UsageDataBilling2: Record "Usage Data Billing"; - ServiceObject: Record "Subscription Header"; - ServiceCommitment: Record "Subscription Line"; - CustomerContractLine: Record "Cust. Sub. Contract Line"; - begin - CustomerContractLine.Get(UsageDataBilling."Subscription Contract No.", UsageDataBilling."Subscription Contract Line No."); - CustomerContractLine.GetServiceCommitment(ServiceCommitment); - - if not ServiceObject.Get(ServiceCommitment."Subscription Header No.") then - exit; - if ServiceObject.Quantity <> 0 then - exit; - - UsageDataBilling2.SetRange(Partner, UsageDataBilling2.Partner::Customer); - UsageDataBilling2.SetRange("Subscription Line Entry No.", UsageDataBilling."Subscription Line Entry No."); - UsageDataBilling2.SetRange("Document Type", UsageDataBilling2."Document Type"::"Posted Invoice"); - if not UsageDataBilling2.IsEmpty then - exit; - - UsageDataBilling."Unit Price" := 0; - UsageDataBilling.Amount := 0; - UsageDataBilling.Modify(false); - end; - local procedure GetCustomerContractData(var CustomerContract: Record "Customer Subscription Contract"; var CustomerContractLine: Record "Cust. Sub. Contract Line"; var ServiceCommitment: Record "Subscription Line"; UsageDataBilling: Record "Usage Data Billing") begin CustomerContract.Get(UsageDataBilling."Subscription Contract No."); diff --git a/src/Apps/W1/Subscription Billing/Test/Billing/AutomatedBillingTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Billing/AutomatedBillingTest.Codeunit.al index 226b3fb05f..0d8dd2aaad 100644 --- a/src/Apps/W1/Subscription Billing/Test/Billing/AutomatedBillingTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Billing/AutomatedBillingTest.Codeunit.al @@ -77,6 +77,7 @@ codeunit 148455 "Automated Billing Test" var CustomerSubscriptionContract: Record "Customer Subscription Contract"; SubscriptionHeader: Record "Subscription Header"; + SubscriptionLine: Record "Subscription Line"; BillingTemplate: Record "Billing Template"; BillingLine: Record "Billing Line"; SalesHeader: Record "Sales Header"; @@ -87,8 +88,16 @@ codeunit 148455 "Automated Billing Test" // [GIVEN] A Billing Template with automation settings CreateBillingTemplateWithAutomation(BillingTemplate); - // [WHEN] Bill the contracts automatically + // [GIVEN] A contract with subscription lines starting today ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerSubscriptionContract, SubscriptionHeader, ''); + SubscriptionLine.SetRange("Subscription Header No.", SubscriptionHeader."No."); + SubscriptionLine.FindSet(); + repeat + SubscriptionLine.Validate("Subscription Line Start Date", Today()); + SubscriptionLine.Modify(true); + until SubscriptionLine.Next() = 0; + + // [WHEN] Bill the contracts automatically BillingTemplate.BillContractsAutomatically(); // [THEN] Verify that Sales Header is created for the billed contract @@ -118,8 +127,15 @@ codeunit 148455 "Automated Billing Test" CreateBillingTemplateWithAutomation(BillingTemplate); ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerSubscriptionContract, SubscriptionHeader, ''); - // [GIVEN]Remove Item UOM to cause error during billing + // [GIVEN] Subscription lines starting today SubscriptionLine.SetRange("Subscription Header No.", SubscriptionHeader."No."); + SubscriptionLine.FindSet(); + repeat + SubscriptionLine.Validate("Subscription Line Start Date", Today()); + SubscriptionLine.Modify(true); + until SubscriptionLine.Next() = 0; + + // [GIVEN] Remove Item UOM to cause error during billing SubscriptionLine.FindLast(); ItemUnitOfMeasure.Get(SubscriptionLine."Invoicing Item No.", SubscriptionHeader."Unit of Measure"); ItemUnitOfMeasure.Delete(); diff --git a/src/Apps/W1/Subscription Billing/Test/Billing/RecurringBillingDocsTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Billing/RecurringBillingDocsTest.Codeunit.al index 194870f5f6..ff94dd00b4 100644 --- a/src/Apps/W1/Subscription Billing/Test/Billing/RecurringBillingDocsTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Billing/RecurringBillingDocsTest.Codeunit.al @@ -511,6 +511,40 @@ codeunit 139687 "Recurring Billing Docs Test" SalesInvoiceHeader.TestField("Posting Description", 'Customer Subscription Contract ' + BillingLine."Subscription Contract No."); end; + [Test] + [HandlerFunctions('CreateCustomerBillingDocsContractPageHandler,MessageHandler')] + procedure PostRecurringBillingInvoiceWithZeroQuantity() + var + Customer: Record Customer; + PostedDocumentNo: Code[20]; + begin + // [SCENARIO] Sales Invoice with "Recurring Billing" = YES can be posted even when it has zero quantity lines + Initialize(); + + // [GIVEN] Create Customer Subscription Contract with subscription having quantity 0 + ContractTestLibrary.CreateCustomerInLCY(Customer); + ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); + ContractTestLibrary.DisableDeferralsForCustomerContract(CustomerContract, false); + ServiceObject.SetHideValidationDialog(true); + ServiceObject.Validate(Quantity, 0); + ServiceObject.Modify(true); + + // [GIVEN] Create billing proposal and billing documents - sales lines will have quantity 0 + ContractTestLibrary.CreateBillingProposal(BillingTemplate, Enum::"Service Partner"::Customer); + CreateBillingDocuments(false); + BillingLine.SetFilter("Document No.", '<>%1', ''); + BillingLine.FindFirst(); + SalesHeader.Get(Enum::"Sales Document Type"::Invoice, BillingLine."Document No."); + SalesHeader.TestField("Recurring Billing", true); + + // [WHEN] Post the Sales Invoice with zero quantity lines + PostedDocumentNo := LibrarySales.PostSalesDocument(SalesHeader, true, true); + + // [THEN] The Sales Invoice is posted successfully + SalesInvoiceHeader.Get(PostedDocumentNo); + SalesInvoiceHeader.TestField("Recurring Billing", true); + end; + [Test] [HandlerFunctions('CheckDialogConfirmHandler,ExchangeRateSelectionModalPageHandler,CreateVendorBillingDocsTestOpenPageHandler,MessageHandler')] procedure CheckVendorBillingProposalCanBeCreatedForPurchaseCrMemoExists() @@ -533,6 +567,42 @@ codeunit 139687 "Recurring Billing Docs Test" BillingProposal.CreateBillingProposalFromContract(VendorContract."No.", VendorContract.GetFilter("Billing Rhythm Filter"), "Service Partner"::Vendor); end; + [Test] + [HandlerFunctions('CreateVendorBillingDocsContractPageHandler,MessageHandler')] + procedure PostRecurringBillingPurchaseInvoiceWithZeroQuantity() + var + Vendor: Record Vendor; + PostedDocumentNo: Code[20]; + begin + // [SCENARIO] Purchase Invoice with "Recurring Billing" = YES can be posted even when it has zero quantity lines + Initialize(); + + // [GIVEN] Create Vendor Subscription Contract with subscription having quantity 0 + ContractTestLibrary.CreateVendorInLCY(Vendor); + ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Vendor."No."); + ContractTestLibrary.DisableDeferralsForVendorContract(VendorContract, false); + ServiceObject.SetHideValidationDialog(true); + ServiceObject.Validate(Quantity, 0); + ServiceObject.Modify(true); + + // [GIVEN] Create billing proposal and billing documents - purchase lines will have quantity 0 + ContractTestLibrary.CreateBillingProposal(BillingTemplate, Enum::"Service Partner"::Vendor); + CreateBillingDocuments(false); + BillingLine.SetFilter("Document No.", '<>%1', ''); + BillingLine.FindFirst(); + PurchaseHeader.Get(Enum::"Purchase Document Type"::Invoice, BillingLine."Document No."); + PurchaseHeader.TestField("Recurring Billing", true); + PurchaseHeader.Validate("Vendor Invoice No.", LibraryUtility.GenerateGUID()); + PurchaseHeader.Modify(false); + + // [WHEN] Post the Purchase Invoice with zero quantity lines + PostedDocumentNo := LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, true); + + // [THEN] The Purchase Invoice is posted successfully + PurchaseInvoiceHeader.Get(PostedDocumentNo); + PurchaseInvoiceHeader.TestField("Recurring Billing", true); + end; + [Test] [HandlerFunctions('CheckDialogConfirmHandler,ExchangeRateSelectionModalPageHandler,CreateVendorBillingDocsTestOpenPageHandler,MessageHandler')] procedure CheckVendorBillingProposalCanBeCreatedForPurchaseInvoiceExists() @@ -1294,7 +1364,7 @@ codeunit 139687 "Recurring Billing Docs Test" end; [Test] - [HandlerFunctions('MessageHandler,GetVendorContractLinesProducesCorrectAmountsDuringSelectionPageHandler,ExchangeRateSelectionModalPageHandler')] + [HandlerFunctions('GetVendorContractLinesProducesCorrectAmountsDuringSelectionPageHandler')] procedure GetVendorContractLinesProducesCorrectAmountsDuringSelection() var Item: Record Item; @@ -1306,7 +1376,7 @@ codeunit 139687 "Recurring Billing Docs Test" // [GIVEN] Setup Subscription with Subscription Line and assign it to Vendor Subscription Contract // [GIVEN] Create Purchase Invoice with Purchase Invoice Line ContractTestLibrary.DeleteAllContractRecords(); - ContractTestLibrary.CreateVendor(Vendor); + ContractTestLibrary.CreateVendorInLCY(Vendor); ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Vendor."No."); GetVendorContractServiceCommitment(VendorContract."No."); ServiceCommitment."Billing Rhythm" := ServiceCommitment."Billing Base Period"; @@ -1358,6 +1428,41 @@ codeunit 139687 "Recurring Billing Docs Test" asserterror BillingLine.FindFirst(); end; + [Test] + [HandlerFunctions('CreateCustomerBillingDocsContractPageHandler,ExchangeRateSelectionModalPageHandler,MessageHandler')] + procedure TestBillingDocumentWithZeroQuantity() + begin + // [SCENARIO] Billing documents are created with quantity 0 and amount 0 when subscription quantity is 0 + Initialize(); + + // [GIVEN] Create customer contract with subscription and set quantity to 0 + ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, ''); + ContractTestLibrary.DisableDeferralsForCustomerContract(CustomerContract, false); + ServiceObject.SetHideValidationDialog(true); + ServiceObject.Validate(Quantity, 0); + ServiceObject.Modify(true); + + // [WHEN] Create billing proposal and billing documents + ContractTestLibrary.CreateBillingProposal(BillingTemplate, Enum::"Service Partner"::Customer); + BillingLine.SetRange("Billing Template Code", BillingTemplate.Code); + BillingLine.SetRange("Subscription Header No.", ServiceObject."No."); + BillingLine.SetRange("Document No.", ''); + if not BillingLine.IsEmpty() then begin + CreateBillingDocuments(false); + + // [THEN] Sales line is created with quantity 0 + BillingLine.SetFilter("Document No.", '<>%1', ''); + if BillingLine.FindFirst() then begin + SalesHeader.Get(SalesHeader."Document Type"::Invoice, BillingLine."Document No."); + SalesLine.SetRange("Document Type", SalesHeader."Document Type"); + SalesLine.SetRange("Document No.", SalesHeader."No."); + SalesLine.FindFirst(); + Assert.AreEqual(0, SalesLine.Quantity, 'Sales Line quantity should be 0.'); + Assert.AreEqual(0, SalesLine."Line Amount", 'Sales Line amount should be 0.'); + end; + end; + end; + [Test] [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler,CreateBillingDocumentPageHandler')] procedure TestBillingLineOnCreateSinglePurchaseDocument() diff --git a/src/Apps/W1/Subscription Billing/Test/Billing/RecurringBillingTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Billing/RecurringBillingTest.Codeunit.al index 34969e5a44..c37f061b0d 100644 --- a/src/Apps/W1/Subscription Billing/Test/Billing/RecurringBillingTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Billing/RecurringBillingTest.Codeunit.al @@ -590,22 +590,6 @@ codeunit 139688 "Recurring Billing Test" end; [Test] - procedure CheckBillingLineServiceAmountCalculation() - var - ExpectedServiceAmount: Decimal; - begin - // [SCENARIO] Unit testing the function CalculateBillingLineServiceAmount from Codeunit BillingProposal - Initialize(); - - // [GIVEN] BillingLine has values - MockBillingLineForPartnerNoWithUnitPriceAndDiscountAndServiceObjectQuantity(LibraryRandom.RandDec(100, 2), LibraryRandom.RandDec(50, 2), LibraryRandom.RandDec(10, 2)); - ExpectedServiceAmount := BillingLine."Unit Price" * BillingLine."Service Object Quantity" * (1 - BillingLine."Discount %" / 100); - - Assert.AreEqual(ExpectedServiceAmount, BillingProposal.CalculateBillingLineServiceAmount(BillingLine), 'Service Amount has not been calculated correctly on a Billing Line.'); - end; - - [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure CheckBillingLineUpdateRequiredOnModifyCustomerContractLine() var DiscountAmount: Decimal; @@ -615,7 +599,7 @@ codeunit 139688 "Recurring Billing Test" begin Initialize(); - ContractTestLibrary.CreateCustomer(Customer); + ContractTestLibrary.CreateCustomerInLCY(Customer); ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); ContractTestLibrary.CreateBillingProposal(BillingTemplate, Enum::"Service Partner"::Customer); BillingLine.Reset(); @@ -660,7 +644,6 @@ codeunit 139688 "Recurring Billing Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure CheckBillingLineUpdateRequiredOnModifyVendorContractLine() var DiscountAmount: Decimal; @@ -670,7 +653,7 @@ codeunit 139688 "Recurring Billing Test" begin Initialize(); - ContractTestLibrary.CreateVendor(Vendor); + ContractTestLibrary.CreateVendorInLCY(Vendor); ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Vendor."No."); ContractTestLibrary.CreateBillingProposal(BillingTemplate, Enum::"Service Partner"::Vendor); BillingLine.Reset(); @@ -1476,7 +1459,6 @@ codeunit 139688 "Recurring Billing Test" Assert.AreEqual("Sales Document Type"::"Credit Memo", BillingLine.GetSalesDocumentTypeForContractNo(), 'Sales Document Type is not calculated correctly for Credit Memo.'); end; - [Test] [HandlerFunctions('CreateBillingDocsCustomerPageHandler,MessageHandler')] procedure ExtendedTextTransferredToSalesLine() var @@ -1550,6 +1532,45 @@ codeunit 139688 "Recurring Billing Test" Assert.IsTrue(VerifyPurchaseLine.Count() > 0, ExtendedTextPurchValueErr); end; + [Test] + procedure TestBillingProposalWithZeroQuantity() + var + ExpectedNextBillingDate: Date; + begin + // [SCENARIO] Billing proposal creates billing lines with quantity 0 and amount 0 when subscription quantity is 0 + Initialize(); + + // [GIVEN] Create customer contract with subscription and set quantity to 0 + CreateCustomerContract('<1M>', '<12M>'); + ServiceObject.SetHideValidationDialog(true); + ServiceObject.Validate(Quantity, 0); + ServiceObject.Modify(true); + + // [GIVEN] Read the subscription line to be able to refresh it after billing proposal is created + ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); + ServiceCommitment.SetRange(Partner, "Service Partner"::Customer); + ServiceCommitment.FindFirst(); + + // [WHEN] Create billing proposal + CreateRecurringBillingTemplateSetupForCustomerContract('<2M-CM>', '<8M+CM>', CustomerContract.GetView()); + ContractTestLibrary.CreateBillingProposal(BillingTemplate, Enum::"Service Partner"::Customer); + + // [THEN] Billing lines are created with quantity 0 and amount 0 + BillingLine.SetRange("Billing Template Code", BillingTemplate.Code); + BillingLine.SetRange("Subscription Header No.", ServiceObject."No."); + Assert.RecordIsNotEmpty(BillingLine); + BillingLine.FindSet(); + repeat + Assert.AreEqual(0, BillingLine."Service Object Quantity", 'Billing Line quantity should be 0.'); + Assert.AreEqual(0, BillingLine.Amount, 'Billing Line amount should be 0 when quantity is 0.'); + until BillingLine.Next() = 0; + + // [THEN] Next Billing Date on the subscription line is set to the day after the last billing line's Billing To date + ExpectedNextBillingDate := CalcDate('<1D>', BillingLine."Billing to"); + ServiceCommitment.Get(ServiceCommitment."Entry No."); + Assert.AreEqual(ExpectedNextBillingDate, ServiceCommitment."Next Billing Date", 'Next Billing Date should be the day after the last Billing To date.'); + end; + #endregion Tests #region Procedures @@ -1838,14 +1859,6 @@ codeunit 139688 "Recurring Billing Test" BillingLine.Insert(false); end; - local procedure MockBillingLineForPartnerNoWithUnitPriceAndDiscountAndServiceObjectQuantity(NewUnitPrice: Decimal; NewDiscountPercentage: Decimal; NewServiceObjQuantity: Decimal) - begin - BillingLine.InitNewBillingLine(); - BillingLine."Unit Price" := NewUnitPrice; - BillingLine."Discount %" := NewDiscountPercentage; - BillingLine."Service Object Quantity" := NewServiceObjQuantity; - BillingLine.Insert(false); - end; local procedure RecurringBillingPageSetupForCustomer() begin diff --git a/src/Apps/W1/Subscription Billing/Test/Contract Renewal/ContractRenewalTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Contract Renewal/ContractRenewalTest.Codeunit.al index c33f04aaa2..6112bd6b50 100644 --- a/src/Apps/W1/Subscription Billing/Test/Contract Renewal/ContractRenewalTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Contract Renewal/ContractRenewalTest.Codeunit.al @@ -674,6 +674,8 @@ codeunit 139692 "Contract Renewal Test" Initialize(); // [GIVEN] We Create all the needed data CreateBaseData(); + CustomerContract.Validate("Currency Code", ''); + CustomerContract.Modify(false); BaseCalculationPercentage := LibraryRandom.RandDecInDecimalRange(80, 100, 2); CalculationBaseAmount := LibraryRandom.RandDecInDecimalRange(80, 100, 2); // [WHEN] We run the action Contract Renewal Quote and change the values on Subscription, values are tested in a ContractRenewalSelectionModalPageHandler diff --git a/src/Apps/W1/Subscription Billing/Test/Customer Contracts/ContractsTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Customer Contracts/ContractsTest.Codeunit.al index 4ed5f38425..ee7e5f1e36 100644 --- a/src/Apps/W1/Subscription Billing/Test/Customer Contracts/ContractsTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Customer Contracts/ContractsTest.Codeunit.al @@ -77,7 +77,6 @@ codeunit 148155 "Contracts Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure CheckContractLineTypeForCommentOnCustomerContractLine() var Customer: Record Customer; @@ -91,7 +90,7 @@ codeunit 148155 "Contracts Test" // [SCENARIO] Create Customer Subscription Contract. Add Description and check if the ContractLineType for that line is Comment Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); CreateCustomerContractSetup(Customer, ServiceObject, CustomerContract); DescriptionText := LibraryRandom.RandText(100); @@ -222,7 +221,7 @@ codeunit 148155 "Contracts Test" begin Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); ServCommWOCustContract.OpenEdit(); ServiceCommitment.Reset(); @@ -237,7 +236,7 @@ codeunit 148155 "Contracts Test" end; [Test] - [HandlerFunctions('ServCommWOCustContractPageHandler,ExchangeRateSelectionModalPageHandler,MessageHandler')] + [HandlerFunctions('ServCommWOCustContractPageHandler')] procedure CheckServiceCommitmentAssignmentToCustomerContractForServiceObjectWithItem() var Customer: Record Customer; @@ -251,7 +250,7 @@ codeunit 148155 "Contracts Test" // [SCENARIO] Check that proper Subscription Lines are assigned to Customer Subscription Contract Lines Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); @@ -323,7 +322,7 @@ codeunit 148155 "Contracts Test" end; [Test] - [HandlerFunctions('ServCommWOCustContractPageHandler,ExchangeRateSelectionModalPageHandler,MessageHandler')] + [HandlerFunctions('ServCommWOCustContractPageHandler')] procedure CheckServiceCommitmentAssignmentToCustomerContractWithShipToCode() var Customer: Record Customer; @@ -337,8 +336,8 @@ codeunit 148155 "Contracts Test" // [SCENARIO] Check that proper Subscription Lines are assigned to Customer Subscription Contract Lines Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject2, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject2, false, false); ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); ShipToAddress.SetRange("Customer No.", Customer."No."); ShipToAddress.FindFirst(); @@ -373,7 +372,7 @@ codeunit 148155 "Contracts Test" Initialize(); Currency.InitRoundingPrecision(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, true); ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); CustomerContractPage.OpenEdit(); @@ -457,7 +456,7 @@ codeunit 148155 "Contracts Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,ConfirmHandlerYes,MessageHandler')] + [HandlerFunctions('ConfirmHandlerYes,MessageHandler')] procedure CheckValueChangesOnCustomerContractLines() var ItemVariant: Record "Item Variant"; @@ -481,7 +480,7 @@ codeunit 148155 "Contracts Test" // [SCENARIO] Assign Subscription Lines to Customer Subscription Contract Lines. Change values on Customer Subscription Contract Lines and check that Subscription Line has changed values. Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); Currency.InitRoundingPrecision(); CreateCustomerContractSetup(Customer, ServiceObject, CustomerContract); @@ -594,7 +593,7 @@ codeunit 148155 "Contracts Test" begin Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); LibrarySales.CreateShipToAddress(ShipToAddress, Customer."No."); @@ -787,7 +786,6 @@ codeunit 148155 "Contracts Test" end; [Test] - [HandlerFunctions('MessageHandler,ExchangeRateSelectionModalPageHandler')] procedure ExpectErrorForWrongServiceCommitmentToCustomerContractAssignment() var Customer: Record Customer; @@ -800,7 +798,7 @@ codeunit 148155 "Contracts Test" // [SCENARIO] try to assign Subscription Line to wrong Contract No (different Customer No.) Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); ContractTestLibrary.CreateCustomer(Customer2); ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer2."No."); @@ -826,7 +824,7 @@ codeunit 148155 "Contracts Test" begin Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, true); ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); @@ -1032,7 +1030,6 @@ codeunit 148155 "Contracts Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure ExpectNoClosedCustomerContractLines() var Customer: Record Customer; @@ -1044,7 +1041,7 @@ codeunit 148155 "Contracts Test" begin Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); ContractTestLibrary.InsertCustomerContractCommentLine(CustomerContract, CustomerContractLine2); ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); @@ -1266,7 +1263,7 @@ codeunit 148155 "Contracts Test" // [GIVEN] ContractAnalysisEntry.DeleteAll(false); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, true); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, true, true); ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); // ExchangeRateSelectionModalPageHandler, MessageHandler // [WHEN] @@ -1427,7 +1424,6 @@ codeunit 148155 "Contracts Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure TestDeleteServiceCommitmentLinkedToContractLineNotClosed() var Customer: Record Customer; @@ -1439,8 +1435,8 @@ codeunit 148155 "Contracts Test" // Test: Subscription Line cannot be deleted if an open contract line exists Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); - ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); // ExchangeRateSelectionModalPageHandler, MessageHandler + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); + ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); ServiceCommitment.Reset(); ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); @@ -1453,7 +1449,7 @@ codeunit 148155 "Contracts Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler,ConfirmHandlerYes')] + [HandlerFunctions('ConfirmHandlerYes')] procedure TestDeleteServiceCommitmentLinkedToContractLineIsClosed() var Customer: Record Customer; @@ -1465,8 +1461,8 @@ codeunit 148155 "Contracts Test" // Test: A closed Contract Line is deleted when deleting the Subscription Line Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); - ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); // ExchangeRateSelectionModalPageHandler, MessageHandler + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); + ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); ServiceCommitment.Reset(); ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); @@ -1632,7 +1628,7 @@ codeunit 148155 "Contracts Test" Initialize(); Currency.InitRoundingPrecision(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, true); ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); CustomerContractPage.OpenEdit(); @@ -1650,7 +1646,7 @@ codeunit 148155 "Contracts Test" end; [Test] - [HandlerFunctions('ServCommWOCustContractPageHandler,ExchangeRateSelectionModalPageHandler,MessageHandler')] + [HandlerFunctions('ServCommWOCustContractPageHandler')] procedure TestResetServiceCommitmentsOnCurrencyCodeDelete() var Currency: Record Currency; @@ -1663,7 +1659,7 @@ codeunit 148155 "Contracts Test" Initialize(); Currency.InitRoundingPrecision(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); CustomerContractPage.OpenEdit(); @@ -1705,7 +1701,7 @@ codeunit 148155 "Contracts Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler,CreateCustomerBillingDocsContractPageHandler')] + [HandlerFunctions('MessageHandler,CreateCustomerBillingDocsContractPageHandler')] procedure UpdatingServiceDatesWillNotCloseCustomerContractLinesWhenLineIsNotInvoicedCompletely() var Customer: Record Customer; @@ -1718,10 +1714,10 @@ codeunit 148155 "Contracts Test" // [SCENARIO] Test if the Customer Subscription Contract line will be closed in case of different constellations Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, false, false); // [GIVEN] Create a Customer Subscription Contract with Subscription Lines - ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); // ExchangeRateSelectionModalPageHandler, MessageHandler + ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); ContractTestLibrary.InsertCustomerContractCommentLine(CustomerContract, CustomerContractLine2); // [GIVEN] Make sure that Subscription Line End Date is filled, to fullfil the requirement for close of the Customer Subscription Contract line @@ -2276,7 +2272,7 @@ codeunit 148155 "Contracts Test" ServiceCommitment.Modify(false); end; - local procedure SetupServiceObjectForNewItemWithServiceCommitment(var Customer: Record Customer; var ServiceObject: Record "Subscription Header"; SNSpecificTracking: Boolean; AddVendorServiceCommitment: Boolean) + local procedure SetupServiceObjectForNewItemWithServiceCommitment(var Customer: Record Customer; var ServiceObject: Record "Subscription Header"; SNSpecificTracking: Boolean; AddVendorServiceCommitment: Boolean; CustomerWithCurrency: Boolean) var Item: Record Item; ItemServCommitmentPackage: Record "Item Subscription Package"; @@ -2285,7 +2281,10 @@ codeunit 148155 "Contracts Test" ServiceCommitmentTemplate: Record "Sub. Package Line Template"; ServiceCommitmentTemplate2: Record "Sub. Package Line Template"; begin - ContractTestLibrary.CreateCustomer(Customer); + if CustomerWithCurrency then + ContractTestLibrary.CreateCustomer(Customer) + else + ContractTestLibrary.CreateCustomerInLCY(Customer); ContractTestLibrary.CreateServiceObjectForItem(ServiceObject, Item, SNSpecificTracking); ServiceObject.Validate("End-User Customer Name", Customer.Name); ServiceObject.Modify(false); @@ -2333,9 +2332,9 @@ codeunit 148155 "Contracts Test" ServiceObject.InsertServiceCommitmentsFromServCommPackage(WorkDate(), ServiceCommitmentPackage); end; - local procedure SetupServiceObjectForNewItemWithServiceCommitment(var Customer: Record Customer; var ServiceObject: Record "Subscription Header"; SNSpecificTracking: Boolean) + local procedure SetupServiceObjectForNewItemWithServiceCommitment(var Customer: Record Customer; var ServiceObject: Record "Subscription Header"; SNSpecificTracking: Boolean; CustomerWithCurrency: Boolean) begin - SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, SNSpecificTracking, false); + SetupServiceObjectForNewItemWithServiceCommitment(Customer, ServiceObject, SNSpecificTracking, false, CustomerWithCurrency); end; local procedure SetupServiceObjectForNewGLAccountWithServiceCommitment(var Customer: Record Customer; var ServiceObject: Record "Subscription Header") diff --git a/src/Apps/W1/Subscription Billing/Test/Deferrals/CustomerDeferralsTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Deferrals/CustomerDeferralsTest.Codeunit.al index e4ca028654..7eded9bb91 100644 --- a/src/Apps/W1/Subscription Billing/Test/Deferrals/CustomerDeferralsTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Deferrals/CustomerDeferralsTest.Codeunit.al @@ -58,7 +58,7 @@ codeunit 139912 "Customer Deferrals Test" PrevGLEntry: Integer; TotalNumberOfMonths: Integer; IsInitialized: Boolean; - ConfirmQuestionLbl: Label 'If you change Quantity, only the Amount for existing service commitments will be recalculated.\\Do you want to continue?', Comment = '%1= Changed Field Name.'; + ConfirmQuestionLbl: Label 'If you change the Quantity, the amount for open subscription lines will be recalculated.\\Do you want to continue?'; #region Tests diff --git a/src/Apps/W1/Subscription Billing/Test/DisabledTests/ContractRenewalTest.json b/src/Apps/W1/Subscription Billing/Test/DisabledTests/ContractRenewalTest.json deleted file mode 100644 index 12e9c5275e..0000000000 --- a/src/Apps/W1/Subscription Billing/Test/DisabledTests/ContractRenewalTest.json +++ /dev/null @@ -1,98 +0,0 @@ -[ - { - "bug": "597430", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "TestIfContractRenewalSelectionIsUpdateOnAfterValidateCalcBasePercentage" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "CheckAndVerifyCreateSingleSalesQuote" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "CheckCreateMultipleContractRenewalQuotes" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "CheckEndDateForRenewalTermDifferentThanSubsequentTerm" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "CheckSortingInRenewalQuote" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "CheckVatCalculationForContractRenewalServiceCommitmentRhythmInReports" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "CheckServCommSelectionPageAcceptChanges" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "CheckSerialNoDescriptionForRenewalSalesQuoteWithSNTracking" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "ExpectErrorOnChangeQtyToShip" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "ExpectNoPostedSalesInvoiceOnPostRenewalSalesOrder" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "PostContractRenewalAndVerifyResult" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "PostModifiedContractRenewalWithFinalInvoiceAndVerifyResult" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "TestPostedDocumentsOnPostRenewalSalesOrder" - }, - { - "bug": "574501", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "TestSalesQuoteToOrderYesNoForContractRenewal" - }, - { - "bug": "603387", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "AllowSelectionOfResponsibilityCenterInContractRenewalSalesDocuments" - }, - { - "bug": "603387", - "codeunitId": 139692, - "codeunitName": "Contract Renewal Test", - "method": "PreventExistingDimensionsInContractRenewalSalesDocumentsWhenResponsibilityCenterIsChanged" - } -] diff --git a/src/Apps/W1/Subscription Billing/Test/DisabledTests/ContractsTest.json b/src/Apps/W1/Subscription Billing/Test/DisabledTests/ContractsTest.json deleted file mode 100644 index ce7528a094..0000000000 --- a/src/Apps/W1/Subscription Billing/Test/DisabledTests/ContractsTest.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "bug": "597430", - "codeunitId": 148155, - "CodeunitName": "Contracts Test", - "Method": "CheckValueChangesOnCustomerContractLines" - } -] \ No newline at end of file diff --git a/src/Apps/W1/Subscription Billing/Test/DisabledTests/RecurringBillingDocsTest.json b/src/Apps/W1/Subscription Billing/Test/DisabledTests/RecurringBillingDocsTest.json deleted file mode 100644 index 45fea04443..0000000000 --- a/src/Apps/W1/Subscription Billing/Test/DisabledTests/RecurringBillingDocsTest.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "bug": "597430", - "codeunitId": 139687, - "CodeunitName": "Recurring Billing Docs Test", - "Method": "GetVendorContractLinesProducesCorrectAmountsDuringSelection" - } -] \ No newline at end of file diff --git a/src/Apps/W1/Subscription Billing/Test/DisabledTests/RecurringBillingTest.json b/src/Apps/W1/Subscription Billing/Test/DisabledTests/RecurringBillingTest.json deleted file mode 100644 index c805971b3f..0000000000 --- a/src/Apps/W1/Subscription Billing/Test/DisabledTests/RecurringBillingTest.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "bug": "597430", - "codeunitId": 139688, - "CodeunitName": "Recurring Billing Test", - "Method": "CheckBillingLineUpdateRequiredOnModifyCustomerContractLine" - }, - { - "bug": "597430", - "codeunitId": 139688, - "CodeunitName": "Recurring Billing Test", - "Method": "CheckBillingLineUpdateRequiredOnModifyVendorContractLine" - } -] \ No newline at end of file diff --git a/src/Apps/W1/Subscription Billing/Test/DisabledTests/ServiceCommArchiveTest.json b/src/Apps/W1/Subscription Billing/Test/DisabledTests/ServiceCommArchiveTest.json deleted file mode 100644 index 965b51bb75..0000000000 --- a/src/Apps/W1/Subscription Billing/Test/DisabledTests/ServiceCommArchiveTest.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "bug": "597430", - "codeunitId": 139916, - "CodeunitName": "Service Comm. Archive Test", - "Method": "ExpectSingleServiceCommitmentArchiveOnModifyMultipleFieldsOnCustomerContractLine" - } -] \ No newline at end of file diff --git a/src/Apps/W1/Subscription Billing/Test/DisabledTests/ServiceObjectDisabledTest.json b/src/Apps/W1/Subscription Billing/Test/DisabledTests/ServiceObjectDisabledTest.json deleted file mode 100644 index 3c7f14f4c8..0000000000 --- a/src/Apps/W1/Subscription Billing/Test/DisabledTests/ServiceObjectDisabledTest.json +++ /dev/null @@ -1,9 +0,0 @@ - -[ - { - "bug": "619916", - "codeunitId": 148157, - "codeunitName": "Service Object Test", - "method": "CheckSubscriptionLineRecalculatesTerminationDatesOnStartDateChange" - } -] \ No newline at end of file diff --git a/src/Apps/W1/Subscription Billing/Test/DisabledTests/VendorContractsTest.json b/src/Apps/W1/Subscription Billing/Test/DisabledTests/VendorContractsTest.json deleted file mode 100644 index 55cdc68abf..0000000000 --- a/src/Apps/W1/Subscription Billing/Test/DisabledTests/VendorContractsTest.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "bug": "597430", - "codeunitId": 148154, - "CodeunitName": "Vendor Contracts Test", - "Method": "CheckValueChangesOnVendorContractLines" - } -] \ No newline at end of file diff --git a/src/Apps/W1/Subscription Billing/Test/Service Commitments/ServiceCommArchiveTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Service Commitments/ServiceCommArchiveTest.Codeunit.al index 3840e79b8f..e258336ede 100644 --- a/src/Apps/W1/Subscription Billing/Test/Service Commitments/ServiceCommArchiveTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Service Commitments/ServiceCommArchiveTest.Codeunit.al @@ -84,7 +84,6 @@ codeunit 139916 "Service Comm. Archive Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure ExpectSingleServiceCommitmentArchiveOnModifyMultipleFieldsOnCustomerContractLine() var TempServiceCommitment: Record "Subscription Line" temporary; @@ -93,7 +92,7 @@ codeunit 139916 "Service Comm. Archive Test" // Expect only one Subscription Line archive if multiple fields are modified in less then a minute SetupServiceObjectWithServiceCommitment(false, false); - ContractTestLibrary.CreateCustomer(Customer); + ContractTestLibrary.CreateCustomerInLCY(Customer); ServiceObject."End-User Customer No." := Customer."No."; ServiceObject.Modify(false); ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); diff --git a/src/Apps/W1/Subscription Billing/Test/Service Objects/ServiceObjectTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Service Objects/ServiceObjectTest.Codeunit.al index e732c1be38..31614eb724 100644 --- a/src/Apps/W1/Subscription Billing/Test/Service Objects/ServiceObjectTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Service Objects/ServiceObjectTest.Codeunit.al @@ -322,6 +322,20 @@ codeunit 148157 "Service Object Test" Assert.AreEqual(0D, SubscriptionLine."Cancellation Possible Until", '"Cancellation Possible Until" is not cleared.'); end; + [Test] + procedure CheckCreateServiceObjectWithGLAccountNo() + var + GLAccount: Record "G/L Account"; + ServiceObject: Record "Subscription Header"; + begin + ClearAll(); + ContractTestLibrary.CreateServiceObjectForGLAccount(ServiceObject, GLAccount); + ServiceObject.TestField(Type, ServiceObject.Type::"G/L Account"); + ServiceObject.TestField("Source No.", GLAccount."No."); + ServiceObject.TestField(Description, GLAccount.Name); + ServiceObject.TestField(Quantity, 1); + end; + [Test] procedure CheckDeleteServiceObjectWithArchivedServComm() var @@ -375,31 +389,6 @@ codeunit 148157 "Service Object Test" Assert.RecordIsEmpty(SubscriptionLine); end; - [Test] - procedure ExpectErrorWhenDeletingSubscriptionHeaderWithAssignedSubscriptionLines() - var - Item: Record Item; - SubscriptionLine: Record "Subscription Line"; - SubscriptionHeader: Record "Subscription Header"; - CannotDeleteBecauseServiceCommitmentExistsErr: Label 'Cannot delete %1 while %2 connected to a contract exists.', Comment = '%1 = No., %2 = TableCaption', Locked = true; - begin - // [SCENARIO] When trying to delete a subscription header with subscription lines assigned to a contract, an error should be thrown - Initialize(); - - // [GIVEN] A subscription header with subscription lines - SetupServiceObjectWithServiceCommitment(Item, SubscriptionHeader, false, false); - - // [GIVEN] Subscription lines are assigned to a contract - SubscriptionLine.SetRange("Subscription Header No.", SubscriptionHeader."No."); - SubscriptionLine.ModifyAll("Subscription Contract No.", LibraryRandom.RandText(20)); - - // [WHEN] Attempting to delete the subscription header - asserterror SubscriptionHeader.Delete(true); - - // [THEN] An error is thrown preventing the deletion - Assert.ExpectedError(StrSubstNo(CannotDeleteBecauseServiceCommitmentExistsErr, SubscriptionHeader."No.", SubscriptionLine.TableCaption)); - end; - [Test] [HandlerFunctions('AssignServiceCommitmentsModalPageHandler')] @@ -1123,6 +1112,50 @@ codeunit 148157 "Service Object Test" ServiceObjectTestPage.Attributes.Invoke(); // ServiceObjectAttributeValueEditorModalPageHandlerExpectErrorOnPrimary end; + [Test] + procedure ExpectErrorWhenDeletingSubscriptionHeaderWithAssignedSubscriptionLines() + var + Item: Record Item; + SubscriptionLine: Record "Subscription Line"; + SubscriptionHeader: Record "Subscription Header"; + CannotDeleteBecauseServiceCommitmentExistsErr: Label 'Cannot delete %1 while %2 connected to a contract exists.', Comment = '%1 = No., %2 = TableCaption', Locked = true; + begin + // [SCENARIO] When trying to delete a subscription header with subscription lines assigned to a contract, an error should be thrown + Initialize(); + + // [GIVEN] A subscription header with subscription lines + SetupServiceObjectWithServiceCommitment(Item, SubscriptionHeader, false, false); + + // [GIVEN] Subscription lines are assigned to a contract + SubscriptionLine.SetRange("Subscription Header No.", SubscriptionHeader."No."); + SubscriptionLine.ModifyAll("Subscription Contract No.", LibraryRandom.RandText(20)); + + // [WHEN] Attempting to delete the subscription header + asserterror SubscriptionHeader.Delete(true); + + // [THEN] An error is thrown preventing the deletion + Assert.ExpectedError(StrSubstNo(CannotDeleteBecauseServiceCommitmentExistsErr, SubscriptionHeader."No.", SubscriptionLine.TableCaption)); + end; + + [Test] + procedure TestNegativeSubscriptionQuantityBlocked() + var + Item: Record Item; + ServiceObject: Record "Subscription Header"; + begin + // [SCENARIO] Setting subscription quantity to a negative value is blocked + Initialize(); + + // [GIVEN] Create service object with subscription lines + SetupServiceObjectWithServiceCommitment(Item, ServiceObject, false, true); + + // [WHEN] Try to set the quantity to -1 + asserterror ServiceObject.Validate(Quantity, -1); + + // [THEN] Error is raised + Assert.ExpectedError('The quantity cannot be negative.'); + end; + [Test] procedure TestModifyCustomerAddress() var @@ -1144,6 +1177,40 @@ codeunit 148157 "Service Object Test" ServiceObject.Modify(false); end; + [Test] + procedure TestPauseAndResumeSubscriptionPreservesPrice() + var + Item: Record Item; + ServiceCommitment: Record "Subscription Line"; + ServiceObject: Record "Subscription Header"; + OriginalPrice: Decimal; + begin + // [SCENARIO] Subscription can be paused (qty 0) and resumed with original prices preserved + Initialize(); + + // [GIVEN] Create service object with subscription lines + SetupServiceObjectWithServiceCommitment(Item, ServiceObject, false, true); + ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); + ServiceCommitment.SetRange(Partner, "Service Partner"::Customer); + ServiceCommitment.FindFirst(); + OriginalPrice := ServiceCommitment.Price; + + // [WHEN] Set quantity to 0 (pause) + ServiceObject.Validate(Quantity, 0); + ServiceObject.Modify(true); + + // [THEN] Service Object quantity is 0 + Assert.AreEqual(0, ServiceObject.Quantity, 'Service Object Quantity should be 0 after pausing.'); + + // [WHEN] Resume with original quantity + ServiceObject.Validate(Quantity, LibraryRandom.RandDecInRange(1, 10, 2)); + ServiceObject.Modify(true); + + // [THEN] Price is preserved after resume + ServiceCommitment.Get(ServiceCommitment."Entry No."); + Assert.AreEqual(OriginalPrice, ServiceCommitment.Price, 'Price should be preserved after pause and resume.'); + end; + [Test] [HandlerFunctions('AssignServiceCommitmentsModalPageHandler')] procedure TestPriceGroupFilterOnAssignServiceCommitments() @@ -1200,6 +1267,32 @@ codeunit 148157 "Service Object Test" until ServiceCommitment.Next() = 0; end; + [Test] + procedure TestSetSubscriptionQuantityToZero() + var + Item: Record Item; + ServiceCommitment: Record "Subscription Line"; + ServiceObject: Record "Subscription Header"; + begin + // [SCENARIO] Setting subscription quantity to 0 is allowed and sets subscription line amounts to 0 + Initialize(); + + // [GIVEN] Create service object with subscription lines + SetupServiceObjectWithServiceCommitment(Item, ServiceObject, false, true); + + // [WHEN] Set the quantity to 0 + ServiceObject.Validate(Quantity, 0); + ServiceObject.Modify(true); + + // [THEN] Quantity is 0 and subscription line amounts are 0 + Assert.AreEqual(0, ServiceObject.Quantity, 'Quantity should be 0.'); + ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); + ServiceCommitment.FindSet(); + repeat + Assert.AreEqual(0, ServiceCommitment.Amount, 'Subscription Line Amount should be 0 when quantity is 0.'); + until ServiceCommitment.Next() = 0; + end; + [Test] procedure TestRecalculateServiceCommitmentsOnChangeBillToCustomer() var @@ -1387,26 +1480,6 @@ codeunit 148157 "Service Object Test" until ServiceCommitment.Next() = 0; end; - local procedure VerifyUnitCost(ServiceCommitment: Record "Subscription Line"; Item: Record Item) - var - ExpectedUnitCost: Decimal; - ValueNotCorrectTok: Label '%1 value is not correct.', Locked = true; - begin - case ServiceCommitment.Partner of - "Service Partner"::Customer: - begin - ExpectedUnitCost := Item."Unit Cost" * ServiceCommitment."Calculation Base %" / 100; - Assert.AreEqual(ExpectedUnitCost, ServiceCommitment."Unit Cost", StrSubstNo(ValueNotCorrectTok, ServiceCommitment.FieldCaption("Unit Cost"))); - Assert.AreEqual(ExpectedUnitCost, ServiceCommitment."Unit Cost (LCY)", StrSubstNo(ValueNotCorrectTok, ServiceCommitment.FieldCaption("Unit Cost (LCY)"))); - end; - "Service Partner"::Vendor: - begin - Assert.AreEqual(ServiceCommitment.Price, ServiceCommitment."Unit Cost", StrSubstNo(ValueNotCorrectTok, ServiceCommitment.FieldCaption("Unit Cost"))); - Assert.AreEqual(ServiceCommitment.Price, ServiceCommitment."Unit Cost (LCY)", StrSubstNo(ValueNotCorrectTok, ServiceCommitment.FieldCaption("Unit Cost (LCY)"))); - end; - end; - - end; [Test] procedure UT_CheckCreateServiceObject() @@ -1458,28 +1531,14 @@ codeunit 148157 "Service Object Test" end; [Test] - procedure CheckCreateServiceObjectWithGLAccountNo() - var - GLAccount: Record "G/L Account"; - ServiceObject: Record "Subscription Header"; - begin - ClearAll(); - ContractTestLibrary.CreateServiceObjectForGLAccount(ServiceObject, GLAccount); - ServiceObject.TestField(Type, ServiceObject.Type::"G/L Account"); - ServiceObject.TestField("Source No.", GLAccount."No."); - ServiceObject.TestField(Description, GLAccount.Name); - ServiceObject.TestField(Quantity, 1); - end; - - [Test] - procedure UT_CheckServiceObjectQtyCannotBeBlank() + procedure UT_CheckServiceObjectQtyCannotBeNegative() var ServiceObject: Record "Subscription Header"; begin Initialize(); ContractTestLibrary.CreateServiceObjectForItem(ServiceObject, ''); - asserterror ServiceObject.Validate(Quantity, 0); + asserterror ServiceObject.Validate(Quantity, -1); end; [Test] @@ -1862,6 +1921,7 @@ codeunit 148157 "Service Object Test" local procedure VerifySubscriptionLineDates(var SubscriptionLine: Record "Subscription Line"; StartDate: Date; InitialTerm: Text; NoticePeriod: Text) var + DateTimeManagement: Codeunit "Date Time Management"; ExpectedEndDate: Date; ExpectedTermUntil: Date; ExpectedCancellationPossibleUntil: Date; @@ -1869,6 +1929,8 @@ codeunit 148157 "Service Object Test" ExpectedEndDate := CalcDate(InitialTerm + '-1D', StartDate); ExpectedTermUntil := ExpectedEndDate; ExpectedCancellationPossibleUntil := CalcDate('-' + NoticePeriod, ExpectedTermUntil); + if DateTimeManagement.IsLastDayOfMonth(ExpectedTermUntil) then + DateTimeManagement.MoveDateToLastDayOfMonth(ExpectedCancellationPossibleUntil); SubscriptionLine.TestField("Subscription Line Start Date", StartDate); SubscriptionLine.TestField("Subscription Line End Date", ExpectedEndDate); @@ -1876,6 +1938,27 @@ codeunit 148157 "Service Object Test" SubscriptionLine.TestField("Cancellation Possible Until", ExpectedCancellationPossibleUntil); end; + local procedure VerifyUnitCost(ServiceCommitment: Record "Subscription Line"; Item: Record Item) + var + ExpectedUnitCost: Decimal; + ValueNotCorrectTok: Label '%1 value is not correct.', Locked = true; + begin + case ServiceCommitment.Partner of + "Service Partner"::Customer: + begin + ExpectedUnitCost := Item."Unit Cost" * ServiceCommitment."Calculation Base %" / 100; + Assert.AreEqual(ExpectedUnitCost, ServiceCommitment."Unit Cost", StrSubstNo(ValueNotCorrectTok, ServiceCommitment.FieldCaption("Unit Cost"))); + Assert.AreEqual(ExpectedUnitCost, ServiceCommitment."Unit Cost (LCY)", StrSubstNo(ValueNotCorrectTok, ServiceCommitment.FieldCaption("Unit Cost (LCY)"))); + end; + "Service Partner"::Vendor: + begin + Assert.AreEqual(ServiceCommitment.Price, ServiceCommitment."Unit Cost", StrSubstNo(ValueNotCorrectTok, ServiceCommitment.FieldCaption("Unit Cost"))); + Assert.AreEqual(ServiceCommitment.Price, ServiceCommitment."Unit Cost (LCY)", StrSubstNo(ValueNotCorrectTok, ServiceCommitment.FieldCaption("Unit Cost (LCY)"))); + end; + end; + + end; + #endregion Procedures #region Handlers diff --git a/src/Apps/W1/Subscription Billing/Test/UBB/UsageBasedBillingTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/UBB/UsageBasedBillingTest.Codeunit.al index d12114db16..e21d45d32c 100644 --- a/src/Apps/W1/Subscription Billing/Test/UBB/UsageBasedBillingTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/UBB/UsageBasedBillingTest.Codeunit.al @@ -46,11 +46,11 @@ codeunit 148153 "Usage Based Billing Test" SalesHeader: Record "Sales Header"; SalesInvoiceHeader: Record "Sales Invoice Header"; SalesLine: Record "Sales Line"; - ServiceCommPackageLine: Record "Subscription Package Line"; - ServiceCommitment: Record "Subscription Line"; - ServiceCommitmentPackage: Record "Subscription Package"; - ServiceCommitmentTemplate: Record "Sub. Package Line Template"; - ServiceObject: Record "Subscription Header"; + SubscriptionPackageLine: Record "Subscription Package Line"; + SubscriptionLine: Record "Subscription Line"; + SubscriptionPackage: Record "Subscription Package"; + SubPackageLineTemplate: Record "Sub. Package Line Template"; + SubscriptionHeader: Record "Subscription Header"; UsageDataBlob: Record "Usage Data Blob"; UsageDataCustomer: Record "Usage Data Supp. Customer"; UsageDataImport: Record "Usage Data Import"; @@ -98,10 +98,7 @@ codeunit 148153 "Usage Based Billing Test" // [GIVEN] Create Subscription Item Initialize(); - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - Item."Unit Price" := LibraryRandom.RandDec(1000, 2); - Item."Unit Cost" := LibraryRandom.RandDec(1000, 2); - Item.Modify(false); + CreateSubscriptionItemWithPrices(LibraryRandom.RandDec(1000, 2), LibraryRandom.RandDec(1000, 2)); // [GIVEN] Setup Subscription with Subscription Lines and usage quantity SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Usage Quantity", "Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, @@ -109,13 +106,13 @@ codeunit 148153 "Usage Based Billing Test" // [GIVEN] Add discount to Subscription Line DiscountPct := LibraryRandom.RandDec(99, 2); - ServiceCommitment.Reset(); - ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); - ServiceCommitment.FindSet(); + SubscriptionLine.Reset(); + SubscriptionLine.SetRange("Subscription Header No.", SubscriptionHeader."No."); + SubscriptionLine.FindSet(); repeat - ServiceCommitment.Validate("Discount %", DiscountPct); - ServiceCommitment.Modify(false); - until ServiceCommitment.Next() = 0; + SubscriptionLine.Validate("Discount %", DiscountPct); + SubscriptionLine.Modify(false); + until SubscriptionLine.Next() = 0; // [WHEN] Create and process simple usage data ProcessUsageDataWithSimpleGenericImport(WorkDate(), WorkDate(), WorkDate(), CalcDate('', WorkDate()), 1); @@ -149,48 +146,45 @@ codeunit 148153 "Usage Based Billing Test" ContractTestLibrary.InitContractsApp(); //[GIVEN]: Create service commitment Item - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - Item."Unit Price" := LibraryRandom.RandDec(1000, 2); - Item."Unit Cost" := LibraryRandom.RandDec(1000, 2); - Item.Modify(false); + CreateSubscriptionItemWithPrices(LibraryRandom.RandDec(1000, 2), LibraryRandom.RandDec(1000, 2)); //[GIVEN]: Setup service object with service commitments and usage quantity - ContractTestLibrary.CreateServiceCommitmentTemplate(ServiceCommitmentTemplate); - ContractTestLibrary.CreateServiceCommitmentPackageWithLine(ServiceCommitmentTemplate.Code, ServiceCommitmentPackage, ServiceCommPackageLine); - ContractTestLibrary.UpdateServiceCommitmentPackageLine(ServiceCommPackageLine, '1M', 100, '1M', '1M', "Service Partner"::Customer, ''); - ServiceCommPackageLine."Usage Based Billing" := true; - ServiceCommPackageLine."Usage Based Pricing" := "Usage Based Pricing"::"Usage Quantity"; - ServiceCommPackageLine."Calculation Base Type" := "Calculation Base Type"::"Item Price"; - ServiceCommPackageLine.Modify(); - ContractTestLibrary.CreateServiceCommitmentPackageLine(ServiceCommPackageLine."Subscription Package Code", ServiceCommPackageLine.Template, ServiceCommPackageLine, + ContractTestLibrary.CreateServiceCommitmentTemplate(SubPackageLineTemplate); + ContractTestLibrary.CreateServiceCommitmentPackageWithLine(SubPackageLineTemplate.Code, SubscriptionPackage, SubscriptionPackageLine); + ContractTestLibrary.UpdateServiceCommitmentPackageLine(SubscriptionPackageLine, '1M', 100, '1M', '1M', "Service Partner"::Customer, ''); + SubscriptionPackageLine."Usage Based Billing" := true; + SubscriptionPackageLine."Usage Based Pricing" := "Usage Based Pricing"::"Usage Quantity"; + SubscriptionPackageLine."Calculation Base Type" := "Calculation Base Type"::"Item Price"; + SubscriptionPackageLine.Modify(); + ContractTestLibrary.CreateServiceCommitmentPackageLine(SubscriptionPackageLine."Subscription Package Code", SubscriptionPackageLine.Template, SubscriptionPackageLine, '1M', '1M', "Service Partner"::Customer); - ServiceCommPackageLine.Discount := true; - ServiceCommPackageLine."Calculation Base Type" := "Calculation Base Type"::"Item Price"; - ServiceCommPackageLine.Modify(true); - ContractTestLibrary.AssignItemToServiceCommitmentPackage(Item, ServiceCommitmentPackage.Code); - ItemServCommitmentPackage.Get(Item."No.", ServiceCommitmentPackage.Code); + SubscriptionPackageLine.Discount := true; + SubscriptionPackageLine."Calculation Base Type" := "Calculation Base Type"::"Item Price"; + SubscriptionPackageLine.Modify(true); + ContractTestLibrary.AssignItemToServiceCommitmentPackage(Item, SubscriptionPackage.Code); + ItemServCommitmentPackage.Get(Item."No.", SubscriptionPackage.Code); ItemServCommitmentPackage.Standard := true; ItemServCommitmentPackage.Modify(false); LibrarySales.CreateCustomer(Customer); - ContractTestLibrary.CreateServiceObjectForItem(ServiceObject, Item."No."); - ServiceObject.InsertServiceCommitmentsFromStandardServCommPackages(WorkDate()); - ServiceObject."End-User Customer No." := Customer."No."; - ServiceObject.Validate(Quantity, 1); //mock service object quantity to avoid issues with rounding - ServiceObject.Modify(false); + ContractTestLibrary.CreateServiceObjectForItem(SubscriptionHeader, Item."No."); + SubscriptionHeader.InsertServiceCommitmentsFromStandardServCommPackages(WorkDate()); + SubscriptionHeader."End-User Customer No." := Customer."No."; + SubscriptionHeader.Validate(Quantity, 1); //mock service object quantity to avoid issues with rounding + SubscriptionHeader.Modify(false); CreateCustomerContractAndAssignServiceCommitments(); //[GIVEN]: Add discount to service commitment - ServiceCommitment.Reset(); - ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); - ServiceCommitment.findset(); + SubscriptionLine.Reset(); + SubscriptionLine.SetRange("Subscription Header No.", SubscriptionHeader."No."); + SubscriptionLine.findset(); repeat - if ServiceCommitment.Discount = false then - ServiceCommitment.Validate("Discount Amount", ServiceCommitment.Amount) //Rounding issue; Make sure that the Discount amount is equal to Service Amount + if SubscriptionLine.Discount = false then + SubscriptionLine.Validate("Discount Amount", SubscriptionLine.Amount) //Rounding issue; Make sure that the Discount amount is equal to Service Amount else - ServiceCommitment.Validate(Price, LibraryRandom.RandDec(1000, 2)); - ServiceCommitment.Modify(); - until ServiceCommitment.Next() = 0; + SubscriptionLine.Validate(Price, LibraryRandom.RandDec(1000, 2)); + SubscriptionLine.Modify(); + until SubscriptionLine.Next() = 0; //[WHEN]: Create and process simple usage data ProcessUsageDataWithSimpleGenericImport(WorkDate(), WorkDate(), WorkDate(), CalcDate('', WorkDate()), 1); @@ -208,13 +202,87 @@ codeunit 148153 "Usage Based Billing Test" Assert.RecordIsNotEmpty(UsageDataBilling); end; + [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateVendorBillingDocumentPageHandler,MessageHandler')] + [Test] + procedure DeleteUsageDataBillingLineWhenRelatedPurchCrMemoLineIsDeleted() + var + PurchCrMemoHeader: Record "Purchase Header"; + PurchInvHeader: Record "Purch. Inv. Header"; + UsageDataBilling: Record "Usage Data Billing"; + begin + // [SCENARIO] Creating a corrective purchase credit memo for a contract with two usage-based service commitments, deleting one line, and posting the memo should only create a new usage data billing line for the credited line. + // [GIVEN] A vendor contract with two usage-based service commitments, both invoiced + ResetAll(); + CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); + UsageDataImport.CollectVendorContractsAndCreateInvoices(UsageDataImport); + PostPurchaseDocuments(); + PurchInvHeader.FindLast(); + CorrectPostedPurchaseInvoice.CreateCreditMemoCopyDocument(PurchInvHeader, PurchCrMemoHeader); + + PurchaseLine.Reset(); + PurchaseLine.SetRange("Document Type", PurchCrMemoHeader."Document Type"); + PurchaseLine.SetRange("Document No.", PurchCrMemoHeader."No."); + SalesLine.SetRange(Type, "Sales Line Type"::Item); + PurchaseLine.FindFirst(); + + // Delete the first line (simulate user action) + PurchaseLine.Delete(true); + + // [THEN] Only the credited line should have a new usage data billing line + // Check usage data billing lines for the contract + UsageDataBilling.Reset(); + UsageDataBilling.SetRange("Document Type", UsageDataBilling."Document Type"::"Credit Memo"); + UsageDataBilling.SetRange("Document No.", PurchCrMemoHeader."No."); + UsageDataBilling.SetRange("Document Line No.", PurchaseLine."Line No."); + Assert.RecordIsEmpty(UsageDataBilling); + end; + + [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] + [Test] + procedure DeleteUsageDataBillingLineWhenRelatedSalesCrMemoLineIsDeleted() + var + UsageDataBilling: Record "Usage Data Billing"; + begin + // [SCENARIO] Creating a corrective credit memo for a contract with two usage-based service commitments, deleting one line, and posting the memo should only create a new usage data billing line for the credited line. + // [GIVEN] A customer contract with two usage-based service commitments, both invoiced + ResetAll(); + CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); + PostDocument := true; + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); + UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); + FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer, UsageDataBilling."Document Type"::"Posted Invoice"); + UsageDataBilling.FindFirst(); + + SalesInvoiceHeader.Get(UsageDataBilling."Document No."); + CorrectPostedSalesInvoice.CreateCreditMemoCopyDocument(SalesInvoiceHeader, SalesCrMemoHeader); + + SalesLine.Reset(); + SalesLine.SetRange("Document Type", "Sales Document Type"::"Credit Memo"); + SalesLine.SetRange("Document No.", SalesCrMemoHeader."No."); + SalesLine.SetRange(Type, "Sales Line Type"::Item); + SalesLine.FindFirst(); + + // Delete the first line (simulate user action) + SalesLine.Delete(true); + + // [THEN] Only the credited line should have a new usage data billing line + // Check usage data billing lines for the contract + UsageDataBilling.Reset(); + UsageDataBilling.SetRange("Document Type", UsageDataBilling."Document Type"::"Credit Memo"); + UsageDataBilling.SetRange("Document No.", SalesCrMemoHeader."No."); + UsageDataBilling.SetRange("Document Line No.", SalesLine."Line No."); + Assert.RecordIsEmpty(UsageDataBilling); + end; + [Test] [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] procedure EnsureUsageDataBillingContainsSubscriptionAndProduct() var UsageDataBilling: Record "Usage Data Billing"; UsageDataGenericImport: Record "Usage Data Generic Import"; - SubscriptionLine: Record "Subscription Line"; begin // [SCENARIO] Ensure that Usage Data Billing contains Subscription Line Entry No and Product details after processing usage data import @@ -333,7 +401,7 @@ codeunit 148153 "Usage Based Billing Test" [Test] procedure ExistForServiceCommitmentsDependsOnServiceCommitmentUsageData() var - ServiceCommitment1: Record "Subscription Line"; + SubscriptionLine2: Record "Subscription Line"; UsageDataBilling: Record "Usage Data Billing"; UsageDataExist: Boolean; begin @@ -341,17 +409,17 @@ codeunit 148153 "Usage Based Billing Test" // [GIVEN] Create Subscription Line Initialize(); - UsageBasedBTestLibrary.MockServiceCommitmentLine(ServiceCommitment1); + UsageBasedBTestLibrary.MockServiceCommitmentLine(SubscriptionLine2); // [WHEN] Subscription Line line is selected - UsageDataExist := UsageDataBilling.ExistForServiceCommitments(ServiceCommitment1.Partner, ServiceCommitment1."Subscription Header No.", ServiceCommitment1."Entry No."); + UsageDataExist := UsageDataBilling.ExistForServiceCommitments(SubscriptionLine2.Partner, SubscriptionLine2."Subscription Header No.", SubscriptionLine2."Entry No."); // [THEN] Action Usage Data should be disabled Assert.IsFalse(UsageDataExist, 'Usage Data Action should be disabled'); // [WHEN] Usage data is created and Subscription Line line is selected - UsageBasedBTestLibrary.MockUsageDataBillingForServiceCommitmentLine(UsageDataBilling, ServiceCommitment1.Partner, ServiceCommitment1."Subscription Header No.", ServiceCommitment1."Entry No."); - UsageDataExist := UsageDataBilling.ExistForServiceCommitments(ServiceCommitment1.Partner, ServiceCommitment1."Subscription Header No.", ServiceCommitment1."Entry No."); + UsageBasedBTestLibrary.MockUsageDataBillingForServiceCommitmentLine(UsageDataBilling, SubscriptionLine2.Partner, SubscriptionLine2."Subscription Header No.", SubscriptionLine2."Entry No."); + UsageDataExist := UsageDataBilling.ExistForServiceCommitments(SubscriptionLine2.Partner, SubscriptionLine2."Subscription Header No.", SubscriptionLine2."Entry No."); // [THEN] Action Usage Data should be enabled Assert.IsTrue(UsageDataExist, 'Usage Data Action should be enabled'); @@ -360,11 +428,16 @@ codeunit 148153 "Usage Based Billing Test" [Test] procedure ExpectErrorIfGenericSettingIsNotLinkedToDataExchangeDefinition() begin + // [SCENARIO] Error is expected when generic import setting is not linked to a data exchange definition + // [GIVEN] Usage data for generic import and an unlinked data exchange definition Initialize(); SetupUsageDataForProcessingToGenericImport(); SetupDataExchangeDefinition(); UsageDataImport."Processing Step" := Enum::"Processing Step"::"Create Imported Lines"; UsageDataImport.Modify(false); + + // [WHEN] Running import and process usage data + // [THEN] Error is raised because generic setting is not linked asserterror Codeunit.Run(Codeunit::"Import And Process Usage Data", UsageDataImport); end; @@ -374,19 +447,18 @@ codeunit 148153 "Usage Based Billing Test" var UsageDataGenericImport: Record "Usage Data Generic Import"; begin + // [SCENARIO] Error is expected when creating usage data billing if subscription lines are not fully assigned to contracts + // [GIVEN] Service object with subscription lines where only customer contract is assigned (vendor contract missing) Initialize(); SetupUsageDataForProcessingToGenericImport(WorkDate(), WorkDate(), WorkDate(), WorkDate(), 1, false); SetupDataExchangeDefinition(); ContractTestLibrary.CreateCustomer(Customer); - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - Item."Unit Price" := 1; - Item."Unit Cost" := 1; - Item.Modify(false); + CreateSubscriptionItemWithPrices(1, 1); SetupItemWithMultipleServiceCommitmentPackages(); - ContractTestLibrary.CreateServiceObjectForItem(ServiceObject, Item."No."); - ServiceObject.InsertServiceCommitmentsFromStandardServCommPackages(WorkDate()); - ServiceObject."End-User Customer No." := Customer."No."; - ServiceObject.Modify(false); + ContractTestLibrary.CreateServiceObjectForItem(SubscriptionHeader, Item."No."); + SubscriptionHeader.InsertServiceCommitmentsFromStandardServCommPackages(WorkDate()); + SubscriptionHeader."End-User Customer No." := Customer."No."; + SubscriptionHeader.Modify(false); CreateCustomerContractAndAssignServiceCommitments(); UsageBasedBTestLibrary.ConnectDataExchDefinitionToUsageDataGenericSettings(DataExchDef.Code, GenericImportSettings); @@ -398,8 +470,11 @@ codeunit 148153 "Usage Based Billing Test" PrepareServiceCommitmentAndUsageDataGenericImportForUsageBilling(UsageDataGenericImport, "Usage Based Pricing"::"Usage Quantity", '1D', '1D'); Codeunit.Run(Codeunit::"Import And Process Usage Data", UsageDataImport); UsageDataImport.SetRecFilter(); + + // [WHEN] Creating usage data billing UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Create Usage Data Billing"); + // [THEN] Processing status is Error because not all subscription lines are assigned to contracts UsageDataImport.Get(UsageDataImport."Entry No."); UsageDataImport.TestField("Processing Status", Enum::"Processing Status"::Error); end; @@ -410,11 +485,16 @@ codeunit 148153 "Usage Based Billing Test" var UsageDataBilling: Record "Usage Data Billing"; begin + // [SCENARIO] Error is expected when trying to delete a customer contract line that has usage data billing + // [GIVEN] Usage data billing linked to a customer contract line Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); UsageDataBilling.SetRange(Partner, "Service Partner"::Customer); UsageDataBilling.FindFirst(); CustomerContractLine.Get(UsageDataBilling."Subscription Contract No.", UsageDataBilling."Subscription Contract Line No."); + + // [WHEN] Deleting the customer contract line + // [THEN] Error is raised asserterror CustomerContractLine.Delete(true); end; @@ -422,40 +502,36 @@ codeunit 148153 "Usage Based Billing Test" [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] procedure ExpectErrorOnDeleteUsageDataImportIfDocumentIsCreated() begin + // [SCENARIO] Error is expected when deleting usage data import after documents have been created + // [GIVEN] Usage data import with processed billing and created customer contract invoices Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); PostDocument := true; UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); + + // [WHEN] Deleting the usage data import or its billing lines + // [THEN] Error is raised asserterror UsageDataImport.Delete(true); asserterror UsageDataImport.DeleteUsageDataBillingLines(); end; - [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] - procedure ExpectErrorOnProcessUsageDataBillingWithZeroQuantity() - begin - Initialize(); - CreateUsageDataBilling("Usage Based Pricing"::"Usage Quantity", WorkDate(), WorkDate(), WorkDate(), WorkDate(), 0); - ServiceObject.Quantity := 0; - ServiceObject.Modify(false); - Codeunit.Run(Codeunit::"Process Usage Data Billing", UsageDataImport); - UsageDataImport.TestField("Processing Status", "Processing Status"::Error); - end; - [Test] procedure ExpectErrorWhenDataExchangeDefinitionIsNotGenericImportForGenericImportSettings() var DataExchDefType: Enum "Data Exchange Definition Type"; ListOfOrdinals: List of [Integer]; begin - // [GIVEN] Error for validating "Data Exchange Definition" for "Data Exchange Definition Type" different than "Generic Import" + // [SCENARIO] Error for validating Data Exchange Definition for types different than Generic Import + // [GIVEN] A usage data supplier with generic import settings Initialize(); UsageBasedBTestLibrary.CreateUsageDataSupplier(UsageDataSupplier, Enum::"Usage Data Supplier Type"::Generic, true, Enum::"Vendor Invoice Per"::Import); UsageBasedBTestLibrary.CreateGenericImportSettings(GenericImportSettings, UsageDataSupplier."No.", true, true); ListOfOrdinals := "Data Exchange Definition Type".Ordinals(); + // [WHEN] Validating data exchange definition for each type + // [THEN] Only "Generic Import" type succeeds; all others raise an error foreach i in ListOfOrdinals do begin DataExchDefType := "Data Exchange Definition Type".FromInteger(i); UsageBasedBTestLibrary.CreateDataExchDefinition(DataExchDef, FileType::"Variable Text", DataExchDefType, FileEncoding::"UTF-8", ColumnSeparator::Semicolon, '', 1); @@ -472,6 +548,8 @@ codeunit 148153 "Usage Based Billing Test" var UsageDataGenericImport: Record "Usage Data Generic Import"; begin + // [SCENARIO] Error is expected when subscription line start date does not match usage data + // [GIVEN] Usage data imported on WorkDate and service object created with start date before WorkDate Initialize(); SetupUsageDataForProcessingToGenericImport(); SetupDataExchangeDefinition(); @@ -481,8 +559,12 @@ codeunit 148153 "Usage Based Billing Test" UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); UsageDataGenericImport.FindFirst(); - SetupServiceObjectAndContracts(CalcDate('<-1D>', WorkDate())); // USage data generic import is create on workdate + SetupServiceObjectAndContracts(CalcDate('<-1D>', WorkDate())); // Usage data generic import is created on WorkDate + + // [WHEN] Processing imported lines with mismatched start date ProcessUsageDataImport(Enum::"Processing Step"::"Process Imported Lines"); + + // [THEN] Processing status is Error due to invalid start date UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); UsageDataGenericImport.FindFirst(); UsageDataGenericImport.TestField("Processing Status", Enum::"Processing Status"::Error); @@ -494,16 +576,17 @@ codeunit 148153 "Usage Based Billing Test" var UsageDataBilling: Record "Usage Data Billing"; begin - // [SCENARIO] When usage data is processed with an error + // [SCENARIO] When usage data import has error processing status // expect no invoices to be created - // [GIVEN] Create usage data which will for sure cause an error + // [GIVEN] Create usage data and set processing status to error manually Initialize(); - CreateUsageDataBilling("Usage Based Pricing"::"Usage Quantity", WorkDate(), WorkDate(), WorkDate(), WorkDate(), 0); - ServiceObject.Quantity := 0; // Zero Quantity on Subscription will cause error - ServiceObject.Modify(false); + CreateUsageDataBilling("Usage Based Pricing"::"Usage Quantity", WorkDate(), WorkDate(), WorkDate(), WorkDate(), LibraryRandom.RandDec(10, 2)); PostDocument := false; UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + UsageDataImport.Get(UsageDataImport."Entry No."); + UsageDataImport.Validate("Processing Status", Enum::"Processing Status"::Error); + UsageDataImport.Modify(false); // [WHEN] Try to create Customer Subscription Contract invoices; Error should be caught and no usage data lines should be taken into contract invoice UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); @@ -515,18 +598,147 @@ codeunit 148153 "Usage Based Billing Test" Assert.RecordIsEmpty(UsageDataBilling); end; + [Test] + [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] + procedure ProcessUsageDataBillingWithZeroQuantitySucceeds() + begin + // [SCENARIO] Processing usage data billing with quantity 0 succeeds without error + // [GIVEN] Usage data billing with zero quantity and zero service object quantity + Initialize(); + CreateUsageDataBilling("Usage Based Pricing"::"Usage Quantity", WorkDate(), WorkDate(), WorkDate(), WorkDate(), 0); + SubscriptionHeader.Quantity := 0; + SubscriptionHeader.Modify(false); + + // [WHEN] Processing usage data billing + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + + // [THEN] Processing should not result in error + UsageDataImport.Get(UsageDataImport."Entry No."); + Assert.AreNotEqual(Enum::"Processing Status"::Error, UsageDataImport."Processing Status", 'Processing of usage data billing with quantity 0 should not result in error.'); + end; + + [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] + [Test] + procedure ResetUsageDataBillingWhenRelatedSalesInvoiceLineIsDeleted() + var + UsageDataBilling: Record "Usage Data Billing"; + begin + // [SCENARIO] When sales invoice with usage data is created if a line is deleted related usage data billing should be reset + // [GIVEN] A customer contract with usage-based service commitments and a sales invoice created from usage data + ResetAll(); + CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); + PostDocument := false; + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); + UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); + FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer, UsageDataBilling."Document Type"::"Invoice"); + UsageDataBilling.FindFirst(); + + SalesLine.Reset(); + SalesLine.SetRange("Document Type", "Sales Document Type"::Invoice); + SalesLine.SetRange("Document No.", UsageDataBilling."Document No."); + SalesLine.SetRange(Type, "Sales Line Type"::Item); + SalesLine.FindFirst(); + + // [WHEN] Delete the first line (simulate user action) + SalesLine.Delete(true); + + // [THEN] Check that invoice data is removed from usage data billing + UsageDataBilling.Reset(); + UsageDataBilling.Get(UsageDataBilling."Entry No."); + UsageDataBilling.TestField("Document Type", UsageDataBilling."Document Type"::None); + UsageDataBilling.TestField("Document No.", ''); + UsageDataBilling.TestField("Document Line No.", 0); + UsageDataBilling.TestField("Billing Line Entry No.", 0); + end; + + [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateVendorBillingDocumentPageHandler,MessageHandler')] + [Test] + procedure ResetUsageDataBillingWhenRelatedPurchLineIsDeleted() + var + UsageDataBilling: Record "Usage Data Billing"; + begin + // [SCENARIO] When purchase invoice with usage data is created if a line is deleted related usage data billing should be reset + // [GIVEN] A vendor contract with usage-based service commitments and a purchase invoice created from usage data + ResetAll(); + CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); + UsageDataImport.CollectVendorContractsAndCreateInvoices(UsageDataImport); + FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Vendor, UsageDataBilling."Document Type"::Invoice); + UsageDataBilling.FindFirst(); + + PurchaseHeader.Get(PurchaseHeader."Document Type"::Invoice, UsageDataBilling."Document No."); + + PurchaseLine.Reset(); + PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type"); + PurchaseLine.SetRange("Document No.", PurchaseHeader."No."); + PurchaseLine.SetRange(Type, "Sales Line Type"::Item); + PurchaseLine.FindFirst(); + + // [WHEN] Delete the first line (simulate user action) + PurchaseLine.Delete(true); + + // [THEN] Check that invoice data is removed from usage data billing + UsageDataBilling.Reset(); + UsageDataBilling.Get(UsageDataBilling."Entry No."); + UsageDataBilling.TestField("Document Type", UsageDataBilling."Document Type"::None); + UsageDataBilling.TestField("Document No.", ''); + UsageDataBilling.TestField("Document Line No.", 0); + UsageDataBilling.TestField("Billing Line Entry No.", 0); + end; + + [Test] + [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler,StrMenuHandlerClearBillingProposal')] + procedure TestBillingLineInUsageDataNoWhenBillingProposalIsCreated() + var + UsageDataBilling: Record "Usage Data Billing"; + BillingProposal: Codeunit "Billing Proposal"; + begin + //[SCENARIO] Create recurring billing for simple customer contract; Check if Usage Data Billing Line No. has billing line no + + ResetAll(); + //[GIVEN]: Setup Usage Data Import and process it + CreateUsageDataBilling("Usage Based Pricing"::"Usage Quantity", LibraryRandom.RandDec(10, 2)); + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + + //[WHEN]: Create recurring billing proposal for customer contract + CreateBillingProposalForSimpleCustomerContract(); + + //[THEN]: Check if Usage Data Billing Line No. has billing line no + BillingLine.Reset(); + BillingLine.SetRange(Partner, BillingLine.Partner::Customer); + BillingLine.FindFirst(); + UsageDataBilling.Reset(); + UsageDataBilling.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); + UsageDataBilling.SetRange(Partner, UsageDataBilling.Partner::Customer); + UsageDataBilling.FindSet(); + repeat + UsageDataBilling.TestField("Billing Line Entry No.", BillingLine."Entry No."); + until UsageDataBilling.Next() = 0; + + LibraryVariableStorage.Enqueue(2); //StrMenuHandlerClearBillingProposal + BillingProposal.DeleteBillingProposal(BillingTemplate.Code); + UsageDataBilling.Get(UsageDataBilling."Entry No."); + UsageDataBilling.TestField("Billing Line Entry No.", 0); + end; + [Test] [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] procedure TestCreateContractInvoiceForMultipleCustomerContracts() begin + // [SCENARIO] Contract invoices can be created for multiple customer contracts from a single usage data import + // [GIVEN] Usage data billing for multiple customer contracts Initialize(); for i := 1 to 2 do // create usage data for 3 different contracts CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); - // Process usage data and create Customer Subscription Contract invoices + // [WHEN] Processing usage data and creating customer contract invoices UsageDataImport.Reset(); UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); UsageDataImport.Reset(); + + // [THEN] Customer contract invoices are created successfully UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); end; @@ -534,14 +746,18 @@ codeunit 148153 "Usage Based Billing Test" [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateVendorBillingDocumentPageHandler,MessageHandler')] procedure TestCreateContractInvoiceForMultipleVendorContracts() begin + // [SCENARIO] Contract invoices can be created for multiple vendor contracts from a single usage data import + // [GIVEN] Usage data billing for multiple vendor contracts Initialize(); for i := 1 to 2 do // create usage data for 3 different contracts CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); - // Process usage data and create Vendor Subscription Contract invoices + // [WHEN] Processing usage data and creating vendor contract invoices UsageDataImport.Reset(); UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); UsageDataImport.Reset(); + + // [THEN] Vendor contract invoices are created successfully UsageDataImport.CollectVendorContractsAndCreateInvoices(UsageDataImport); end; @@ -549,13 +765,13 @@ codeunit 148153 "Usage Based Billing Test" [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] procedure TestCreateContractInvoiceFromUsageDataImport() begin - Initialize(); - CreateUsageDataBilling("Usage Based Pricing"::"Usage Quantity", LibraryRandom.RandDec(10, 2)); - PostDocument := false; - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); - UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); - CheckIfSalesDocumentsHaveBeenCreated(); + // [SCENARIO] Contract invoice can be created from usage data import for each usage-based pricing type + // [GIVEN] Usage data billing for a given pricing type + // [WHEN] Processing usage data billing and creating customer contract invoices + // [THEN] Sales documents are created successfully + CreateContractInvoiceFromUsageDataImportForPricingType("Usage Based Pricing"::"Usage Quantity"); + CreateContractInvoiceFromUsageDataImportForPricingType("Usage Based Pricing"::"Fixed Quantity"); + CreateContractInvoiceFromUsageDataImportForPricingType("Usage Based Pricing"::"Unit Cost Surcharge"); end; [Test] @@ -577,19 +793,6 @@ codeunit 148153 "Usage Based Billing Test" Assert.AreEqual(false, BillingLine.IsEmpty, 'A new Billing Line should be created for Usage Based Service Commitments with usage data when creating an invoice from the contract'); end; - [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] - procedure TestCreateCustomerContractInvoiceFromUsageDataImport() - begin - Initialize(); - CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); - PostDocument := false; - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); - UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); - CheckIfSalesDocumentsHaveBeenCreated(); - end; - [Test] [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure TestCreateInvoicesForMoreThanOneContractPerImportViaRecurringBilling() @@ -610,16 +813,16 @@ codeunit 148153 "Usage Based Billing Test" TestSubscribers.SetTestContext('TestCreateInvoicesForMoreThanOneContractPerImport'); BindSubscription(TestSubscribers); - ContractTestLibrary.AssignServiceObjectForItemToCustomerContract(CustomerContract, ServiceObject, false); // ExchangeRateSelectionModalPageHandler,MessageHandler + ContractTestLibrary.AssignServiceObjectForItemToCustomerContract(CustomerContract, SubscriptionHeader, false); // ExchangeRateSelectionModalPageHandler,MessageHandler ContractTestLibrary.AssignServiceObjectForItemToCustomerContract(CustomerContract2, ServiceObject2, false); // ExchangeRateSelectionModalPageHandler,MessageHandler UnbindSubscription(TestSubscribers); - ServiceCommitment.SetFilter("Subscription Header No.", '%1|%2', ServiceObject."No.", ServiceObject2."No."); - QuantityOfServiceCommitments := ServiceCommitment.Count(); - ServiceCommitment.FindSet(); + SubscriptionLine.SetFilter("Subscription Header No.", '%1|%2', SubscriptionHeader."No.", ServiceObject2."No."); + QuantityOfServiceCommitments := SubscriptionLine.Count(); + SubscriptionLine.FindSet(); repeat - CreateUsageDataBillingDummyDataFromServiceCommitment(UsageDataBilling, UsageDataImport."Entry No.", ServiceCommitment); - until ServiceCommitment.Next() = 0; + CreateUsageDataBillingDummyDataFromSubscriptionLine(UsageDataBilling, UsageDataImport."Entry No.", SubscriptionLine); + until SubscriptionLine.Next() = 0; // [WHEN] Creating a billing proposal via Contract or "Recurring Billing" ContractTestLibrary.CreateRecurringBillingTemplate(BillingTemplate, '', '', '', Enum::"Service Partner"::Customer); @@ -650,16 +853,16 @@ codeunit 148153 "Usage Based Billing Test" TestSubscribers.SetTestContext('TestCreateInvoicesForMoreThanOneContractPerImport'); BindSubscription(TestSubscribers); - ContractTestLibrary.AssignServiceObjectForItemToCustomerContract(CustomerContract, ServiceObject, false); // ExchangeRateSelectionModalPageHandler,MessageHandler + ContractTestLibrary.AssignServiceObjectForItemToCustomerContract(CustomerContract, SubscriptionHeader, false); // ExchangeRateSelectionModalPageHandler,MessageHandler ContractTestLibrary.AssignServiceObjectForItemToCustomerContract(CustomerContract2, ServiceObject2, false); // ExchangeRateSelectionModalPageHandler,MessageHandler UnbindSubscription(TestSubscribers); - ServiceCommitment.SetFilter("Subscription Header No.", '%1|%2', ServiceObject."No.", ServiceObject2."No."); - QuantityOfServiceCommitments := ServiceCommitment.Count(); - ServiceCommitment.FindSet(); + SubscriptionLine.SetFilter("Subscription Header No.", '%1|%2', SubscriptionHeader."No.", ServiceObject2."No."); + QuantityOfServiceCommitments := SubscriptionLine.Count(); + SubscriptionLine.FindSet(); repeat - CreateUsageDataBillingDummyDataFromServiceCommitment(UsageDataBilling, UsageDataImport."Entry No.", ServiceCommitment); - until ServiceCommitment.Next() = 0; + CreateUsageDataBillingDummyDataFromSubscriptionLine(UsageDataBilling, UsageDataImport."Entry No.", SubscriptionLine); + until SubscriptionLine.Next() = 0; // [WHEN] Creating a billing proposal via "Usage Data Imports" (CollectCustomerContractsAndCreateInvoices) UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); // CreateCustomerBillingDocumentPageHandler @@ -676,8 +879,12 @@ codeunit 148153 "Usage Based Billing Test" UsageDataBilling: Record "Usage Data Billing"; UsageDataGenericImport: Record "Usage Data Generic Import"; begin + // [SCENARIO] Usage data billing records are created correctly from generic import + // [GIVEN] Usage data for fixed quantity pricing Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); + + // [THEN] Usage data import has Ok status and billing records match the generic import UsageDataImport.FindLast(); UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); UsageDataBilling.FindLast(); @@ -690,19 +897,20 @@ codeunit 148153 "Usage Based Billing Test" [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] procedure TestCreateUsageDataBillingDocumentsWhenBillingRequiredInBillingProposal() begin - // Create recurring billing for simple Customer Subscription Contract - // Set update required - // Expect no error on create Usage data billing documents + // [SCENARIO] Usage data billing documents can be created even when billing proposal requires update + // [GIVEN] Usage data billing with a billing proposal and modified subscription line discount Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); PostDocument := false; UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); CreateBillingProposalForSimpleCustomerContract(); - ServiceCommitment.Get(BillingLine."Subscription Line Entry No."); - ServiceCommitment.Validate("Discount %", LibraryRandom.RandDec(50, 2)); - ServiceCommitment.Modify(true); + SubscriptionLine.Get(BillingLine."Subscription Line Entry No."); + SubscriptionLine.Validate("Discount %", LibraryRandom.RandDec(50, 2)); + SubscriptionLine.Modify(true); + // [WHEN] Collecting customer contracts and creating invoices + // [THEN] No error occurs despite billing proposal requiring update UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); end; @@ -711,14 +919,17 @@ codeunit 148153 "Usage Based Billing Test" var UsageDataGenericImport: Record "Usage Data Generic Import"; begin - // Create Setup Data and Import file + // [SCENARIO] Usage data generic import records are created from an imported file + // [GIVEN] Setup data and data exchange definition for processing an imported file Initialize(); SetupUsageDataForProcessingToGenericImport(); SetupDataExchangeDefinition(); - // Create Data Exchange definition for processing imported file and Creating Usage Data Generic Import UsageBasedBTestLibrary.ConnectDataExchDefinitionToUsageDataGenericSettings(DataExchDef.Code, GenericImportSettings); + + // [WHEN] Creating imported lines ProcessUsageDataImport(Enum::"Processing Step"::"Create Imported Lines"); - // Expect that Usage Data Generic Import is created + + // [THEN] Usage data generic import record is created with None processing status Commit(); UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); UsageDataGenericImport.FindFirst(); @@ -729,47 +940,35 @@ codeunit 148153 "Usage Based Billing Test" [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateVendorBillingDocumentPageHandler,MessageHandler')] procedure TestCreateVendorContractInvoiceFromUsageDataImport() begin + // [SCENARIO] Vendor contract invoice can be created from usage data import + // [GIVEN] Usage data billing for usage quantity pricing Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Usage Quantity", LibraryRandom.RandDec(10, 2)); PostDocument := false; + + // [WHEN] Processing usage data billing and creating vendor contract invoices UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); UsageDataImport.CollectVendorContractsAndCreateInvoices(UsageDataImport); + + // [THEN] Purchase documents are created CheckIfPurchaseDocumentsHaveBeenCreated(); end; [Test] [HandlerFunctions('MessageHandler,CreateCustomerBillingDocumentPageHandler')] - procedure TestDailyServiceCommitmentWithDailyUsageData() + procedure TestDailySubscriptionWithDailyUsageData() begin - Initialize(); - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - Item."Unit Price" := 2; - Item."Unit Cost" := 1; - Item.Modify(false); - - SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Usage Quantity", "Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, - '1D', '1D', '1Y', "Service Partner"::Customer, 100, Item."No."); - - ProcessUsageDataWithSimpleGenericImport(WorkDate(), WorkDate(), WorkDate(), CalcDate('', WorkDate()), 1); - CreateContractInvoicesAndTestProcessedUsageData(); + // [SCENARIO] Daily subscription line with daily usage data creates correct billing + TestSubscriptionWithUsageData('1D', WorkDate(), CalcDate('', WorkDate()), 2, false); end; [Test] [HandlerFunctions('MessageHandler,CreateCustomerBillingDocumentPageHandler')] - procedure TestDailyServiceCommitmentWithMonthlyUsageData() + procedure TestDailySubscriptionWithMonthlyUsageData() begin - Initialize(); - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - Item."Unit Price" := 2; - Item."Unit Cost" := 1; - Item.Modify(false); - - SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Usage Quantity", "Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, - '1D', '1D', '1Y', "Service Partner"::Customer, 100, Item."No."); - - ProcessUsageDataWithSimpleGenericImport(WorkDate(), CalcDate('', WorkDate()), WorkDate(), CalcDate('', WorkDate()), 1); - CreateContractInvoicesAndTestProcessedUsageData(); + // [SCENARIO] Daily subscription line with monthly usage data creates correct billing + TestSubscriptionWithUsageData('1D', CalcDate('', WorkDate()), CalcDate('', WorkDate()), 2, false); end; [Test] @@ -779,11 +978,16 @@ codeunit 148153 "Usage Based Billing Test" UsageDataBilling: Record "Usage Data Billing"; UsageDataGenericImport: Record "Usage Data Generic Import"; begin + // [SCENARIO] Deleting usage data billing lines removes all related records and resets processing status + // [GIVEN] Usage data billing records for fixed quantity pricing Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); + + // [WHEN] Deleting usage data billing lines UsageDataImport.DeleteUsageDataBillingLines(); Commit(); // retain data after asserterror + // [THEN] All usage data billing and generic import records are removed, processing status is reset FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No."); Assert.RecordIsEmpty(UsageDataBilling); Clear(UsageDataGenericImport); @@ -801,18 +1005,22 @@ codeunit 148153 "Usage Based Billing Test" NoSeriesLine: Record "No. Series Line"; LastUsedNo: Code[20]; begin + // [SCENARIO] Processing usage data billing does not consume sales order numbers + // [GIVEN] The last used number from the sales order number series Initialize(); SalesSetup.Get(); NoSeriesLine.SetRange("Series Code", SalesSetup."Order Nos."); NoSeriesLine.FindLast(); LastUsedNo := NoSeriesLine."Last No. Used"; + // [WHEN] Processing usage data billing Currency.InitRoundingPrecision(); CreateUsageDataBilling("Usage Based Pricing"::"Usage Quantity", LibraryRandom.RandDec(10, 2)); UsageDataSupplier."Unit Price from Import" := false; UsageDataSupplier.Modify(false); UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + // [THEN] Sales order number series remains unchanged NoSeriesLine.SetRange("Series Code", SalesSetup."Order Nos."); NoSeriesLine.FindLast(); Assert.AreEqual(LastUsedNo, NoSeriesLine."Last No. Used", 'No Series changed after GetSalesPrice()'); @@ -825,11 +1033,15 @@ codeunit 148153 "Usage Based Billing Test" UsageDataBilling: Record "Usage Data Billing"; UsageDataGenericImport: Record "Usage Data Generic Import"; begin + // [SCENARIO] Deleting a usage data import removes all related blobs, generic imports, and billing records + // [GIVEN] Multiple usage data imports with billing records Initialize(); j := LibraryRandom.RandIntInRange(2, 10); for i := 1 to j do CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); + // [WHEN] Deleting each usage data import + // [THEN] All related data (blobs, generic imports, billing) is removed UsageDataImport.Reset(); UsageDataImport.FindSet(); repeat @@ -853,8 +1065,12 @@ codeunit 148153 "Usage Based Billing Test" [Test] procedure TestImportFileToUsageDataBlob() begin + // [SCENARIO] Usage data file can be imported to a usage data blob + // [GIVEN] Usage data setup for generic import processing Initialize(); SetupUsageDataForProcessingToGenericImport(); + + // [THEN] Usage data blob is created with correct import status and data UsageDataBlob.TestField("Usage Data Import Entry No.", UsageDataImport."Entry No."); UsageDataBlob.TestField("Import Status", Enum::"Processing Status"::Ok); UsageDataBlob.TestField(Data); @@ -863,19 +1079,10 @@ codeunit 148153 "Usage Based Billing Test" [Test] [HandlerFunctions('MessageHandler,CreateCustomerBillingDocumentPageHandler')] - procedure TestMonthlyServiceCommitmentWithDailyUsageData() + procedure TestMonthlySubscriptionWithDailyUsageData() begin - Initialize(); - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - Item."Unit Price" := 2; - Item."Unit Cost" := 1; - Item.Modify(false); - - SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Usage Quantity", "Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, - '1M', '1M', '1Y', "Service Partner"::Customer, 100, Item."No."); - - ProcessUsageDataWithSimpleGenericImport(WorkDate(), WorkDate(), WorkDate(), CalcDate('', WorkDate()), 1); - CreateContractInvoicesAndTestProcessedUsageData(); + // [SCENARIO] Monthly subscription line with daily usage data creates correct billing + TestSubscriptionWithUsageData('1M', WorkDate(), CalcDate('', WorkDate()), 2, false); end; [Test] @@ -886,6 +1093,8 @@ codeunit 148153 "Usage Based Billing Test" BillingDate2: Date; TestBillingDate: Date; begin + // [SCENARIO] Only usage data within the billing period is processed; gaps between periods are skipped + // [GIVEN] Two usage data imports for non-consecutive billing periods Initialize(); BillingDate1 := WorkDate(); TestBillingDate := CalcDate('<1M>', WorkDate()); @@ -899,6 +1108,8 @@ codeunit 148153 "Usage Based Billing Test" PostDocument := false; UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); + + // [THEN] The month between the two billing periods is not billed // Expect that month between BillingDate1 and BillingDate2 is skipped BillingLine.Reset(); BillingLine.SetRange(Partner, "Service Partner"::Customer); @@ -915,19 +1126,18 @@ codeunit 148153 "Usage Based Billing Test" ProcessUsageDataBilling: Codeunit "Process Usage Data Billing"; RoundingPrecision: Decimal; begin + // [SCENARIO] Price calculation in usage-based billing with daily billing period uses subscription line price + // [GIVEN] Service object with daily usage-based subscription lines and a single day of usage data Initialize(); SetupUsageDataForProcessingToGenericImport(WorkDate(), WorkDate(), WorkDate(), WorkDate(), 1, false); SetupDataExchangeDefinition(); ContractTestLibrary.CreateCustomer(Customer); - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - Item."Unit Price" := 1; - Item."Unit Cost" := 1; - Item.Modify(false); + CreateSubscriptionItemWithPrices(1, 1); SetupItemWithMultipleServiceCommitmentPackages(); - ContractTestLibrary.CreateServiceObjectForItem(ServiceObject, Item."No."); - ServiceObject.InsertServiceCommitmentsFromStandardServCommPackages(WorkDate()); - ServiceObject."End-User Customer No." := Customer."No."; - ServiceObject.Modify(false); + ContractTestLibrary.CreateServiceObjectForItem(SubscriptionHeader, Item."No."); + SubscriptionHeader.InsertServiceCommitmentsFromStandardServCommPackages(WorkDate()); + SubscriptionHeader."End-User Customer No." := Customer."No."; + SubscriptionHeader.Modify(false); CreateCustomerContractAndAssignServiceCommitments(); CreateVendorContractAndAssignServiceCommitments(); UsageBasedBTestLibrary.ConnectDataExchDefinitionToUsageDataGenericSettings(DataExchDef.Code, GenericImportSettings); @@ -943,20 +1153,55 @@ codeunit 148153 "Usage Based Billing Test" UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Create Usage Data Billing"); UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + // [THEN] Unit price in usage data billing matches the subscription line price FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No."); UsageDataBilling.FindFirst(); ProcessUsageDataBilling.SetRoundingPrecision(RoundingPrecision, UsageDataBilling."Unit Price", Currency); - Assert.AreEqual(Round(ServiceCommitment.Price, RoundingPrecision), UsageDataBilling."Unit Price", 'Amount was not calculated properly in Usage data.'); + Assert.AreEqual(Round(SubscriptionLine.Price, RoundingPrecision), UsageDataBilling."Unit Price", 'Amount was not calculated properly in Usage data.'); + end; + + [Test] + [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] + procedure TestProcessImportedLinesWithZeroQuantity() + var + UsageDataGenericImport: Record "Usage Data Generic Import"; + begin + // [SCENARIO] Usage data with quantity 0 can be processed without error during "Process Imported Lines" + Initialize(); + + // [GIVEN] Usage data generic import with Quantity = 0 + SetupUsageDataForProcessingToGenericImport(WorkDate(), CalcDate('', WorkDate()), WorkDate(), CalcDate('', WorkDate()), 0); + SetupDataExchangeDefinition(); + SetupServiceObjectAndContracts(WorkDate()); + UsageBasedBTestLibrary.ConnectDataExchDefinitionToUsageDataGenericSettings(DataExchDef.Code, GenericImportSettings); + + // [WHEN] Create and Process Imported Lines + ProcessUsageDataImport(Enum::"Processing Step"::"Create Imported Lines"); + ProcessUsageDataImport(Enum::"Processing Step"::"Process Imported Lines"); + + // [THEN] No error occurs - processing status should not be error due to zero quantity + UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); + UsageDataGenericImport.FindFirst(); + PrepareServiceCommitmentAndUsageDataGenericImportForUsageBilling(UsageDataGenericImport, "Usage Based Pricing"::"Usage Quantity"); + Codeunit.Run(Codeunit::"Import And Process Usage Data", UsageDataImport); + + // [THEN] Verify the processing was successful + UsageDataGenericImport.FindFirst(); + Assert.AreEqual(Enum::"Processing Status"::Ok, UsageDataGenericImport."Processing Status", 'Processing of usage data with quantity 0 should succeed.'); end; [Test] [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure TestProcessUsageDataBilling() begin + // [SCENARIO] Usage data billing can be processed to update subscription and subscription lines + // [GIVEN] Usage data billing for fixed quantity pricing Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); + + // [WHEN] Processing usage data billing UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - // Test update Subscription and Subscription Line + // [THEN] Subscription and subscription lines are updated end; [Test] @@ -971,13 +1216,13 @@ codeunit 148153 "Usage Based Billing Test" Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); - ServiceCommitment.Reset(); - ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); - ServiceCommitment.FindSet(); + SubscriptionLine.Reset(); + SubscriptionLine.SetRange("Subscription Header No.", SubscriptionHeader."No."); + SubscriptionLine.FindSet(); repeat - ServiceCommitment.Validate("Discount %", 100); - ServiceCommitment.Modify(true); - until ServiceCommitment.Next() = 0; + SubscriptionLine.Validate("Discount %", 100); + SubscriptionLine.Modify(true); + until SubscriptionLine.Next() = 0; // [WHEN] Expect no error to happen on processing usage data billing UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); @@ -995,26 +1240,27 @@ codeunit 148153 "Usage Based Billing Test" ExpectedResult: Decimal; RoundingPrecision: Decimal; begin + // [SCENARIO] Fixed quantity pricing with partial billing periods calculates prorated amounts correctly + // [GIVEN] A subscription line with monthly billing starting at the beginning of the month Initialize(); - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - Item."Unit Price" := LibraryRandom.RandDec(100, 2); - Item."Unit Cost" := LibraryRandom.RandDec(100, 2); - Item.Modify(false); + CreateSubscriptionItemWithPrices(LibraryRandom.RandDec(100, 2), LibraryRandom.RandDec(100, 2)); SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Fixed Quantity", "Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, '1M', '1M', '1M', "Service Partner"::Customer, 100, Item."No."); - ServiceCommitment.Reset(); - ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); - ServiceCommitment.SetRange(Partner, "Service Partner"::Customer); - ServiceCommitment.FindFirst(); + SubscriptionLine.Reset(); + SubscriptionLine.SetRange("Subscription Header No.", SubscriptionHeader."No."); + SubscriptionLine.SetRange(Partner, "Service Partner"::Customer); + SubscriptionLine.FindFirst(); - ServiceCommitment.Validate("Subscription Line Start Date", CalcDate('<-CM>', WorkDate())); - ServiceCommitment.Modify(false); - ExpectedResult := ServiceCommitment.UnitPriceForPeriod(CalcDate('<-CM>', WorkDate()), WorkDate()) * ServiceObject.Quantity; + SubscriptionLine.Validate("Subscription Line Start Date", CalcDate('<-CM>', WorkDate())); + SubscriptionLine.Modify(false); + ExpectedResult := SubscriptionLine.UnitPriceForPeriod(CalcDate('<-CM>', WorkDate()), WorkDate()) * SubscriptionHeader.Quantity; - ProcessUsageDataWithSimpleGenericImport(CalcDate('<-CM>', WorkDate()), WorkDate(), CalcDate('<-CM>', WorkDate()), WorkDate(), ServiceObject.Quantity, "Usage Based Pricing"::"Fixed Quantity"); + // [WHEN] Processing usage data with fixed quantity for a partial period + ProcessUsageDataWithSimpleGenericImport(CalcDate('<-CM>', WorkDate()), WorkDate(), CalcDate('<-CM>', WorkDate()), WorkDate(), SubscriptionHeader.Quantity, "Usage Based Pricing"::"Fixed Quantity"); + // [THEN] The calculated amount matches the expected prorated amount FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer); UsageDataBilling.CalcSums(Amount); CalculatedAmount := UsageDataBilling.Amount; @@ -1030,6 +1276,8 @@ codeunit 148153 "Usage Based Billing Test" var UsageDataGenericImport: Record "Usage Data Generic Import"; begin + // [SCENARIO] Processing usage data generic import creates supplier references for subscription, customer, and product + // [GIVEN] Usage data imported and service object with contracts Initialize(); SetupUsageDataForProcessingToGenericImport(); SetupDataExchangeDefinition(); @@ -1038,9 +1286,11 @@ codeunit 148153 "Usage Based Billing Test" UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); UsageDataGenericImport.FindFirst(); + // [WHEN] Processing imported lines with service object and contracts set up SetupServiceObjectAndContracts(WorkDate()); ProcessUsageDataImport(Enum::"Processing Step"::"Process Imported Lines"); - // Process Usage Data Generic Import + + // [THEN] Supplier references for subscription, customer, subscription, and product are created CheckIfUsageDataSubscriptionIsCreated(UsageDataGenericImport); CheckIfUsageDataCustomerIsCreated(UsageDataGenericImport."Customer ID"); CheckIfCustomerSupplierReferencesAreIsCreated(UsageDataGenericImport."Customer ID"); @@ -1058,14 +1308,19 @@ codeunit 148153 "Usage Based Billing Test" ExpectedResult: Decimal; Result: Decimal; begin + // [SCENARIO] Prorated amount for daily prices equals the base amount for a single day + // [GIVEN] A subscription line with daily billing period and a one-day charge period Initialize(); BaseAmount := 100; Evaluate(BillingBasePeriod, '1D'); ChargeStartDate := CalcDate('<-CY>', WorkDate()); ChargeEndDate := ChargeStartDate; - MockServiceCommitment(ServiceCommitment, BillingBasePeriod, BillingBasePeriod, BaseAmount); - Result := ServiceCommitment.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + MockServiceCommitment(SubscriptionLine, BillingBasePeriod, BillingBasePeriod, BaseAmount); + // [WHEN] Calculating unit price for the period + Result := SubscriptionLine.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + + // [THEN] Result equals the base amount ExpectedResult := BaseAmount; Assert.AreEqual(Result, ExpectedResult, 'Amount was not calculated properly'); end; @@ -1080,22 +1335,29 @@ codeunit 148153 "Usage Based Billing Test" ExpectedResult: Decimal; Result: Decimal; begin + // [SCENARIO] Prorated amount for monthly prices scales correctly for full-year and partial-month periods + // [GIVEN] A subscription line with monthly billing Initialize(); BaseAmount := 100; Evaluate(BillingBasePeriod, '1M'); + + // [WHEN] Calculating unit price for a full year ChargeStartDate := CalcDate('<-CY>', WorkDate()); ChargeEndDate := CalcDate('', ChargeStartDate); - MockServiceCommitment(ServiceCommitment, BillingBasePeriod, BillingBasePeriod, BaseAmount); - Result := ServiceCommitment.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + MockServiceCommitment(SubscriptionLine, BillingBasePeriod, BillingBasePeriod, BaseAmount); + Result := SubscriptionLine.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + // [THEN] Result equals 12 times the monthly base amount ExpectedResult := BaseAmount * 12; Assert.AreEqual(Result, ExpectedResult, 'Amount was not calculated properly'); + // [WHEN] Calculating unit price for a single month period ChargeStartDate := CalcDate('<15D>', ChargeStartDate); ChargeEndDate := CalcDate('<1M>', ChargeStartDate); - MockServiceCommitment(ServiceCommitment, BillingBasePeriod, BillingBasePeriod, BaseAmount); - Result := ServiceCommitment.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate - 1); + MockServiceCommitment(SubscriptionLine, BillingBasePeriod, BillingBasePeriod, BaseAmount); + Result := SubscriptionLine.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate - 1); + // [THEN] Result equals the monthly base amount Assert.AreEqual(Result, BaseAmount, 'Amount was not calculated properly'); end; @@ -1110,14 +1372,19 @@ codeunit 148153 "Usage Based Billing Test" Result: Decimal; NoOfDaysInMonth1: Integer; begin + // [SCENARIO] Prorated amount for monthly price with daily usage data is calculated by dividing the monthly price by days in month + // [GIVEN] A subscription line with monthly billing and a single day charge period Initialize(); BaseAmount := 100; Evaluate(BillingBasePeriod, '1M'); ChargeStartDate := CalcDate('<-CY>', WorkDate()); ChargeEndDate := ChargeStartDate; - MockServiceCommitment(ServiceCommitment, BillingBasePeriod, BillingBasePeriod, BaseAmount); - Result := ServiceCommitment.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + MockServiceCommitment(SubscriptionLine, BillingBasePeriod, BillingBasePeriod, BaseAmount); + // [WHEN] Calculating unit price for a single day + Result := SubscriptionLine.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + + // [THEN] Result equals the monthly amount divided by the number of days in the month NoOfDaysInMonth1 := CalcDate('', ChargeEndDate) - ChargeStartDate + 1; ExpectedResult := BaseAmount * 1 / NoOfDaysInMonth1; @@ -1134,26 +1401,37 @@ codeunit 148153 "Usage Based Billing Test" ExpectedResult: Decimal; Result: Decimal; begin + // [SCENARIO] Prorated amount for yearly prices returns the full amount for a full year and prorates for partial periods + // [GIVEN] A subscription line with yearly billing (12M and 1Y formats) Initialize(); BaseAmount := 100; Evaluate(BillingBasePeriod, '12M'); ChargeStartDate := CalcDate('<-CY>', WorkDate()); ChargeEndDate := CalcDate('', ChargeStartDate); - MockServiceCommitment(ServiceCommitment, BillingBasePeriod, BillingBasePeriod, BaseAmount); - Result := ServiceCommitment.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + MockServiceCommitment(SubscriptionLine, BillingBasePeriod, BillingBasePeriod, BaseAmount); + + // [WHEN] Calculating unit price for a full year with 12M period + Result := SubscriptionLine.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + // [THEN] Result equals the yearly base amount ExpectedResult := BaseAmount; Assert.AreEqual(ExpectedResult, Result, 'Amount was not calculated properly'); + // [WHEN] Calculating unit price for a full year with 1Y period Evaluate(BillingBasePeriod, '1Y'); - MockServiceCommitment(ServiceCommitment, BillingBasePeriod, BillingBasePeriod, BaseAmount); - Result := ServiceCommitment.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + MockServiceCommitment(SubscriptionLine, BillingBasePeriod, BillingBasePeriod, BaseAmount); + Result := SubscriptionLine.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + + // [THEN] Result equals the yearly base amount Assert.AreEqual(ExpectedResult, Result, 'Amount was not calculated properly'); + // [WHEN] Calculating unit price for a single day with amount set to number of days in year BaseAmount := ChargeEndDate - ChargeStartDate + 1; // Set the Amount to number of days ChargeEndDate := ChargeStartDate; - MockServiceCommitment(ServiceCommitment, BillingBasePeriod, BillingBasePeriod, BaseAmount); - Result := ServiceCommitment.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + MockServiceCommitment(SubscriptionLine, BillingBasePeriod, BillingBasePeriod, BaseAmount); + Result := SubscriptionLine.UnitPriceForPeriod(ChargeStartDate, ChargeEndDate); + + // [THEN] Result equals 1 (daily price) Assert.AreEqual(1, Result, 'Amount was not calculated properly'); end; @@ -1166,15 +1444,15 @@ codeunit 148153 "Usage Based Billing Test" // [GIVEN] Setup simple Customer Subscription Contract with Subscription Line marked as Usage based billing // Try to create a billing proposal with Billing To Date (crucial) - ContractTestLibrary.CreateMultipleServiceObjectsWithItemSetup(Customer, ServiceObject, Item, 2); + ContractTestLibrary.CreateMultipleServiceObjectsWithItemSetup(Customer, SubscriptionHeader, Item, 2); ContractTestLibrary.UpdateItemUnitCostAndPrice(Item, LibraryRandom.RandDec(1000, 2), LibraryRandom.RandDec(1000, 2), false); - ContractTestLibrary.CreateServiceCommitmentTemplateSetup(ServiceCommitmentTemplate, '<12M>', Enum::"Invoicing Via"::Contract); - ContractTestLibrary.CreateServiceCommPackageAndAssignItemToServiceCommitmentSetup(ServiceCommitmentTemplate.Code, ServiceCommitmentPackage, ServiceCommPackageLine, Item, '<12M>'); - ServiceCommPackageLine."Usage Based Billing" := true; - ServiceCommPackageLine.Modify(false); - ContractTestLibrary.InsertServiceCommitmentFromServiceCommPackageSetup(ServiceCommitmentPackage, ServiceObject); - ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); + ContractTestLibrary.CreateServiceCommitmentTemplateSetup(SubPackageLineTemplate, '<12M>', Enum::"Invoicing Via"::Contract); + ContractTestLibrary.CreateServiceCommPackageAndAssignItemToServiceCommitmentSetup(SubPackageLineTemplate.Code, SubscriptionPackage, SubscriptionPackageLine, Item, '<12M>'); + SubscriptionPackageLine."Usage Based Billing" := true; + SubscriptionPackageLine.Modify(false); + ContractTestLibrary.InsertServiceCommitmentFromServiceCommPackageSetup(SubscriptionPackage, SubscriptionHeader); + ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, SubscriptionHeader, Customer."No."); CustomerContract.SetRange("No.", CustomerContract."No."); CreateRecurringBillingTemplateSetupForCustomerContract('<2M-CM>', '<8M+CM>', CustomerContract.GetView()); @@ -1186,6 +1464,167 @@ codeunit 148153 "Usage Based Billing Test" Assert.AreEqual(true, BillingLine.IsEmpty, 'No Billing Line should be created for Usage Based Service Commitments without usage data'); end; + [Test] + [HandlerFunctions('MessageHandler,CreateCustomerBillingDocumentPageHandler')] + procedure TestUsageDataBillingInvoiceQuantityWithZeroQuantityEntry() + var + UsageDataGenericImport: Record "Usage Data Generic Import"; + NonZeroQuantity: Decimal; + CustomerId: Text[80]; + SubscriptionId: Text[80]; + begin + // [SCENARIO] When usage data billing has entries with both non-zero and zero quantities (paused subscription), + // the sales invoice line should use the last non-zero quantity + Initialize(); + CreateSubscriptionItemWithPrices(LibraryRandom.RandDec(100, 2), LibraryRandom.RandDec(100, 2)); + + // [GIVEN] Setup service data with Usage Quantity pricing + SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Usage Quantity", "Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, + '1M', '1M', '1Y', "Service Partner"::Customer, 100, Item."No."); + + // [GIVEN] Setup usage data for processing with non-zero quantity for first period + NonZeroQuantity := LibraryRandom.RandIntInRange(1, 10); + CustomerId := CopyStr(LibraryRandom.RandText(80), 1, 80); + SubscriptionId := CopyStr(LibraryRandom.RandText(80), 1, 80); + UsageBasedBTestLibrary.CreateUsageDataSupplier(UsageDataSupplier, Enum::"Usage Data Supplier Type"::Generic, true, Enum::"Vendor Invoice Per"::Import); + UsageBasedBTestLibrary.CreateGenericImportSettings(GenericImportSettings, UsageDataSupplier."No.", true, true); + UsageBasedBTestLibrary.CreateUsageDataImport(UsageDataImport, UsageDataSupplier."No."); + RRef.GetTable(UsageDataGenericImport); + UsageDataBlob.InsertFromUsageDataImport(UsageDataImport); + UsageBasedBTestLibrary.CreateUsageDataCSVFileBasedOnRecordAndImportToUsageDataBlob( + UsageDataBlob, RRef, CustomerId, SubscriptionId, + SubscriptionHeader."No.", SubscriptionLine."Entry No.", + WorkDate(), CalcDate('', WorkDate()), WorkDate(), CalcDate('', WorkDate()), NonZeroQuantity); + + // [GIVEN] Create a second CSV blob file with quantity 0 for a second period (paused period) + UsageDataBlob.InsertFromUsageDataImport(UsageDataImport); + UsageBasedBTestLibrary.CreateUsageDataCSVFileBasedOnRecordAndImportToUsageDataBlob( + UsageDataBlob, RRef, CustomerId, SubscriptionId, + SubscriptionHeader."No.", SubscriptionLine."Entry No.", + CalcDate('', WorkDate()), CalcDate('', WorkDate()), + CalcDate('', WorkDate()), CalcDate('', WorkDate()), 0); + + // [WHEN] Import and process CSV files into usage data generic import records + SetupDataExchangeDefinition(); + UsageBasedBTestLibrary.ConnectDataExchDefinitionToUsageDataGenericSettings(DataExchDef.Code, GenericImportSettings); + ProcessUsageDataImport(Enum::"Processing Step"::"Create Imported Lines"); + ProcessUsageDataImport(Enum::"Processing Step"::"Process Imported Lines"); + + // [WHEN] Link imported usage data to service commitments via supplier references + UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); + UsageDataGenericImport.FindFirst(); + repeat + PrepareServiceCommitmentAndUsageDataGenericImportForUsageBilling(UsageDataGenericImport, "Usage Based Pricing"::"Usage Quantity"); + until UsageDataGenericImport.Next() = 0; + Codeunit.Run(Codeunit::"Import And Process Usage Data", UsageDataImport); + + // [WHEN] Create usage data billing records and generate contract invoices + UsageDataImport.SetRecFilter(); + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Create Usage Data Billing"); + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + PostDocument := false; + UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); + + // [THEN] Sales line should have the non-zero quantity from the first usage data billing entry + BillingLine.Reset(); + BillingLine.SetFilter("Document No.", '<>%1', ''); + BillingLine.FindFirst(); + SalesHeader.Get(Enum::"Sales Document Type"::Invoice, BillingLine."Document No."); + SalesLine.SetRange("Document Type", SalesHeader."Document Type"); + SalesLine.SetRange("Document No.", SalesHeader."No."); + SalesLine.SetRange(Type, "Sales Line Type"::Item); + SalesLine.FindFirst(); + Assert.AreEqual(NonZeroQuantity, SalesLine.Quantity, 'Sales Line quantity should use the last non-zero usage data billing quantity.'); + end; + + [Test] + [HandlerFunctions('MessageHandler,CreateCustomerBillingDocumentPageHandler')] + procedure TestUsageDataImportWithMultipleUsageDataGenericImports() + var + UsageDataBilling: Record "Usage Data Billing"; + UsageDataGenericImport: Record "Usage Data Generic Import"; + BillingPeriodStartDate: Date; + SubscriptionStartDate: Date; + SubscriptionID: Text; + ExpectedAmount: Decimal; + begin + // [SCENARIO] Verify that usage data import with multiple usage data generic imports creates correct billing and invoices + + // [GIVEN] Initialize the contracts and usage-based billing applications + Initialize(); + ContractTestLibrary.InitContractsApp(); + + // [GIVEN] Create a Subscription Item + CreateSubscriptionItemWithPrices(LibraryRandom.RandDec(1000, 2), LibraryRandom.RandDec(1000, 2)); + + // [GIVEN] Setup Subscription with Subscription Lines and usage quantity + SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Usage Quantity", Enum::"Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, + '1M', '1M', '1M', "Service Partner"::Customer, 100, Item."No."); + + // [WHEN] Create and process simple usage data + UsageBasedBTestLibrary.CreateUsageDataSupplier(UsageDataSupplier, Enum::"Usage Data Supplier Type"::Generic, false, Enum::"Vendor Invoice Per"::Import); + UsageBasedBTestLibrary.CreateGenericImportSettings(GenericImportSettings, UsageDataSupplier."No.", true, true); + UsageBasedBTestLibrary.CreateUsageDataImport(UsageDataImport, UsageDataSupplier."No."); + BillingPeriodStartDate := CalcDate('<-CM>', WorkDate()); + SubscriptionStartDate := CalcDate('<-CM>', WorkDate()); + SubscriptionID := LibraryRandom.RandText(80); + for i := 1 to LibraryRandom.RandInt(10) do begin + UsageBasedBTestLibrary.CreateSimpleUsageDataGenericImport(UsageDataGenericImport, UsageDataImport."Entry No.", SubscriptionHeader."No.", Customer."No.", Item."Unit Cost", + BillingPeriodStartDate, CalcDate('', BillingPeriodStartDate), SubscriptionStartDate, CalcDate('', SubscriptionStartDate), LibraryRandom.RandInt(10)); + + UsageDataGenericImport."Supp. Subscription ID" := CopyStr(SubscriptionID, 1, MaxStrLen(UsageDataGenericImport."Supp. Subscription ID")); + UsageDataGenericImport.Modify(); + BillingPeriodStartDate := CalcDate('<1M>', BillingPeriodStartDate); + SubscriptionStartDate := CalcDate('<1M>', SubscriptionStartDate); + end; + ProcessUsageDataImport(Enum::"Processing Step"::"Process Imported Lines"); + + // [WHEN] Prepare Subscription Line and usage data generic import for usage billing + UsageDataGenericImport.Reset(); + UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); + UsageDataGenericImport.FindFirst(); + repeat + PrepareServiceCommitmentAndUsageDataGenericImportForUsageBilling(UsageDataGenericImport, Enum::"Usage Based Pricing"::"Usage Quantity", '1M', '1M', Calcdate('<-CM>', WorkDate())); + until UsageDataGenericImport.Next() = 0; + Codeunit.Run(Codeunit::"Import And Process Usage Data", UsageDataImport); + + // [WHEN] Process usage data import to create and process usage data billing + UsageDataImport.SetRecFilter(); + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Create Usage Data Billing"); + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + + // [WHEN] Create contract invoice from usage data + UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); + + // [THEN] Verify that Line Amount in the Invoice equals the sum of all Usage Billing Data Amounts + UsageDataBilling.Reset(); + UsageDataBilling.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); + UsageDataBilling.SetRange(Partner, "Service Partner"::Customer); + UsageDataBilling.CalcSums(Amount); + ExpectedAmount := UsageDataBilling.Amount; + UsageDataBilling.FindLast(); + + SalesLine.SetRange("Document Type", "Sales Document Type"::Invoice); + SalesLine.SetRange("Document No.", UsageDataBilling."Document No."); + SalesLine.SetRange("Line No.", UsageDataBilling."Document Line No."); + SalesLine.FindFirst(); + Assert.AreEqual(ExpectedAmount, SalesLine.Amount, 'Line Amount in the Invoice should be equal to the sum of all Usage Billing Data Amounts'); + Assert.AreEqual(UsageDataBilling.Quantity, SalesLine.Quantity, 'Quantity in the Invoice should be equal to the Quantity of Last Usage Billing Data'); + + // [THEN] Verify that each Billing line corresponds to each Usage Data Billing + BillingLine.Reset(); + BillingLine.SetRange("Document No.", UsageDataBilling."Document No."); + BillingLine.FindFirst(); + repeat + UsageDataBilling.SetRange("Document No.", BillingLine."Document No."); + UsageDataBilling.SetRange("Charge Start Date", BillingLine."Billing from"); + UsageDataBilling.SetRange("Charge End Date", BillingLine."Billing to"); + UsageDataBilling.FindFirst(); + Assert.AreEqual(BillingLine.Amount, UsageDataBilling.Amount, 'Billing Line Amount should be equal to Usage Data Billing Amount'); + Assert.AreEqual(BillingLine."Service Object Quantity", UsageDataBilling.Quantity, 'Billing Line Quantity should be equal to Usage Data Billing Quantity'); + until BillingLine.Next() = 0; + end; + [Test] [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateVendorBillingDocumentPageHandler,MessageHandler')] procedure TestUpdateUsageBasedAfterDeletePurchaseCreditMemo() @@ -1226,6 +1665,8 @@ codeunit 148153 "Usage Based Billing Test" var UsageDataBilling: Record "Usage Data Billing"; begin + // [SCENARIO] Deleting a purchase invoice resets usage data billing to no document + // [GIVEN] Usage data billing with a created vendor contract invoice Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); @@ -1234,7 +1675,11 @@ codeunit 148153 "Usage Based Billing Test" FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No."); UsageDataBilling.MarkPurchaseHeaderFromUsageDataBilling(UsageDataBilling, PurchaseHeader); PurchaseHeader.FindSet(); + + // [WHEN] Deleting the purchase invoice PurchaseHeader.Delete(true); + + // [THEN] Usage data billing is reset to no document TestIfRelatedUsageDataBillingIsUpdated("Service Partner"::Vendor, Enum::"Usage Based Billing Doc. Type"::None, '', false, 0); end; @@ -1244,6 +1689,8 @@ codeunit 148153 "Usage Based Billing Test" var PurchaseInvoiceHeader: Record "Purch. Inv. Header"; begin + // [SCENARIO] Deleting a posted purchase invoice header resets usage data billing + // [GIVEN] A posted purchase invoice from usage data billing Initialize(); PurchaseInvoiceHeader.DeleteAll(false); @@ -1261,7 +1708,10 @@ codeunit 148153 "Usage Based Billing Test" PurchSetup."Allow Document Deletion Before" := CalcDate('<1D>', WorkDate()); PurchSetup.Modify(false); + // [WHEN] Deleting the posted purchase invoice header PurchaseInvoiceHeader.Delete(true); + + // [THEN] Usage data billing is reset to no document TestIfRelatedUsageDataBillingIsUpdated("Service Partner"::Vendor, Enum::"Usage Based Billing Doc. Type"::None, '', false, 0); end; @@ -1305,12 +1755,17 @@ codeunit 148153 "Usage Based Billing Test" [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] procedure TestUpdateUsageBasedAfterDeleteSalesInvoice() begin + // [SCENARIO] Deleting a sales invoice resets usage data billing to no document + // [GIVEN] Usage data billing with created customer contract invoices Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); PostDocument := false; UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); + + // [WHEN] Deleting each sales invoice + // [THEN] Usage data billing is reset to no document BillingLine.FindSet(); repeat SalesHeader.Get(Enum::"Sales Document Type"::Invoice, BillingLine."Document No."); @@ -1323,6 +1778,8 @@ codeunit 148153 "Usage Based Billing Test" [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] procedure TestUpdateUsageBasedAfterDeleteSalesInvoiceHeader() begin + // [SCENARIO] Deleting a posted sales invoice header resets usage data billing + // [GIVEN] A posted sales invoice from usage data billing Initialize(); SalesInvoiceHeader.DeleteAll(false); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); @@ -1337,32 +1794,12 @@ codeunit 148153 "Usage Based Billing Test" SalesSetup.Get(); SalesSetup."Allow Document Deletion Before" := CalcDate('<1D>', WorkDate()); SalesSetup.Modify(false); - SalesInvoiceHeader.Delete(true); - TestIfRelatedUsageDataBillingIsUpdated("Service Partner"::Customer, Enum::"Usage Based Billing Doc. Type"::None, '', false, 0); - end; - - [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] - procedure TestUpdateUsageBasedAfterInsertCreditMemo() - var - UsageDataBilling: Record "Usage Data Billing"; - begin - Initialize(); - CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); - PostDocument := true; - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); - UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); - FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer, UsageDataBilling."Document Type"::"Posted Invoice"); - UsageDataBilling.FindFirst(); - SalesInvoiceHeader.Get(UsageDataBilling."Document No."); - CorrectPostedSalesInvoice.CreateCreditMemoCopyDocument(SalesInvoiceHeader, SalesCrMemoHeader); - FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer); - Assert.RecordCount(UsageDataBilling, 2); // Expect additional usage data billing for credit memo + // [WHEN] Deleting the posted sales invoice header + SalesInvoiceHeader.Delete(true); - UsageDataBilling.FilterOnDocumentTypeAndDocumentNo(Enum::"Service Partner"::Customer, Enum::"Usage Based Billing Doc. Type"::"Credit Memo", SalesCrMemoHeader."No."); - Assert.RecordIsNotEmpty(UsageDataBilling); + // [THEN] Usage data billing is reset to no document + TestIfRelatedUsageDataBillingIsUpdated("Service Partner"::Customer, Enum::"Usage Based Billing Doc. Type"::None, '', false, 0); end; [Test] @@ -1372,6 +1809,8 @@ codeunit 148153 "Usage Based Billing Test" UsageDataBilling: Record "Usage Data Billing"; PurchaseInvoiceHeader: Record "Purch. Inv. Header"; begin + // [SCENARIO] Creating a purchase credit memo from a posted invoice creates additional usage data billing records + // [GIVEN] A posted purchase invoice from usage data billing Initialize(); PurchaseInvoiceHeader.DeleteAll(false); @@ -1381,10 +1820,13 @@ codeunit 148153 "Usage Based Billing Test" UsageDataImport.CollectVendorContractsAndCreateInvoices(UsageDataImport); PostPurchaseDocuments(); PurchaseInvoiceHeader.FindLast(); + + // [WHEN] Creating a credit memo from the posted purchase invoice CorrectPostedPurchaseInvoice.CreateCreditMemoCopyDocument(PurchaseInvoiceHeader, PurchaseHeader); + // [THEN] Additional usage data billing record is created for the credit memo FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Vendor); - Assert.RecordCount(UsageDataBilling, 2); // Expect additional usage data billing for credit memo and one without document type and document no + Assert.RecordCount(UsageDataBilling, 2); UsageDataBilling.FilterOnDocumentTypeAndDocumentNo(Enum::"Service Partner"::Vendor, Enum::"Usage Based Billing Doc. Type"::"Credit Memo", PurchaseHeader."No."); Assert.RecordIsNotEmpty(UsageDataBilling); @@ -1396,6 +1838,8 @@ codeunit 148153 "Usage Based Billing Test" var UsageDataBilling: Record "Usage Data Billing"; begin + // [SCENARIO] Creating a sales credit memo from a posted invoice creates additional usage data billing records + // [GIVEN] A posted sales invoice from usage data billing Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); PostDocument := true; @@ -1406,41 +1850,15 @@ codeunit 148153 "Usage Based Billing Test" FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer, UsageDataBilling."Document Type"::"Posted Invoice"); UsageDataBilling.FindFirst(); SalesInvoiceHeader.Get(UsageDataBilling."Document No."); - CorrectPostedSalesInvoice.CreateCreditMemoCopyDocument(SalesInvoiceHeader, SalesCrMemoHeader); - - FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer); - Assert.RecordCount(UsageDataBilling, 2); // Expect additional usage data billing for credit memo and one without document type and document no - - UsageDataBilling.FilterOnDocumentTypeAndDocumentNo(Enum::"Service Partner"::Customer, Enum::"Usage Based Billing Doc. Type"::"Credit Memo", SalesCrMemoHeader."No."); - Assert.RecordIsNotEmpty(UsageDataBilling); - end; - - [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] - procedure TestUpdateUsageBasedAfterPostCreditMemo() - var - UsageDataBilling: Record "Usage Data Billing"; - begin - Initialize(); - CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); - PostDocument := true; - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); - UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); - FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer, UsageDataBilling."Document Type"::"Posted Invoice"); - UsageDataBilling.FindFirst(); - SalesInvoiceHeader.Get(UsageDataBilling."Document No."); + // [WHEN] Creating a credit memo from the posted sales invoice CorrectPostedSalesInvoice.CreateCreditMemoCopyDocument(SalesInvoiceHeader, SalesCrMemoHeader); - CorrectedDocumentNo := LibrarySales.PostSalesDocument(SalesCrMemoHeader, true, true); + // [THEN] Additional usage data billing record is created for the credit memo FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer); - Assert.RecordCount(UsageDataBilling, 3); // Expect additional usage data billing for credit memo and one without document type and document no - - UsageDataBilling.FilterOnDocumentTypeAndDocumentNo(Enum::"Service Partner"::Customer, Enum::"Usage Based Billing Doc. Type"::"Posted Credit Memo", CorrectedDocumentNo); - Assert.RecordIsNotEmpty(UsageDataBilling); + Assert.RecordCount(UsageDataBilling, 2); - UsageDataBilling.FilterOnDocumentTypeAndDocumentNo(Enum::"Service Partner"::Customer, Enum::"Usage Based Billing Doc. Type"::None, ''); + UsageDataBilling.FilterOnDocumentTypeAndDocumentNo(Enum::"Service Partner"::Customer, Enum::"Usage Based Billing Doc. Type"::"Credit Memo", SalesCrMemoHeader."No."); Assert.RecordIsNotEmpty(UsageDataBilling); end; @@ -1451,6 +1869,8 @@ codeunit 148153 "Usage Based Billing Test" UsageDataBilling: Record "Usage Data Billing"; PurchaseInvoiceHeader: Record "Purch. Inv. Header"; begin + // [SCENARIO] Posting a purchase credit memo creates additional usage data billing records for the posted credit memo and a reset entry + // [GIVEN] A posted purchase invoice and a credit memo created from it Initialize(); PurchaseInvoiceHeader.DeleteAll(false); @@ -1463,10 +1883,13 @@ codeunit 148153 "Usage Based Billing Test" CorrectPostedPurchaseInvoice.CreateCreditMemoCopyDocument(PurchaseInvoiceHeader, PurchaseHeader); PurchaseHeader."Vendor Cr. Memo No." := LibraryUtility.GenerateGUID(); PurchaseHeader.Modify(false); + + // [WHEN] Posting the purchase credit memo CorrectedDocumentNo := LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, true); + // [THEN] Three usage data billing records exist: posted invoice, posted credit memo, and reset entry FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Vendor); - Assert.RecordCount(UsageDataBilling, 3); // Expect additional usage data billing for credit memo and one without document type and document no + Assert.RecordCount(UsageDataBilling, 3); UsageDataBilling.FilterOnDocumentTypeAndDocumentNo(Enum::"Service Partner"::Vendor, Enum::"Usage Based Billing Doc. Type"::"Posted Credit Memo", CorrectedDocumentNo); Assert.RecordIsNotEmpty(UsageDataBilling); @@ -1481,6 +1904,8 @@ codeunit 148153 "Usage Based Billing Test" var PurchaseInvoiceHeader: Record "Purch. Inv. Header"; begin + // [SCENARIO] Posting a purchase invoice updates usage data billing to reference the posted document + // [GIVEN] Usage data billing with a created vendor contract invoice Initialize(); PurchaseInvoiceHeader.DeleteAll(false); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); @@ -1488,7 +1913,10 @@ codeunit 148153 "Usage Based Billing Test" UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); UsageDataImport.CollectVendorContractsAndCreateInvoices(UsageDataImport); + // [WHEN] Posting the purchase invoice PostPurchaseDocuments(); + + // [THEN] Usage data billing references the posted purchase invoice PurchaseInvoiceHeader.FindLast(); TestIfRelatedUsageDataBillingIsUpdated("Service Partner"::Vendor, Enum::"Usage Based Billing Doc. Type"::"Posted Invoice", PurchaseInvoiceHeader."No.", true, 0); end; @@ -1499,6 +1927,8 @@ codeunit 148153 "Usage Based Billing Test" var UsageDataBilling: Record "Usage Data Billing"; begin + // [SCENARIO] Posting a sales credit memo creates additional usage data billing records for the posted credit memo and a reset entry + // [GIVEN] A posted sales invoice from usage data billing Initialize(); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); PostDocument := true; @@ -1510,10 +1940,13 @@ codeunit 148153 "Usage Based Billing Test" SalesInvoiceHeader.Get(UsageDataBilling."Document No."); CorrectPostedSalesInvoice.CreateCreditMemoCopyDocument(SalesInvoiceHeader, SalesCrMemoHeader); + + // [WHEN] Posting the sales credit memo CorrectedDocumentNo := LibrarySales.PostSalesDocument(SalesCrMemoHeader, true, true); + // [THEN] Three usage data billing records exist: posted invoice, posted credit memo, and reset entry FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer); - Assert.RecordCount(UsageDataBilling, 3); // Expect additional usage data billing for credit memo and one without document type and document no + Assert.RecordCount(UsageDataBilling, 3); UsageDataBilling.FilterOnDocumentTypeAndDocumentNo(Enum::"Service Partner"::Customer, Enum::"Usage Based Billing Doc. Type"::"Posted Credit Memo", CorrectedDocumentNo); Assert.RecordIsNotEmpty(UsageDataBilling); @@ -1526,6 +1959,8 @@ codeunit 148153 "Usage Based Billing Test" [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] procedure TestUpdateUsageBasedAfterPostSalesHeader() begin + // [SCENARIO] Posting a sales invoice updates usage data billing to reference the posted document + // [GIVEN] Usage data billing with a created customer contract invoice Initialize(); SalesInvoiceHeader.DeleteAll(false); CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); @@ -1533,63 +1968,29 @@ codeunit 148153 "Usage Based Billing Test" UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); + + // [THEN] Usage data billing references the posted sales invoice SalesInvoiceHeader.FindLast(); TestIfRelatedUsageDataBillingIsUpdated("Service Partner"::Customer, "Usage Based Billing Doc. Type"::"Posted Invoice", SalesInvoiceHeader."No.", true, 0); end; [Test] [HandlerFunctions('MessageHandler,CreateCustomerBillingDocumentPageHandler')] - procedure TestYearlyServiceCommitmentWithDailyUsageData() - var - UsageDataBilling: Record "Usage Data Billing"; + procedure TestYearlySubscriptionWithDailyUsageData() begin - Initialize(); - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - //In order to avoid rounding issues, set the Unit price to number of days in the period; This way Daily price will always be 1 - Item."Unit Price" := CalcDate('<1Y>', WorkDate()) - WorkDate(); - Item."Unit Cost" := 1; - Item.Modify(false); - - SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Usage Quantity", "Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, - '1Y', '1Y', '1Y', "Service Partner"::Customer, 100, Item."No."); - - ProcessUsageDataWithSimpleGenericImport(WorkDate(), WorkDate(), WorkDate(), WorkDate(), 1); - UsageDataBilling.Reset(); - UsageDataBilling.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); - UsageDataBilling.SetRange(Partner, "Service Partner"::Customer); - if UsageDataBilling.FindSet() then - repeat - UsageDataBilling.TestField("Unit Price", UsageDataBilling."Charged Period (Days)"); - until UsageDataBilling.Next() = 0; - CreateContractInvoicesAndTestProcessedUsageData(); + // [SCENARIO] Yearly subscription line with daily usage data creates correct billing + // Unit price set to number of days in the period to avoid rounding issues (Daily price = 1) + TestSubscriptionWithUsageData('1Y', WorkDate(), WorkDate(), CalcDate('<1Y>', WorkDate()) - WorkDate(), true); end; [Test] [HandlerFunctions('MessageHandler,CreateCustomerBillingDocumentPageHandler')] - procedure TestYearlyServiceCommitmentWithMonthlyUsageData() - var - UsageDataBilling: Record "Usage Data Billing"; + procedure TestYearlySubscriptionWithMonthlyUsageData() begin - Initialize(); - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - //In order to avoid rounding issues, set the Unit price to number of days in the period; This way Daily price will always be 1 - Item."Unit Price" := CalcDate('<1Y>', WorkDate()) - WorkDate(); - Item."Unit Cost" := 1; - Item.Modify(false); - - SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Usage Quantity", "Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, - '1Y', '1Y', '1Y', "Service Partner"::Customer, 100, Item."No."); - - ProcessUsageDataWithSimpleGenericImport(WorkDate(), CalcDate('', WorkDate()), WorkDate(), CalcDate('', WorkDate()), 1); - UsageDataBilling.Reset(); - UsageDataBilling.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); - UsageDataBilling.SetRange(Partner, "Service Partner"::Customer); - if UsageDataBilling.FindSet() then - repeat - UsageDataBilling.TestField("Unit Price", UsageDataBilling."Charged Period (Days)"); - until UsageDataBilling.Next() = 0; - CreateContractInvoicesAndTestProcessedUsageData(); - end; + // [SCENARIO] Yearly subscription line with monthly usage data creates correct billing + // Unit price set to number of days in the period to avoid rounding issues (Daily price = 1) + TestSubscriptionWithUsageData('1Y', CalcDate('', WorkDate()), CalcDate('', WorkDate()), CalcDate('<1Y>', WorkDate()) - WorkDate(), true); + end; [Test] [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] @@ -1635,15 +2036,15 @@ codeunit 148153 "Usage Based Billing Test" // [WHEN]: insert an subscription reference is set for Subscription Line UsageDataSupplierReference.FindSupplierReference(UsageDataImport."Supplier No.", UsageDataGenericImport."Supp. Subscription ID", Enum::"Usage Data Reference Type"::Subscription); - ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); - ServiceCommitment.SetRange(Partner, Enum::"Service Partner"::Vendor); - ServiceCommitment.FindFirst(); - ServiceCommitment."Supplier Reference Entry No." := UsageDataSupplierReference."Entry No."; - ServiceCommitment.Modify(false); + SubscriptionLine.SetRange("Subscription Header No.", SubscriptionHeader."No."); + SubscriptionLine.SetRange(Partner, Enum::"Service Partner"::Vendor); + SubscriptionLine.FindFirst(); + SubscriptionLine."Supplier Reference Entry No." := UsageDataSupplierReference."Entry No."; + SubscriptionLine.Modify(false); ProcessUsageDataImport(Enum::"Processing Step"::"Process Imported Lines"); // [THEN]: Test if Subscription Availability is set to "Connected" - ValidateUsageDataGenericImportAvailability(UsageDataImport."Entry No.", "Service Object Availability"::Connected, ServiceObject."No."); + ValidateUsageDataGenericImportAvailability(UsageDataImport."Entry No.", "Service Object Availability"::Connected, SubscriptionHeader."No."); end; [Test] @@ -1722,279 +2123,6 @@ codeunit 148153 "Usage Based Billing Test" Assert.IsFalse(LibraryVariableStorage.DequeueBoolean(), 'Usage Data Billing is found, but should not be'); end; - [Test] - [HandlerFunctions('MessageHandler,CreateCustomerBillingDocumentPageHandler')] - procedure TestUsageDataImportWithMultipleUsageDataGenericImports() - var - UsageDataBilling: Record "Usage Data Billing"; - UsageDataGenericImport: Record "Usage Data Generic Import"; - BillingPeriodStartDate: Date; - SubscriptionStartDate: Date; - SubscriptionID: Text; - ExpectedAmount: Decimal; - begin - // [SCENARIO] Verify that usage data import with multiple usage data generic imports creates correct billing and invoices - - // [GIVEN] Initialize the contracts and usage-based billing applications - Initialize(); - ContractTestLibrary.InitContractsApp(); - - // [GIVEN] Create a Subscription Item - ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - Item."Unit Price" := LibraryRandom.RandDec(1000, 2); - Item."Unit Cost" := LibraryRandom.RandDec(1000, 2); - Item.Modify(false); - - // [GIVEN] Setup Subscription with Subscription Lines and usage quantity - SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Usage Quantity", Enum::"Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, - '1M', '1M', '1M', "Service Partner"::Customer, 100, Item."No."); - - // [WHEN] Create and process simple usage data - UsageBasedBTestLibrary.CreateUsageDataSupplier(UsageDataSupplier, Enum::"Usage Data Supplier Type"::Generic, false, Enum::"Vendor Invoice Per"::Import); - UsageBasedBTestLibrary.CreateGenericImportSettings(GenericImportSettings, UsageDataSupplier."No.", true, true); - UsageBasedBTestLibrary.CreateUsageDataImport(UsageDataImport, UsageDataSupplier."No."); - BillingPeriodStartDate := CalcDate('<-CM>', WorkDate()); - SubscriptionStartDate := CalcDate('<-CM>', WorkDate()); - SubscriptionID := LibraryRandom.RandText(80); - for i := 1 to LibraryRandom.RandInt(10) do begin - UsageBasedBTestLibrary.CreateSimpleUsageDataGenericImport(UsageDataGenericImport, UsageDataImport."Entry No.", ServiceObject."No.", Customer."No.", Item."Unit Cost", - BillingPeriodStartDate, CalcDate('', BillingPeriodStartDate), SubscriptionStartDate, CalcDate('', SubscriptionStartDate), LibraryRandom.RandInt(10)); - - UsageDataGenericImport."Supp. Subscription ID" := CopyStr(SubscriptionID, 1, MaxStrLen(UsageDataGenericImport."Supp. Subscription ID")); - UsageDataGenericImport.Modify(); - BillingPeriodStartDate := CalcDate('<1M>', BillingPeriodStartDate); - SubscriptionStartDate := CalcDate('<1M>', SubscriptionStartDate); - end; - ProcessUsageDataImport(Enum::"Processing Step"::"Process Imported Lines"); - - // [WHEN] Prepare Subscription Line and usage data generic import for usage billing - UsageDataGenericImport.Reset(); - UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); - UsageDataGenericImport.FindFirst(); - repeat - PrepareServiceCommitmentAndUsageDataGenericImportForUsageBilling(UsageDataGenericImport, Enum::"Usage Based Pricing"::"Usage Quantity", '1M', '1M', Calcdate('<-CM>', WorkDate())); - until UsageDataGenericImport.Next() = 0; - Codeunit.Run(Codeunit::"Import And Process Usage Data", UsageDataImport); - - // [WHEN] Process usage data import to create and process usage data billing - UsageDataImport.SetRecFilter(); - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Create Usage Data Billing"); - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - - // [WHEN] Create contract invoice from usage data - UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); - - // [THEN] Verify that Line Amount in the Invoice equals the sum of all Usage Billing Data Amounts - UsageDataBilling.Reset(); - UsageDataBilling.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); - UsageDataBilling.SetRange(Partner, "Service Partner"::Customer); - UsageDataBilling.CalcSums(Amount); - ExpectedAmount := UsageDataBilling.Amount; - UsageDataBilling.FindLast(); - - SalesLine.SetRange("Document Type", "Sales Document Type"::Invoice); - SalesLine.SetRange("Document No.", UsageDataBilling."Document No."); - SalesLine.SetRange("Line No.", UsageDataBilling."Document Line No."); - SalesLine.FindFirst(); - Assert.AreEqual(ExpectedAmount, SalesLine.Amount, 'Line Amount in the Invoice should be equal to the sum of all Usage Billing Data Amounts'); - Assert.AreEqual(UsageDataBilling.Quantity, SalesLine.Quantity, 'Quantity in the Invoice should be equal to the Quantity of Last Usage Billing Data'); - - // [THEN] Verify that each Billing line corresponds to each Usage Data Billing - BillingLine.Reset(); - BillingLine.SetRange("Document No.", UsageDataBilling."Document No."); - BillingLine.FindFirst(); - repeat - UsageDataBilling.SetRange("Document No.", BillingLine."Document No."); - UsageDataBilling.SetRange("Charge Start Date", BillingLine."Billing from"); - UsageDataBilling.SetRange("Charge End Date", BillingLine."Billing to"); - UsageDataBilling.FindFirst(); - Assert.AreEqual(BillingLine.Amount, UsageDataBilling.Amount, 'Billing Line Amount should be equal to Usage Data Billing Amount'); - Assert.AreEqual(BillingLine."Service Object Quantity", UsageDataBilling.Quantity, 'Billing Line Quantity should be equal to Usage Data Billing Quantity'); - until BillingLine.Next() = 0; - end; - - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] - [Test] - procedure DeleteUsageDataBillingLineWhenRelatedSalesCrMemoLineIsDeleted() - var - UsageDataBilling: Record "Usage Data Billing"; - begin - // [SCENARIO] Creating a corrective credit memo for a contract with two usage-based service commitments, deleting one line, and posting the memo should only create a new usage data billing line for the credited line. - // [GIVEN] A customer contract with two usage-based service commitments, both invoiced - ResetAll(); - CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); - PostDocument := true; - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); - UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); - FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer, UsageDataBilling."Document Type"::"Posted Invoice"); - UsageDataBilling.FindFirst(); - - SalesInvoiceHeader.Get(UsageDataBilling."Document No."); - CorrectPostedSalesInvoice.CreateCreditMemoCopyDocument(SalesInvoiceHeader, SalesCrMemoHeader); - - SalesLine.Reset(); - SalesLine.SetRange("Document Type", "Sales Document Type"::"Credit Memo"); - SalesLine.SetRange("Document No.", SalesCrMemoHeader."No."); - SalesLine.SetRange(Type, "Sales Line Type"::Item); - SalesLine.FindFirst(); - - // Delete the first line (simulate user action) - SalesLine.Delete(true); - - // [THEN] Only the credited line should have a new usage data billing line - // Check usage data billing lines for the contract - UsageDataBilling.Reset(); - UsageDataBilling.SetRange("Document Type", UsageDataBilling."Document Type"::"Credit Memo"); - UsageDataBilling.SetRange("Document No.", SalesCrMemoHeader."No."); - UsageDataBilling.SetRange("Document Line No.", SalesLine."Line No."); - Assert.RecordIsEmpty(UsageDataBilling); - end; - - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateCustomerBillingDocumentPageHandler,MessageHandler')] - [Test] - procedure ResetUsageDataBillingWhenRelatedSalesInvoiceLineIsDeleted() - var - UsageDataBilling: Record "Usage Data Billing"; - begin - // [SCENARIO] When sales invoice with usage data is created if a line is deleted related usage data billing should be reset - // [GIVEN] A customer contract with usage-based service commitments and a sales invoice created from usage data - ResetAll(); - CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); - PostDocument := false; - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); - UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); - FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Customer, UsageDataBilling."Document Type"::"Invoice"); - UsageDataBilling.FindFirst(); - - SalesLine.Reset(); - SalesLine.SetRange("Document Type", "Sales Document Type"::Invoice); - SalesLine.SetRange("Document No.", UsageDataBilling."Document No."); - SalesLine.SetRange(Type, "Sales Line Type"::Item); - SalesLine.FindFirst(); - - // Delete the first line (simulate user action) - SalesLine.Delete(true); - - // [THEN] Check that invoice data is removed from usage data billing - UsageDataBilling.Reset(); - UsageDataBilling.Get(UsageDataBilling."Entry No."); - UsageDataBilling.TestField("Document Type", UsageDataBilling."Document Type"::None); - UsageDataBilling.TestField("Document No.", ''); - UsageDataBilling.TestField("Document Line No.", 0); - UsageDataBilling.TestField("Billing Line Entry No.", 0); - end; - - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateVendorBillingDocumentPageHandler,MessageHandler')] - [Test] - procedure ResetUsageDataBillingWhenRelatedPurchLineIsDeleted() - var - UsageDataBilling: Record "Usage Data Billing"; - begin - // [SCENARIO] When purchase invoice with usage data is created if a line is deleted related usage data billing should be reset - // [GIVEN] A vendor contract with usage-based service commitments and a purchase invoice created from usage data - ResetAll(); - CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); - UsageDataImport.CollectVendorContractsAndCreateInvoices(UsageDataImport); - FilterUsageDataBillingOnUsageDataImport(UsageDataBilling, UsageDataImport."Entry No.", "Service Partner"::Vendor, UsageDataBilling."Document Type"::Invoice); - UsageDataBilling.FindFirst(); - - PurchaseHeader.Get(PurchaseHeader."Document Type"::Invoice, UsageDataBilling."Document No."); - - PurchaseLine.Reset(); - PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type"); - PurchaseLine.SetRange("Document No.", PurchaseHeader."No."); - PurchaseLine.SetRange(Type, "Sales Line Type"::Item); - PurchaseLine.FindFirst(); - - // Delete the first line (simulate user action) - PurchaseLine.Delete(true); - - // [THEN] Check that invoice data is removed from usage data billing - UsageDataBilling.Reset(); - UsageDataBilling.Get(UsageDataBilling."Entry No."); - UsageDataBilling.TestField("Document Type", UsageDataBilling."Document Type"::None); - UsageDataBilling.TestField("Document No.", ''); - UsageDataBilling.TestField("Document Line No.", 0); - UsageDataBilling.TestField("Billing Line Entry No.", 0); - end; - - - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,CreateVendorBillingDocumentPageHandler,MessageHandler')] - [Test] - procedure DeleteUsageDataBillingLineWhenRelatedPurchCrMemoLineIsDeleted() - var - PurchCrMemoHeader: Record "Purchase Header"; - PurchInvHeader: Record "Purch. Inv. Header"; - UsageDataBilling: Record "Usage Data Billing"; - begin - // [SCENARIO] Creating a corrective purchase credit memo for a contract with two usage-based service commitments, deleting one line, and posting the memo should only create a new usage data billing line for the credited line. - // [GIVEN] A vendor contract with two usage-based service commitments, both invoiced - ResetAll(); - CreateUsageDataBilling("Usage Based Pricing"::"Fixed Quantity", LibraryRandom.RandDec(10, 2)); - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); - UsageDataImport.CollectVendorContractsAndCreateInvoices(UsageDataImport); - PostPurchaseDocuments(); - PurchInvHeader.FindLast(); - CorrectPostedPurchaseInvoice.CreateCreditMemoCopyDocument(PurchInvHeader, PurchCrMemoHeader); - - PurchaseLine.Reset(); - PurchaseLine.SetRange("Document Type", PurchCrMemoHeader."Document Type"); - PurchaseLine.SetRange("Document No.", PurchCrMemoHeader."No."); - SalesLine.SetRange(Type, "Sales Line Type"::Item); - PurchaseLine.FindFirst(); - - // Delete the first line (simulate user action) - PurchaseLine.Delete(true); - - // [THEN] Only the credited line should have a new usage data billing line - // Check usage data billing lines for the contract - UsageDataBilling.Reset(); - UsageDataBilling.SetRange("Document Type", UsageDataBilling."Document Type"::"Credit Memo"); - UsageDataBilling.SetRange("Document No.", PurchCrMemoHeader."No."); - UsageDataBilling.SetRange("Document Line No.", PurchaseLine."Line No."); - Assert.RecordIsEmpty(UsageDataBilling); - end; - - [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler,StrMenuHandlerClearBillingProposal')] - procedure TestBillingLineInUsageDataNoWhenBillingProposalIsCreated() - var - UsageDataBilling: Record "Usage Data Billing"; - BillingProposal: Codeunit "Billing Proposal"; - begin - //[SCENARIO] Create recurring billing for simple customer contract; Check if Usage Data Billing Line No. has billing line no - - ResetAll(); - //[GIVEN]: Setup Usage Data Import and process it - CreateUsageDataBilling("Usage Based Pricing"::"Usage Quantity", LibraryRandom.RandDec(10, 2)); - UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); - - //[WHEN]: Create recurring billing proposal for customer contract - CreateBillingProposalForSimpleCustomerContract(); - - //[THEN]: Check if Usage Data Billing Line No. has billing line no - BillingLine.Reset(); - BillingLine.SetRange(Partner, BillingLine.Partner::Customer); - BillingLine.FindFirst(); - UsageDataBilling.Reset(); - UsageDataBilling.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); - UsageDataBilling.SetRange(Partner, UsageDataBilling.Partner::Customer); - UsageDataBilling.FindSet(); - repeat - UsageDataBilling.TestField("Billing Line Entry No.", BillingLine."Entry No."); - until UsageDataBilling.Next() = 0; - - LibraryVariableStorage.Enqueue(2); //StrMenuHandlerClearBillingProposal - BillingProposal.DeleteBillingProposal(BillingTemplate.Code); - UsageDataBilling.Get(UsageDataBilling."Entry No."); - UsageDataBilling.TestField("Billing Line Entry No.", 0); - end; - #endregion Tests #region Procedures @@ -2044,9 +2172,9 @@ codeunit 148153 "Usage Based Billing Test" repeat BillingLine.TestField("Document Type", Enum::"Rec. Billing Document Type"::Invoice); BillingLine.TestField("Document No."); - ServiceCommitment.Get(BillingLine."Subscription Line Entry No."); - ServiceCommitment.TestField("Usage Based Billing"); - ServiceCommitment.TestField("Supplier Reference Entry No."); + SubscriptionLine.Get(BillingLine."Subscription Line Entry No."); + SubscriptionLine.TestField("Usage Based Billing"); + SubscriptionLine.TestField("Supplier Reference Entry No."); PurchaseHeader.Get(Enum::"Purchase Document Type"::Invoice, BillingLine."Document No."); PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type"); @@ -2066,9 +2194,9 @@ codeunit 148153 "Usage Based Billing Test" repeat BillingLine.TestField("Document Type", Enum::"Rec. Billing Document Type"::Invoice); BillingLine.TestField("Document No."); - ServiceCommitment.Get(BillingLine."Subscription Line Entry No."); - ServiceCommitment.TestField("Usage Based Billing"); - ServiceCommitment.TestField("Supplier Reference Entry No."); + SubscriptionLine.Get(BillingLine."Subscription Line Entry No."); + SubscriptionLine.TestField("Usage Based Billing"); + SubscriptionLine.TestField("Supplier Reference Entry No."); SalesHeader.Get(Enum::"Sales Document Type"::Invoice, BillingLine."Document No."); SalesLine.SetRange("Document Type", SalesHeader."Document Type"); @@ -2082,33 +2210,18 @@ codeunit 148153 "Usage Based Billing Test" until BillingLine.Next() = 0; end; - local procedure TestIfInvoicesMatchesUsageData(ServicePartner: Enum "Service Partner"; InvoiceAmount: Decimal; DocumentNo: Code[20]) - var - UsageDataBilling: Record "Usage Data Billing"; - begin - UsageDataBilling.Reset(); - UsageDataBilling.FilterOnDocumentTypeAndDocumentNo(ServicePartner, Enum::"Usage Based Billing Doc. Type"::"Invoice", DocumentNo); - UsageDataBilling.CalcSums(Amount, "Cost Amount"); - case ServicePartner of - ServicePartner::Customer: - Assert.AreEqual(UsageDataBilling.Amount, InvoiceAmount, 'The Sales Invoice lines were not created properly.'); - ServicePartner::Vendor: - Assert.AreEqual(UsageDataBilling."Cost Amount", InvoiceAmount, 'The Purchase Invoice lines were not created properly.'); - end; - end; - local procedure CheckIfServiceCommitmentRemains(UsageDataBilling: Record "Usage Data Billing") begin - ServiceCommitment.Reset(); - ServiceCommitment.SetRange("Subscription Header No.", UsageDataBilling."Subscription Header No."); - ServiceCommitment.SetRange("Entry No.", UsageDataBilling."Subscription Line Entry No."); - ServiceCommitment.FindSet(); + SubscriptionLine.Reset(); + SubscriptionLine.SetRange("Subscription Header No.", UsageDataBilling."Subscription Header No."); + SubscriptionLine.SetRange("Entry No.", UsageDataBilling."Subscription Line Entry No."); + SubscriptionLine.FindSet(); repeat - if ServiceCommitment.Partner = "Service Partner"::Customer then - ServiceCommitment.TestField(Price, Item."Unit Price") + if SubscriptionLine.Partner = "Service Partner"::Customer then + SubscriptionLine.TestField(Price, Item."Unit Price") else - ServiceCommitment.TestField(Price, Item."Unit Cost"); - until ServiceCommitment.Next() = 0; + SubscriptionLine.TestField(Price, Item."Unit Cost"); + until SubscriptionLine.Next() = 0; end; local procedure CheckIfSubscriptionSupplierReferencesAreIsCreated(SuppSubscriptionID: Text[80]) @@ -2172,13 +2285,24 @@ codeunit 148153 "Usage Based Billing Test" Assert.AreEqual(Round(BillingLine.Amount, Currency."Unit-Amount Rounding Precision"), ExpectedInvoiceAmount, 'Billing lines where not created properly'); end; + local procedure CreateContractInvoiceFromUsageDataImportForPricingType(UsageBasedPricing: Enum "Usage Based Pricing") + begin + Initialize(); + CreateUsageDataBilling(UsageBasedPricing, LibraryRandom.RandDec(10, 2)); + PostDocument := false; + UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Process Usage Data Billing"); + UsageDataImport.TestField("Processing Status", "Processing Status"::Ok); + UsageDataImport.CollectCustomerContractsAndCreateInvoices(UsageDataImport); + CheckIfSalesDocumentsHaveBeenCreated(); + end; + local procedure CreateCustomerContractAndAssignServiceCommitments() var - TempServiceCommitment: Record "Subscription Line" temporary; + TempSubscriptionLine: Record "Subscription Line" temporary; begin ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); - ContractTestLibrary.FillTempServiceCommitment(TempServiceCommitment, ServiceObject, CustomerContract); - CustomerContract.CreateCustomerContractLinesFromServiceCommitments(TempServiceCommitment); + ContractTestLibrary.FillTempServiceCommitment(TempSubscriptionLine, SubscriptionHeader, CustomerContract); + CustomerContract.CreateCustomerContractLinesFromServiceCommitments(TempSubscriptionLine); CustomerContractLine.SetRange("Subscription Contract No.", CustomerContract."No."); CustomerContractLine.FindLast(); ContractTestLibrary.SetGeneralPostingSetup(Customer."Gen. Bus. Posting Group", Item."Gen. Prod. Posting Group", false, Enum::"Service Partner"::Customer); @@ -2189,17 +2313,22 @@ codeunit 148153 "Usage Based Billing Test" ContractTestLibrary.CreateRecurringBillingTemplate(BillingTemplate, DateFormula1Txt, DateFormula2Txt, FilterText, Enum::"Service Partner"::Customer); end; - local procedure CreateServiceObjectWithServiceCommitments(CustomerNo: Code[20]; ServiceAndCalculationStartDate: Date) + local procedure CreateSubscriptionItemWithPrices(UnitPrice: Decimal; UnitCost: Decimal) begin ContractTestLibrary.CreateItemWithServiceCommitmentOption(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item"); - Item."Unit Price" := LibraryRandom.RandDec(1000, 2); - Item."Unit Cost" := LibraryRandom.RandDec(1000, 2); + Item."Unit Price" := UnitPrice; + Item."Unit Cost" := UnitCost; Item.Modify(false); + end; + + local procedure CreateServiceObjectWithServiceCommitments(CustomerNo: Code[20]; ServiceAndCalculationStartDate: Date) + begin + CreateSubscriptionItemWithPrices(LibraryRandom.RandDec(1000, 2), LibraryRandom.RandDec(1000, 2)); SetupItemWithMultipleServiceCommitmentPackages(); - ContractTestLibrary.CreateServiceObjectForItem(ServiceObject, Item."No."); - ServiceObject.InsertServiceCommitmentsFromStandardServCommPackages(ServiceAndCalculationStartDate); - ServiceObject."End-User Customer No." := CustomerNo; - ServiceObject.Modify(false); + ContractTestLibrary.CreateServiceObjectForItem(SubscriptionHeader, Item."No."); + SubscriptionHeader.InsertServiceCommitmentsFromStandardServCommPackages(ServiceAndCalculationStartDate); + SubscriptionHeader."End-User Customer No." := CustomerNo; + SubscriptionHeader.Modify(false); end; local procedure CreateUsageDataBilling(UsageBasedPricing: Enum "Usage Based Pricing"; Quantity: Decimal) @@ -2228,17 +2357,17 @@ codeunit 148153 "Usage Based Billing Test" UsageDataImport.ProcessUsageDataImport(UsageDataImport, Enum::"Processing Step"::"Create Usage Data Billing"); end; - local procedure CreateUsageDataBillingDummyDataFromServiceCommitment(var NewUsageDataBilling: Record "Usage Data Billing"; UsageDataImportEntryNo: Integer; SourceServiceCommitment: Record "Subscription Line") + local procedure CreateUsageDataBillingDummyDataFromSubscriptionLine(var NewUsageDataBilling: Record "Usage Data Billing"; UsageDataImportEntryNo: Integer; SourceSubscriptionLine: Record "Subscription Line") begin - SourceServiceCommitment.SetAutoCalcFields(Quantity); + SourceSubscriptionLine.SetAutoCalcFields(Quantity); NewUsageDataBilling."Entry No." := 0; NewUsageDataBilling."Usage Data Import Entry No." := UsageDataImportEntryNo; - NewUsageDataBilling.Partner := SourceServiceCommitment.Partner; - NewUsageDataBilling."Subscription Header No." := SourceServiceCommitment."Subscription Header No."; - NewUsageDataBilling."Subscription Line Entry No." := SourceServiceCommitment."Entry No."; - NewUsageDataBilling."Subscription Contract No." := SourceServiceCommitment."Subscription Contract No."; - NewUsageDataBilling."Subscription Contract Line No." := SourceServiceCommitment."Subscription Contract Line No."; - NewUsageDataBilling.Quantity := SourceServiceCommitment.Quantity; + NewUsageDataBilling.Partner := SourceSubscriptionLine.Partner; + NewUsageDataBilling."Subscription Header No." := SourceSubscriptionLine."Subscription Header No."; + NewUsageDataBilling."Subscription Line Entry No." := SourceSubscriptionLine."Entry No."; + NewUsageDataBilling."Subscription Contract No." := SourceSubscriptionLine."Subscription Contract No."; + NewUsageDataBilling."Subscription Contract Line No." := SourceSubscriptionLine."Subscription Contract Line No."; + NewUsageDataBilling.Quantity := SourceSubscriptionLine.Quantity; NewUsageDataBilling."Charge Start Date" := WorkDate(); NewUsageDataBilling."Charge End Date" := CalcDate('', WorkDate()); NewUsageDataBilling.Insert(true); @@ -2246,12 +2375,12 @@ codeunit 148153 "Usage Based Billing Test" local procedure CreateVendorContractAndAssignServiceCommitments() var - TempServiceCommitment: Record "Subscription Line" temporary; + TempSubscriptionLine: Record "Subscription Line" temporary; begin ContractTestLibrary.CreateVendor(Vendor); ContractTestLibrary.CreateVendorContract(VendorContract, Vendor."No."); - ContractTestLibrary.FillTempServiceCommitmentForVendor(TempServiceCommitment, ServiceObject, VendorContract); - VendorContract.CreateVendorContractLinesFromServiceCommitments(TempServiceCommitment); + ContractTestLibrary.FillTempServiceCommitmentForVendor(TempSubscriptionLine, SubscriptionHeader, VendorContract); + VendorContract.CreateVendorContractLinesFromServiceCommitments(TempSubscriptionLine); VendorContractLine.SetRange("Subscription Contract No.", VendorContract."No."); VendorContractLine.FindLast(); ContractTestLibrary.SetGeneralPostingSetup(Vendor."Gen. Bus. Posting Group", Item."Gen. Prod. Posting Group", false, Enum::"Service Partner"::Vendor); @@ -2306,6 +2435,14 @@ codeunit 148153 "Usage Based Billing Test" NewBillingLineArchive.Insert(false); end; + local procedure MockServiceCommitment(var ServiceCommitment2: Record "Subscription Line"; BillingBasePeriod: DateFormula; BillingRhythm: DateFormula; Price: Decimal) + begin + ServiceCommitment2.Init(); + ServiceCommitment2."Billing Base Period" := BillingBasePeriod; + ServiceCommitment2."Billing Rhythm" := BillingRhythm; + ServiceCommitment2.Price := Price; + end; + local procedure MockUsageData(var NewUsageDataBilling: Record "Usage Data Billing"; Partner: Enum "Service Partner"; DocumentType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20]) begin NewUsageDataBilling.Init(); @@ -2342,23 +2479,23 @@ codeunit 148153 "Usage Based Billing Test" local procedure PrepareServiceCommitmentAndUsageDataGenericImportForUsageBilling(UsageDataGenericImport: Record "Usage Data Generic Import"; UsageBasedPricing: Enum "Usage Based Pricing"; BillingBasePeriod: Text; BillingRhythm: Text; ServiceStartDate: Date) begin - ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); - ServiceCommitment.FindSet(); + SubscriptionLine.SetRange("Subscription Header No.", SubscriptionHeader."No."); + SubscriptionLine.FindSet(); repeat - ServiceCommitment."Usage Based Pricing" := UsageBasedPricing; + SubscriptionLine."Usage Based Pricing" := UsageBasedPricing; if ServiceStartDate <> 0D then - ServiceCommitment."Subscription Line Start Date" := CalcDate('<-CM>', WorkDate()); + SubscriptionLine."Subscription Line Start Date" := CalcDate('<-CM>', WorkDate()); if BillingBasePeriod <> '' then - Evaluate(ServiceCommitment."Billing Base Period", BillingBasePeriod); + Evaluate(SubscriptionLine."Billing Base Period", BillingBasePeriod); if BillingRhythm <> '' then - Evaluate(ServiceCommitment."Billing Rhythm", BillingRhythm); + Evaluate(SubscriptionLine."Billing Rhythm", BillingRhythm); UsageDataSupplierReference.FilterUsageDataSupplierReference(UsageDataImport."Supplier No.", UsageDataGenericImport."Supp. Subscription ID", Enum::"Usage Data Reference Type"::Subscription); if UsageDataSupplierReference.FindFirst() then - ServiceCommitment."Supplier Reference Entry No." := UsageDataSupplierReference."Entry No."; - ServiceCommitment.Modify(false); - UsageDataGenericImport."Subscription Header No." := ServiceObject."No."; + SubscriptionLine."Supplier Reference Entry No." := UsageDataSupplierReference."Entry No."; + SubscriptionLine.Modify(false); + UsageDataGenericImport."Subscription Header No." := SubscriptionHeader."No."; UsageDataGenericImport.Modify(false); - until ServiceCommitment.Next() = 0; + until SubscriptionLine.Next() = 0; end; local procedure ProcessUsageDataImport(ProcessingStep: Enum "Processing Step") @@ -2380,7 +2517,7 @@ codeunit 148153 "Usage Based Billing Test" UsageBasedBTestLibrary.CreateUsageDataSupplier(UsageDataSupplier, Enum::"Usage Data Supplier Type"::Generic, false, Enum::"Vendor Invoice Per"::Import); UsageBasedBTestLibrary.CreateGenericImportSettings(GenericImportSettings, UsageDataSupplier."No.", true, true); UsageBasedBTestLibrary.CreateUsageDataImport(UsageDataImport, UsageDataSupplier."No."); - UsageBasedBTestLibrary.CreateSimpleUsageDataGenericImport(UsageDataGenericImport, UsageDataImport."Entry No.", ServiceObject."No.", Customer."No.", Item."Unit Cost", BillingPeriodStartDate, BillingPeriodEndDate, SubscriptionStartDate, SubscriptionEndDate, Quantity); + UsageBasedBTestLibrary.CreateSimpleUsageDataGenericImport(UsageDataGenericImport, UsageDataImport."Entry No.", SubscriptionHeader."No.", Customer."No.", Item."Unit Cost", BillingPeriodStartDate, BillingPeriodEndDate, SubscriptionStartDate, SubscriptionEndDate, Quantity); ProcessUsageDataImport(Enum::"Processing Step"::"Process Imported Lines"); UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); UsageDataGenericImport.FindFirst(); @@ -2404,45 +2541,45 @@ codeunit 148153 "Usage Based Billing Test" local procedure SetupItemWithMultipleServiceCommitmentPackages() begin // Billing rhythm should be the same as in Usage data billing which is in the "Usage Based B. Test Library" set to 1D always (WorkDate()) Ref: CreateOutStreamData - ContractTestLibrary.CreateServiceCommitmentTemplate(ServiceCommitmentTemplate); - Evaluate(ServiceCommitmentTemplate."Billing Base Period", '1M'); - ServiceCommitmentTemplate."Calculation Base %" := LibraryRandom.RandDec(100, 2); - ServiceCommitmentTemplate."Invoicing via" := Enum::"Invoicing Via"::Contract; - ServiceCommitmentTemplate."Calculation Base Type" := "Calculation Base Type"::"Item Price"; - ServiceCommitmentTemplate."Usage Based Billing" := true; - ServiceCommitmentTemplate.Modify(false); + ContractTestLibrary.CreateServiceCommitmentTemplate(SubPackageLineTemplate); + Evaluate(SubPackageLineTemplate."Billing Base Period", '1M'); + SubPackageLineTemplate."Calculation Base %" := LibraryRandom.RandDec(100, 2); + SubPackageLineTemplate."Invoicing via" := Enum::"Invoicing Via"::Contract; + SubPackageLineTemplate."Calculation Base Type" := "Calculation Base Type"::"Item Price"; + SubPackageLineTemplate."Usage Based Billing" := true; + SubPackageLineTemplate.Modify(false); // Standard Subscription Package with two Subscription Package Lines // 1. for Customer // 2. for Vendor - ContractTestLibrary.CreateServiceCommitmentPackageWithLine(ServiceCommitmentTemplate.Code, ServiceCommitmentPackage, ServiceCommPackageLine); - ServiceCommPackageLine.Partner := Enum::"Service Partner"::Customer; - Evaluate(ServiceCommPackageLine."Extension Term", '<1Y>'); - Evaluate(ServiceCommPackageLine."Notice Period", '<1M>'); - Evaluate(ServiceCommPackageLine."Initial Term", '<1Y>'); - Evaluate(ServiceCommPackageLine."Billing Rhythm", '<1M>'); - ServiceCommPackageLine.Modify(false); - - ContractTestLibrary.CreateServiceCommitmentPackageLine(ServiceCommitmentPackage.Code, ServiceCommitmentTemplate.Code, ServiceCommPackageLine); - ServiceCommPackageLine.Partner := Enum::"Service Partner"::Vendor; - Evaluate(ServiceCommPackageLine."Extension Term", '<1Y>'); - Evaluate(ServiceCommPackageLine."Notice Period", '<1M>'); - Evaluate(ServiceCommPackageLine."Initial Term", '<1Y>'); - Evaluate(ServiceCommPackageLine."Billing Rhythm", '<1M>'); - ServiceCommPackageLine.Modify(false); - ContractTestLibrary.AssignItemToServiceCommitmentPackage(Item, ServiceCommitmentPackage.Code); - ItemServCommitmentPackage.Get(Item."No.", ServiceCommitmentPackage.Code); + ContractTestLibrary.CreateServiceCommitmentPackageWithLine(SubPackageLineTemplate.Code, SubscriptionPackage, SubscriptionPackageLine); + SubscriptionPackageLine.Partner := Enum::"Service Partner"::Customer; + Evaluate(SubscriptionPackageLine."Extension Term", '<1Y>'); + Evaluate(SubscriptionPackageLine."Notice Period", '<1M>'); + Evaluate(SubscriptionPackageLine."Initial Term", '<1Y>'); + Evaluate(SubscriptionPackageLine."Billing Rhythm", '<1M>'); + SubscriptionPackageLine.Modify(false); + + ContractTestLibrary.CreateServiceCommitmentPackageLine(SubscriptionPackage.Code, SubPackageLineTemplate.Code, SubscriptionPackageLine); + SubscriptionPackageLine.Partner := Enum::"Service Partner"::Vendor; + Evaluate(SubscriptionPackageLine."Extension Term", '<1Y>'); + Evaluate(SubscriptionPackageLine."Notice Period", '<1M>'); + Evaluate(SubscriptionPackageLine."Initial Term", '<1Y>'); + Evaluate(SubscriptionPackageLine."Billing Rhythm", '<1M>'); + SubscriptionPackageLine.Modify(false); + ContractTestLibrary.AssignItemToServiceCommitmentPackage(Item, SubscriptionPackage.Code); + ItemServCommitmentPackage.Get(Item."No.", SubscriptionPackage.Code); ItemServCommitmentPackage.Standard := true; ItemServCommitmentPackage.Modify(false); // Additional Subscription Package - ContractTestLibrary.CreateServiceCommitmentPackageWithLine(ServiceCommitmentTemplate.Code, ServiceCommitmentPackage, ServiceCommPackageLine); - ServiceCommPackageLine.Partner := Enum::"Service Partner"::Customer; - Evaluate(ServiceCommPackageLine."Extension Term", '<1Y>'); - Evaluate(ServiceCommPackageLine."Notice Period", '<1M>'); - Evaluate(ServiceCommPackageLine."Initial Term", '<1Y>'); - Evaluate(ServiceCommPackageLine."Billing Rhythm", '<1M>'); - ServiceCommPackageLine.Modify(false); - ContractTestLibrary.AssignItemToServiceCommitmentPackage(Item, ServiceCommitmentPackage.Code); + ContractTestLibrary.CreateServiceCommitmentPackageWithLine(SubPackageLineTemplate.Code, SubscriptionPackage, SubscriptionPackageLine); + SubscriptionPackageLine.Partner := Enum::"Service Partner"::Customer; + Evaluate(SubscriptionPackageLine."Extension Term", '<1Y>'); + Evaluate(SubscriptionPackageLine."Notice Period", '<1M>'); + Evaluate(SubscriptionPackageLine."Initial Term", '<1Y>'); + Evaluate(SubscriptionPackageLine."Billing Rhythm", '<1M>'); + SubscriptionPackageLine.Modify(false); + ContractTestLibrary.AssignItemToServiceCommitmentPackage(Item, SubscriptionPackage.Code); end; local procedure SetupServiceDataForProcessing(UsageBasedPricing: Enum "Usage Based Pricing"; CalculationBaseType: Enum "Calculation Base Type"; @@ -2454,26 +2591,26 @@ codeunit 148153 "Usage Based Billing Test" CalculationBase: Decimal; ItemNo: Code[20]) begin - ContractTestLibrary.CreateServiceCommitmentTemplate(ServiceCommitmentTemplate); - ServiceCommitmentTemplate."Usage Based Billing" := true; - ServiceCommitmentTemplate."Usage Based Pricing" := UsageBasedPricing; - Evaluate(ServiceCommitmentTemplate."Billing Base Period", '1M'); - ServiceCommitmentTemplate."Calculation Base %" := LibraryRandom.RandDec(100, 2); - ServiceCommitmentTemplate."Invoicing via" := InvoicingVia; - ServiceCommitmentTemplate."Calculation Base Type" := CalculationBaseType; - ServiceCommitmentTemplate.Modify(false); - ContractTestLibrary.CreateServiceCommitmentPackageWithLine(ServiceCommitmentTemplate.Code, ServiceCommitmentPackage, ServiceCommPackageLine); - ContractTestLibrary.UpdateServiceCommitmentPackageLine(ServiceCommPackageLine, BillingBasePeriod, CalculationBase, BillingRhythm, ExtensionTerm, ServicePartner, ''); - ContractTestLibrary.AssignItemToServiceCommitmentPackage(Item, ServiceCommitmentPackage.Code); - ItemServCommitmentPackage.Get(ItemNo, ServiceCommitmentPackage.Code); + ContractTestLibrary.CreateServiceCommitmentTemplate(SubPackageLineTemplate); + SubPackageLineTemplate."Usage Based Billing" := true; + SubPackageLineTemplate."Usage Based Pricing" := UsageBasedPricing; + Evaluate(SubPackageLineTemplate."Billing Base Period", '1M'); + SubPackageLineTemplate."Calculation Base %" := LibraryRandom.RandDec(100, 2); + SubPackageLineTemplate."Invoicing via" := InvoicingVia; + SubPackageLineTemplate."Calculation Base Type" := CalculationBaseType; + SubPackageLineTemplate.Modify(false); + ContractTestLibrary.CreateServiceCommitmentPackageWithLine(SubPackageLineTemplate.Code, SubscriptionPackage, SubscriptionPackageLine); + ContractTestLibrary.UpdateServiceCommitmentPackageLine(SubscriptionPackageLine, BillingBasePeriod, CalculationBase, BillingRhythm, ExtensionTerm, ServicePartner, ''); + ContractTestLibrary.AssignItemToServiceCommitmentPackage(Item, SubscriptionPackage.Code); + ItemServCommitmentPackage.Get(ItemNo, SubscriptionPackage.Code); ItemServCommitmentPackage.Standard := true; ItemServCommitmentPackage.Modify(false); LibrarySales.CreateCustomer(Customer); - ContractTestLibrary.CreateServiceObjectForItem(ServiceObject, ItemNo); - ServiceObject.InsertServiceCommitmentsFromStandardServCommPackages(WorkDate()); - ServiceObject."End-User Customer No." := Customer."No."; - ServiceObject.Modify(false); + ContractTestLibrary.CreateServiceObjectForItem(SubscriptionHeader, ItemNo); + SubscriptionHeader.InsertServiceCommitmentsFromStandardServCommPackages(WorkDate()); + SubscriptionHeader."End-User Customer No." := Customer."No."; + SubscriptionHeader.Modify(false); CreateCustomerContractAndAssignServiceCommitments(); end; @@ -2509,8 +2646,8 @@ codeunit 148153 "Usage Based Billing Test" RRef, CopyStr(LibraryRandom.RandText(80), 1, 80), CopyStr(LibraryRandom.RandText(80), 1, 80), - ServiceObject."No.", - ServiceCommitment."Entry No.", + SubscriptionHeader."No.", + SubscriptionLine."Entry No.", BillingPeriodStartingDate, BillingPeriodEndingDate, SubscriptionStartingDate, @@ -2518,6 +2655,21 @@ codeunit 148153 "Usage Based Billing Test" Quantity); end; + local procedure TestIfInvoicesMatchesUsageData(ServicePartner: Enum "Service Partner"; InvoiceAmount: Decimal; DocumentNo: Code[20]) + var + UsageDataBilling: Record "Usage Data Billing"; + begin + UsageDataBilling.Reset(); + UsageDataBilling.FilterOnDocumentTypeAndDocumentNo(ServicePartner, Enum::"Usage Based Billing Doc. Type"::"Invoice", DocumentNo); + UsageDataBilling.CalcSums(Amount, "Cost Amount"); + case ServicePartner of + ServicePartner::Customer: + Assert.AreEqual(UsageDataBilling.Amount, InvoiceAmount, 'The Sales Invoice lines were not created properly.'); + ServicePartner::Vendor: + Assert.AreEqual(UsageDataBilling."Cost Amount", InvoiceAmount, 'The Purchase Invoice lines were not created properly.'); + end; + end; + local procedure TestIfRelatedUsageDataBillingIsUpdated(ServicePartner: Enum "Service Partner"; UsageBasedBillingDocType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20]; TestNotEmptyDocLineNo: Boolean; BillingLineNo: Integer) var UsageDataBilling: Record "Usage Data Billing"; @@ -2538,6 +2690,31 @@ codeunit 148153 "Usage Based Billing Test" until UsageDataBilling.Next() = 0 end; + local procedure TestSubscriptionWithUsageData(BillingPeriod: Text; BillingEndDate: Date; SubscriptionEndDate: Date; UnitPrice: Decimal; ValidateUnitPrice: Boolean) + var + UsageDataBilling: Record "Usage Data Billing"; + begin + Initialize(); + CreateSubscriptionItemWithPrices(UnitPrice, 1); + + SetupServiceDataForProcessing(Enum::"Usage Based Pricing"::"Usage Quantity", "Calculation Base Type"::"Item Price", Enum::"Invoicing Via"::Contract, + BillingPeriod, BillingPeriod, '1Y', "Service Partner"::Customer, 100, Item."No."); + + ProcessUsageDataWithSimpleGenericImport(WorkDate(), BillingEndDate, WorkDate(), SubscriptionEndDate, 1); + + if ValidateUnitPrice then begin + UsageDataBilling.Reset(); + UsageDataBilling.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No."); + UsageDataBilling.SetRange(Partner, "Service Partner"::Customer); + if UsageDataBilling.FindSet() then + repeat + UsageDataBilling.TestField("Unit Price", UsageDataBilling."Charged Period (Days)"); + until UsageDataBilling.Next() = 0; + end; + + CreateContractInvoicesAndTestProcessedUsageData(); + end; + local procedure TestUsageDataBilling(UsageDataGenericImport: Record "Usage Data Generic Import"; var UsageDataBilling: Record "Usage Data Billing") begin UsageDataBilling.TestField("Usage Data Import Entry No.", UsageDataGenericImport."Usage Data Import Entry No."); @@ -2550,14 +2727,14 @@ codeunit 148153 "Usage Based Billing Test" UsageDataBilling.TestField(Amount, 0); UsageDataBilling.TestField("Unit Price", 0); UsageDataBilling.TestField("Currency Code", UsageDataGenericImport.Currency); - UsageDataBilling.TestField("Subscription Header No.", ServiceCommitment."Subscription Header No."); - UsageDataBilling.TestField(Partner, ServiceCommitment.Partner); - UsageDataBilling.TestField("Subscription Contract No.", ServiceCommitment."Subscription Contract No."); - UsageDataBilling.TestField("Subscription Contract Line No.", ServiceCommitment."Subscription Contract Line No."); - UsageDataBilling.TestField("Subscription Header No.", ServiceCommitment."Subscription Header No."); - UsageDataBilling.TestField("Subscription Line Entry No.", ServiceCommitment."Entry No."); - UsageDataBilling.TestField("Usage Base Pricing", ServiceCommitment."Usage Based Pricing"); - UsageDataBilling.TestField("Pricing Unit Cost Surcharge %", ServiceCommitment."Pricing Unit Cost Surcharge %"); + UsageDataBilling.TestField("Subscription Header No.", SubscriptionLine."Subscription Header No."); + UsageDataBilling.TestField(Partner, SubscriptionLine.Partner); + UsageDataBilling.TestField("Subscription Contract No.", SubscriptionLine."Subscription Contract No."); + UsageDataBilling.TestField("Subscription Contract Line No.", SubscriptionLine."Subscription Contract Line No."); + UsageDataBilling.TestField("Subscription Header No.", SubscriptionLine."Subscription Header No."); + UsageDataBilling.TestField("Subscription Line Entry No.", SubscriptionLine."Entry No."); + UsageDataBilling.TestField("Usage Base Pricing", SubscriptionLine."Usage Based Pricing"); + UsageDataBilling.TestField("Pricing Unit Cost Surcharge %", SubscriptionLine."Pricing Unit Cost Surcharge %"); end; local procedure ValidateUsageDataGenericImportAvailability(UsageDataImportEntryNo: Integer; ExpectedServiceObjectAvailability: Enum "Service Object Availability"; ExpectedServiceObjectNo: Code[20]) @@ -2570,13 +2747,6 @@ codeunit 148153 "Usage Based Billing Test" Assert.AreEqual(ExpectedServiceObjectNo, UsageDataGenericImport."Subscription Header No.", 'Service Object No. is not set to expected value in Usage Data Generic Import.'); end; - local procedure MockServiceCommitment(var ServiceCommitment2: Record "Subscription Line"; BillingBasePeriod: DateFormula; BillingRhythm: DateFormula; Price: Decimal) - begin - ServiceCommitment2.Init(); - ServiceCommitment2."Billing Base Period" := BillingBasePeriod; - ServiceCommitment2."Billing Rhythm" := BillingRhythm; - ServiceCommitment2.Price := Price; - end; #endregion Procedures #region Handlers @@ -2619,6 +2789,12 @@ codeunit 148153 "Usage Based Billing Test" begin end; + [StrMenuHandler] + procedure StrMenuHandlerClearBillingProposal(Option: Text[1024]; var Choice: Integer; Instruction: Text[1024]) + begin + Choice := LibraryVariableStorage.DequeueInteger(); + end; + [ModalPageHandler] procedure UsageDataBillingsModalPageHandler(var UsageDataBillings: TestPage "Usage Data Billings") begin @@ -2626,12 +2802,6 @@ codeunit 148153 "Usage Based Billing Test" LibraryVariableStorage.Enqueue(UsageDataBillings.Next()); end; - [StrMenuHandler] - procedure StrMenuHandlerClearBillingProposal(Option: Text[1024]; var Choice: Integer; Instruction: Text[1024]) - begin - Choice := LibraryVariableStorage.DequeueInteger(); - end; - #endregion Handlers } #pragma warning restore AA0210 diff --git a/src/Apps/W1/Subscription Billing/Test/Vendor Contracts/VendorContractsTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Vendor Contracts/VendorContractsTest.Codeunit.al index 66d258eb82..3dadc5a2ea 100644 --- a/src/Apps/W1/Subscription Billing/Test/Vendor Contracts/VendorContractsTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Vendor Contracts/VendorContractsTest.Codeunit.al @@ -5,7 +5,6 @@ using Microsoft.Finance.GeneralLedger.Account; using Microsoft.Inventory.Item; using Microsoft.Purchases.Document; using Microsoft.Purchases.Vendor; -using Microsoft.Sales.Customer; #pragma warning disable AA0210 codeunit 148154 "Vendor Contracts Test" @@ -22,7 +21,6 @@ codeunit 148154 "Vendor Contracts Test" ContractType: Record "Subscription Contract Type"; Currency: Record Currency; CurrExchRate: Record "Currency Exchange Rate"; - Customer: Record Customer; Item: Record Item; PurchaseHeader: Record "Purchase Header"; ServiceCommitment: Record "Subscription Line"; @@ -53,13 +51,12 @@ codeunit 148154 "Vendor Contracts Test" #region Tests [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure CheckClosedVendorContractLines() var VendorContractLine2: Record "Vend. Sub. Contract Line"; begin Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(false); + SetupServiceObjectForNewItemWithServiceCommitment(false, false); ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Vendor."No."); // ExchangeRateSelectionModalPageHandler, MessageHandler ContractTestLibrary.InsertVendorContractCommentLine(VendorContract, VendorContractLine2); ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); @@ -101,14 +98,14 @@ codeunit 148154 "Vendor Contracts Test" end; [Test] - [HandlerFunctions('ServCommWOVendContractPageHandler,ExchangeRateSelectionModalPageHandler,MessageHandler')] + [HandlerFunctions('ServCommWOVendContractPageHandler')] procedure CheckServiceCommitmentAssignmentToVendorContractForServiceObjectWithItem() var InvoicingViaNotManagedErr: Label 'Invoicing via %1 not managed', Locked = true; begin // [SCENARIO] Check that proper Subscription Lines are assigned to Vendor Subscription Contract Lines. Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(false); + SetupServiceObjectForNewItemWithServiceCommitment(false, false); ContractTestLibrary.CreateVendorContract(VendorContract, Vendor."No."); VendorContractPage.OpenEdit(); @@ -178,7 +175,7 @@ codeunit 148154 "Vendor Contracts Test" procedure CheckServiceCommitmentAssignmentToVendorContractInFCY() begin Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(false); + SetupServiceObjectForNewItemWithServiceCommitment(false, true); ContractTestLibrary.CreateVendorContract(VendorContract, Vendor."No."); VendorContractPage.OpenEdit(); @@ -223,7 +220,7 @@ codeunit 148154 "Vendor Contracts Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,ConfirmHandlerYes,MessageHandler')] + [HandlerFunctions('ConfirmHandlerYes,MessageHandler')] procedure CheckValueChangesOnVendorContractLines() var OldServiceCommitment: Record "Subscription Line"; @@ -438,7 +435,7 @@ codeunit 148154 "Vendor Contracts Test" procedure ExpectErrorOnAssignServiceCommitmentsWithMultipleCurrencies() begin Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(false); + SetupServiceObjectForNewItemWithServiceCommitment(false, true); ContractTestLibrary.CreateVendorContract(VendorContract, Vendor."No."); ServiceCommitment.Reset(); @@ -510,13 +507,12 @@ codeunit 148154 "Vendor Contracts Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure ExpectNoClosedVendorContractLines() var VendorContractLine2: Record "Vend. Sub. Contract Line"; begin Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(false); + SetupServiceObjectForNewItemWithServiceCommitment(false, false); ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Vendor."No."); ContractTestLibrary.InsertVendorContractCommentLine(VendorContract, VendorContractLine2); ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); @@ -604,14 +600,14 @@ codeunit 148154 "Vendor Contracts Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler,ConfirmHandlerYes')] + [HandlerFunctions('ConfirmHandlerYes')] procedure TestDeleteServiceCommitmentLinkedToContractLineIsClosed() begin // Test: A closed Contract Line is deleted when deleting the Subscription Line Initialize(); ContractTestLibrary.DeleteAllContractRecords(); - SetupServiceObjectForNewItemWithServiceCommitment(false); - ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Customer."No."); // ExchangeRateSelectionModalPageHandler, MessageHandler + SetupServiceObjectForNewItemWithServiceCommitment(false, false); + ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Vendor."No."); ServiceCommitment.Reset(); ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); @@ -628,14 +624,13 @@ codeunit 148154 "Vendor Contracts Test" end; [Test] - [HandlerFunctions('ExchangeRateSelectionModalPageHandler,MessageHandler')] procedure TestDeleteServiceCommitmentLinkedToContractLineNotClosed() begin // Test: Subscription Line cannot be deleted if an open contract line exists Initialize(); ContractTestLibrary.DeleteAllContractRecords(); - SetupServiceObjectForNewItemWithServiceCommitment(false); - ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Customer."No."); // ExchangeRateSelectionModalPageHandler, MessageHandler + SetupServiceObjectForNewItemWithServiceCommitment(false, false); + ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Vendor."No."); ServiceCommitment.Reset(); ServiceCommitment.SetRange("Subscription Header No.", ServiceObject."No."); @@ -653,7 +648,7 @@ codeunit 148154 "Vendor Contracts Test" begin Initialize(); ContractTestLibrary.DeleteAllContractRecords(); - ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Vendor."No.", true); + ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, '', true); UpdateServiceStartDateFromVendorContractSubpage(); @@ -704,7 +699,7 @@ codeunit 148154 "Vendor Contracts Test" procedure TestRecalculateServiceCommitmentsOnChangeCurrencyCode() begin Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(false); + SetupServiceObjectForNewItemWithServiceCommitment(false, true); ContractTestLibrary.CreateVendorContract(VendorContract, Vendor."No."); VendorContractPage.OpenEdit(); @@ -719,11 +714,11 @@ codeunit 148154 "Vendor Contracts Test" end; [Test] - [HandlerFunctions('ServCommWOVendContractPageHandler,ExchangeRateSelectionModalPageHandler,MessageHandler')] + [HandlerFunctions('ServCommWOVendContractPageHandler')] procedure TestResetServiceCommitmentsOnCurrencyCodeDelete() begin Initialize(); - SetupServiceObjectForNewItemWithServiceCommitment(false); + SetupServiceObjectForNewItemWithServiceCommitment(false, false); ContractTestLibrary.CreateVendorContract(VendorContract, Vendor."No."); VendorContractPage.OpenEdit(); @@ -808,7 +803,7 @@ codeunit 148154 "Vendor Contracts Test" local procedure CreateVendorContractSetup() begin - SetupServiceObjectForNewItemWithServiceCommitment(false); + SetupServiceObjectForNewItemWithServiceCommitment(false, false); ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Vendor."No."); end; @@ -827,18 +822,18 @@ codeunit 148154 "Vendor Contracts Test" ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, '', CreateAdditionalLine); end; - local procedure SetupServiceObjectForNewItemWithServiceCommitment(SNSpecificTracking: Boolean) + local procedure SetupServiceObjectForNewItemWithServiceCommitment(SNSpecificTracking: Boolean; VendorWithCurrency: Boolean) var ItemServCommitmentPackage: Record "Item Subscription Package"; ServiceCommPackageLine: Record "Subscription Package Line"; ServiceCommitmentPackage: Record "Subscription Package"; begin ClearAll(); - ContractTestLibrary.CreateVendor(Vendor); + if VendorWithCurrency then + ContractTestLibrary.CreateVendor(Vendor) + else + ContractTestLibrary.CreateVendorInLCY(Vendor); ContractTestLibrary.CreateServiceObjectForItem(ServiceObject, Item, SNSpecificTracking); - ServiceObject.SetHideValidationDialog(true); - ServiceObject.Validate("End-User Customer Name", Customer.Name); - ServiceObject.Modify(false); ContractTestLibrary.CreateServiceCommitmentTemplate(ServiceCommitmentTemplate); ServiceCommitmentTemplate."Calculation Base %" := LibraryRandom.RandDec(100, 2); @@ -895,10 +890,7 @@ codeunit 148154 "Vendor Contracts Test" begin ClearAll(); ContractTestLibrary.InitContractsApp(); - ContractTestLibrary.CreateCustomer(Customer); ContractTestLibrary.CreateServiceObjectForGLAccountWithServiceCommitments(ServiceObject, GLAccount, 0, 1, '<1Y>', '<1M>'); - ServiceObject.Validate("End-User Customer Name", Customer.Name); - ServiceObject.Modify(false); end; local procedure TestNewServiceObject()