namespace Microsoft.SubscriptionBilling; using Microsoft.Finance.Currency; using Microsoft.Finance.Dimension; using Microsoft.Finance.GeneralLedger.Account; using Microsoft.Foundation.AuditCodes; using Microsoft.Foundation.Calendar; using Microsoft.Inventory.Item; using Microsoft.Purchases.Vendor; using Microsoft.Sales.Customer; using Microsoft.Sales.Document; using Microsoft.Sales.Pricing; using System.Utilities; table 8059 "Subscription Line" { DataClassification = CustomerContent; Caption = 'Subscription Line'; DrillDownPageId = "Service Commitments List"; LookupPageId = "Service Commitments List"; fields { field(1; "Subscription Header No."; Code[20]) { Caption = 'Subscription No.'; TableRelation = "Subscription Header"; } field(2; "Entry No."; Integer) { Caption = 'Entry No.'; AutoIncrement = true; } field(3; "Subscription Package Code"; Code[20]) { Caption = 'Subscription Package Code'; NotBlank = true; TableRelation = "Subscription Package"; Editable = false; } field(4; Template; Code[20]) { Caption = 'Template'; NotBlank = true; TableRelation = "Sub. Package Line Template"; ValidateTableRelation = false; Editable = false; } field(5; Description; Text[100]) { Caption = 'Description'; } field(6; "Subscription Line Start Date"; Date) { Caption = 'Subscription Line Start Date'; trigger OnValidate() begin CheckSubscriptionLineStartDateChangeAllowed(); DateFormulaManagement.ErrorIfDateEmpty("Subscription Line Start Date", FieldCaption("Subscription Line Start Date")); UpdateNextBillingDate("Subscription Line Start Date" - 1); CheckServiceDates(); RecalculateHarmonizedBillingFieldsOnCustomerContract(); UpdateNextPriceUpdate(); CalculateSubscriptionDates(); end; } field(7; "Subscription Line End Date"; Date) { Caption = 'Subscription Line End Date'; trigger OnValidate() begin DateFormulaManagement.ErrorIfDateEmpty("Subscription Line Start Date", FieldCaption("Subscription Line Start Date")); ErrorIfPlannedServiceCommitmentExists(); CheckServiceDates(); ClearTerminationPeriodsWhenServiceEnded(); RefreshRenewalTerm(); end; } field(8; "Next Billing Date"; Date) { Caption = 'Next Billing Date'; Editable = false; } field(9; "Calculation Base Amount"; Decimal) { Caption = 'Calculation Base Amount'; MinValue = 0; BlankZero = true; AutoFormatType = 2; AutoFormatExpression = Rec."Currency Code"; trigger OnValidate() begin if "Currency Code" <> '' then begin Currency.InitRoundingPrecision(); "Calculation Base Amount (LCY)" := Round(CurrExchRate.ExchangeAmtFCYToLCY("Currency Factor Date", "Currency Code", "Calculation Base Amount" * "Calculation Base %" / 100, "Currency Factor"), Currency."Unit-Amount Rounding Precision"); end else "Calculation Base Amount (LCY)" := "Calculation Base Amount"; CalculatePrice(); end; } field(10; "Calculation Base %"; Decimal) { Caption = 'Calculation Base %'; MinValue = 0; BlankZero = true; DecimalPlaces = 0 : 5; AutoFormatType = 0; trigger OnValidate() begin CalculatePrice(); end; } field(11; "Price"; Decimal) { Caption = 'Price'; Editable = false; BlankZero = true; AutoFormatType = 2; AutoFormatExpression = Rec."Currency Code"; trigger OnValidate() begin if not "Usage Based Billing" then begin Currency.Initialize("Currency Code"); "Price" := Round("Price", Currency."Unit-Amount Rounding Precision"); end; Validate("Discount %"); if "Currency Code" = '' then "Price (LCY)" := Price; end; } field(12; "Discount %"; Decimal) { Caption = 'Discount %'; MinValue = 0; MaxValue = 100; BlankZero = true; DecimalPlaces = 0 : 5; AutoFormatType = 0; trigger OnValidate() begin if "Discount %" <> 0 then TestField(Discount, false); CalculateServiceAmount(FieldNo("Discount %")); end; } field(13; "Discount Amount"; Decimal) { Caption = 'Discount Amount'; MinValue = 0; BlankZero = true; AutoFormatType = 1; AutoFormatExpression = Rec."Currency Code"; trigger OnValidate() begin if "Discount Amount" <> 0 then TestField(Discount, false); CalculateServiceAmount(FieldNo("Discount Amount")); end; } field(14; Amount; Decimal) { Caption = 'Amount'; BlankZero = true; AutoFormatType = 1; AutoFormatExpression = Rec."Currency Code"; trigger OnValidate() begin CalculateServiceAmount(FieldNo(Amount)); end; } field(15; "Billing Base Period"; DateFormula) { Caption = 'Billing Base Period'; trigger OnValidate() begin DateFormulaManagement.ErrorIfDateFormulaEmpty("Billing Base Period", FieldCaption("Billing Base Period")); DateFormulaManagement.ErrorIfDateFormulaNegative("Billing Base Period"); CheckRatioBetweenBillingBasePeriodAndRhythm(); end; } field(16; "Invoicing via"; Enum "Invoicing Via") { Caption = 'Invoicing via'; trigger OnValidate() begin if "Invoicing via" = "Invoicing via"::Sales then "Create Contract Deferrals" := "Create Contract Deferrals"::No else "Create Contract Deferrals" := "Create Contract Deferrals"::"Contract-dependent"; ErrorIfInvoicingViaIsNotContractForDiscount(); end; } field(17; "Invoicing Item No."; Code[20]) { Caption = 'Invoicing Item No.'; TableRelation = Item."No." where("Subscription Option" = filter("Invoicing Item" | "Service Commitment Item")); trigger OnValidate() begin if "Invoicing via" = "Invoicing via"::Sales then Error(InvoicingItemNoErr); ErrorIfInvoicingItemIsNotServiceCommitmentItemForDiscount(); end; } field(18; Partner; Enum "Service Partner") { Caption = 'Partner'; } field(19; "Subscription Contract No."; Code[20]) { Caption = 'Subscription Contract No.'; TableRelation = if (Partner = const(Customer)) "Customer Subscription Contract" where("Sell-to Customer No." = field("Sub. Header Customer No.")) else if (Partner = const(Vendor)) "Vendor Subscription Contract"; } field(20; "Notice Period"; DateFormula) { Caption = 'Notice Period'; trigger OnValidate() begin if IsNoticePeriodEmpty() then exit; DateFormulaManagement.ErrorIfDateFormulaNegative("Notice Period"); if "Term until" <> 0D then CalculateCancellationPossibleUntil(); end; } field(21; "Initial Term"; DateFormula) { Caption = 'Initial Term'; trigger OnValidate() begin DateFormulaManagement.ErrorIfDateFormulaNegative("Initial Term"); RefreshRenewalTerm(); end; } field(22; "Extension Term"; DateFormula) { Caption = 'Subsequent Term'; trigger OnValidate() begin if Format("Extension Term") = '' then TestField("Notice Period", "Extension Term"); DateFormulaManagement.ErrorIfDateFormulaNegative("Extension Term"); end; } field(23; "Billing Rhythm"; DateFormula) { Caption = 'Billing Rhythm'; trigger OnValidate() begin DateFormulaManagement.ErrorIfDateFormulaEmpty("Billing Rhythm", FieldCaption("Billing Rhythm")); DateFormulaManagement.ErrorIfDateFormulaNegative("Billing Rhythm"); CheckRatioBetweenBillingBasePeriodAndRhythm(); end; } field(24; "Cancellation Possible Until"; Date) { Caption = 'Cancellation Possible Until'; trigger OnValidate() begin CalculateTermUntilFromCancellationPossibleUntil(); end; } field(25; "Term Until"; Date) { Caption = 'Term Until'; trigger OnValidate() begin CalculateCancellationPossibleUntil(); end; } field(26; "Sub. Header Customer No."; Code[20]) { Caption = 'Subscription Customer No.'; FieldClass = FlowField; CalcFormula = lookup("Subscription Header"."End-User Customer No." where("No." = field("Subscription Header No."))); Editable = false; } field(27; "Subscription Contract Line No."; Integer) { Caption = 'Subscription Contract Line No.'; TableRelation = if (Partner = const(Customer)) "Cust. Sub. Contract Line"."Line No." where("Subscription Contract No." = field("Subscription Contract No.")) else if (Partner = const(Vendor)) "Vend. Sub. Contract Line"."Line No." where("Subscription Contract No." = field("Subscription Contract No.")); } field(29; "Shortcut Dimension 1 Code"; Code[20]) { CaptionClass = '1,2,1'; Caption = 'Shortcut Dimension 1 Code'; ToolTip = 'Specifies the code for Shortcut Dimension 1, which is one of two global dimension codes that you set up in the General Ledger Setup window.'; TableRelation = "Dimension Value".Code where("Global Dimension No." = const(1), Blocked = const(false)); trigger OnValidate() begin ValidateShortcutDimCode(1, "Shortcut Dimension 1 Code"); end; } field(30; "Shortcut Dimension 2 Code"; Code[20]) { CaptionClass = '1,2,2'; Caption = 'Shortcut Dimension 2 Code'; ToolTip = 'Specifies the code for Shortcut Dimension 2, which is one of two global dimension codes that you set up in the General Ledger Setup window.'; TableRelation = "Dimension Value".Code where("Global Dimension No." = const(2), Blocked = const(false)); trigger OnValidate() begin ValidateShortcutDimCode(2, "Shortcut Dimension 2 Code"); end; } field(31; "Price (LCY)"; Decimal) { Caption = 'Price (LCY)'; Editable = false; BlankZero = true; AutoFormatType = 2; AutoFormatExpression = ''; } field(32; "Discount Amount (LCY)"; Decimal) { Caption = 'Discount Amount (LCY)'; Editable = false; MinValue = 0; BlankZero = true; AutoFormatType = 1; AutoFormatExpression = ''; } field(33; "Amount (LCY)"; Decimal) { Caption = 'Amount (LCY)'; Editable = false; BlankZero = true; AutoFormatType = 1; AutoFormatExpression = ''; } field(34; "Currency Code"; Code[10]) { Caption = 'Currency Code'; Editable = false; TableRelation = Currency; trigger OnValidate() begin if ((CurrFieldNo <> FieldNo("Currency Code")) and ("Currency Code" = xRec."Currency Code")) or ("Currency Code" <> xRec."Currency Code") or ("Currency Code" <> '') then UpdateCurrencyFactorAndRecalculateAmountsFromExchRate(); end; } field(35; "Currency Factor"; Decimal) { Caption = 'Currency Factor'; DecimalPlaces = 0 : 15; Editable = false; MinValue = 0; AutoFormatType = 0; } field(36; "Currency Factor Date"; Date) { Caption = 'Currency Factor Date'; Editable = false; } field(37; "Calculation Base Amount (LCY)"; Decimal) { Caption = 'Calculation Base Amount (LCY)'; Editable = false; BlankZero = true; AutoFormatType = 2; AutoFormatExpression = ''; } field(38; Discount; Boolean) { Caption = 'Discount'; trigger OnValidate() begin ErrorIfInvoicingViaIsNotContractForDiscount(); ErrorIfInvoicingItemIsNotServiceCommitmentItemForDiscount(); ErrorIfDiscountUsedWithUsageBasedBilling(); end; } field(39; Quantity; Decimal) { Caption = 'Quantity'; Editable = false; FieldClass = FlowField; CalcFormula = lookup("Subscription Header".Quantity where("No." = field("Subscription Header No."))); AutoFormatType = 0; DecimalPlaces = 0 : 5; } field(40; "Create Contract Deferrals"; Enum "Create Contract Deferrals") { Caption = 'Create Contract Deferrals'; trigger OnValidate() begin if OpenDeferralsExist() then Error(DeferralsExistErr); end; } field(42; "Customer Price Group"; Code[10]) { Caption = 'Customer Price Group'; Editable = false; TableRelation = "Customer Price Group"; } field(50; "Next Price Update"; Date) { Caption = 'Next Price Update'; } field(51; "Exclude from Price Update"; Boolean) { Caption = 'Exclude from Price Update'; } field(52; "Price Binding Period"; DateFormula) { Caption = 'Price Binding Period'; trigger OnValidate() begin UpdateNextPriceUpdate(); end; } field(59; "Period Calculation"; enum "Period Calculation") { Caption = 'Period Calculation'; } field(60; "Variant Code"; Code[10]) { Caption = 'Variant Code'; ToolTip = 'Specifies the Variant Code of the Subscription.'; FieldClass = FlowField; CalcFormula = lookup("Subscription Header"."Variant Code" where("No." = field("Subscription Header No."))); Editable = false; } field(107; "Closed"; Boolean) { Caption = 'Closed'; } field(100; "Unit Cost"; Decimal) { AutoFormatExpression = Rec."Currency Code"; AutoFormatType = 2; Caption = 'Unit Cost'; Editable = false; } field(101; "Unit Cost (LCY)"; Decimal) { AutoFormatType = 2; AutoFormatExpression = ''; Caption = 'Unit Cost (LCY)'; trigger OnValidate() var Currency: Record Currency; begin NoManualEntryOfUnitCostLCYForVendorServCommError(CurrFieldNo); if Rec."Currency Code" <> '' then begin Currency.Initialize("Currency Code"); Currency.TestField("Unit-Amount Rounding Precision"); "Unit Cost" := Round( CurrExchRate.ExchangeAmtLCYToFCY( "Currency Factor Date", Rec."Currency Code", "Unit Cost (LCY)", Rec."Currency Factor"), Currency."Unit-Amount Rounding Precision") end else "Unit Cost" := "Unit Cost (LCY)"; end; } field(200; "Planned Sub. Line exists"; Boolean) { Caption = 'Planned Subscription Line exists'; Editable = false; FieldClass = FlowField; CalcFormula = exist("Planned Subscription Line" where("Entry No." = field("Entry No."))); } field(202; "Renewal Term"; DateFormula) { Caption = 'Renewal Term'; trigger OnValidate() var BlankDateFormula: DateFormula; begin if Rec."Renewal Term" <> BlankDateFormula then Rec.TestField("Subscription Line End Date"); DateFormulaManagement.ErrorIfDateFormulaNegative("Renewal Term"); end; } field(210; Selected; Boolean) { Caption = 'Selected'; } field(211; Indent; Integer) { Caption = 'Indent'; } field(480; "Dimension Set ID"; Integer) { Caption = 'Dimension Set ID'; Editable = false; TableRelation = "Dimension Set Entry"; trigger OnLookup() begin EditDimensionSet(); end; trigger OnValidate() begin DimMgt.UpdateGlobalDimFromDimSetID("Dimension Set ID", "Shortcut Dimension 1 Code", "Shortcut Dimension 2 Code"); end; } field(8000; "Usage Based Billing"; Boolean) { Caption = 'Usage Based Billing'; DataClassification = CustomerContent; Editable = false; } field(8001; "Usage Based Pricing"; Enum "Usage Based Pricing") { Caption = 'Usage Based Pricing'; DataClassification = CustomerContent; Editable = false; } field(8002; "Pricing Unit Cost Surcharge %"; Decimal) { Caption = 'Pricing Unit Cost Surcharge %'; DataClassification = CustomerContent; Editable = false; AutoFormatType = 0; DecimalPlaces = 0 : 5; } field(8003; "Supplier Reference Entry No."; Integer) { Caption = 'Supplier Reference Entry No.'; DataClassification = CustomerContent; TableRelation = "Usage Data Supplier Reference" where(Type = const(Subscription)); Editable = false; } field(8006; "Created in Contract line"; Boolean) { Caption = 'Created in Contract line'; Editable = false; } field(8007; "Source Type"; Enum "Service Object Type") { Caption = 'Source Type'; Editable = false; FieldClass = FlowField; CalcFormula = lookup("Subscription Header".Type where("No." = field("Subscription Header No."))); } field(8008; "Source No."; Code[20]) { Caption = 'Source No.'; Editable = false; FieldClass = FlowField; CalcFormula = lookup("Subscription Header"."Source No." where("No." = field("Subscription Header No."))); } #if not CLEANSCHEMA29 field(8009; "Item No."; Code[20]) { Caption = 'Item No.'; Editable = false; FieldClass = FlowField; CalcFormula = lookup("Subscription Header"."Item No." where("No." = field("Subscription Header No."))); ObsoleteReason = 'Replaced by field Source No.'; #if not CLEAN26 ObsoleteState = Pending; ObsoleteTag = '26.0'; #else ObsoleteState = Removed; #pragma warning disable AS0072 // Bug 647877: temporary v30 suppression, restore ObsoleteTag to 30.0 ObsoleteTag = '29.0'; #pragma warning restore AS0072 #endif } #endif field(8010; "Subscription Description"; Text[100]) { Caption = 'Subscription Description'; Editable = false; FieldClass = FlowField; CalcFormula = lookup("Subscription Header".Description where("No." = field("Subscription Header No."))); } } keys { key(PK; "Entry No.") { Clustered = true; } key(Contract; "Subscription Contract No.", "Subscription Contract Line No.") { } key(Key3; "Subscription Header No.", Partner) { } key(Key4; "Supplier Reference Entry No.", "Subscription Line Start Date") { } } trigger OnInsert() begin if not SkipTestPackageCode then TestField("Subscription Package Code"); end; trigger OnModify() begin DisplayErrorIfContractLinesExist(ClosedContractLineExistErr, true); SetUpdateRequiredOnBillingLines(); UpdateCustomerContractLineServiceCommitmentDescription(); ArchiveServiceCommitment(); end; trigger OnDelete() begin DisplayErrorIfStartDateIsDifferentThanNextBillingDate(); DisplayErrorIfContractLinesExist(OpenContractLinesExistErr, false); DeleteContractLine(); SetUpdateRequiredOnBillingLines(); DisconnectBillingLineArchive(); DeleteContractPriceUpdateLines(); end; var Currency: Record Currency; CurrExchRate: Record "Currency Exchange Rate"; CalendarManagement: Codeunit "Calendar Management"; DateFormulaManagement: Codeunit "Date Formula Management"; DimMgt: Codeunit DimensionManagement; DateTimeManagement: Codeunit "Date Time Management"; NegativeDateFormula: DateFormula; SkipArchiving: Boolean; SkipTestPackageCode: Boolean; DateBeforeDateErr: Label '%1 cannot be before %2.', Comment = '%1 = Next Billing Date; %2 = Subscription Line Start Date'; OnlyOneDayBeforeErr: Label 'The %1 is only allowed to be 1 day before the %2.', Comment = '%1 = Subscription Line End Date; %2 = Next Billing Date'; CannotBeGreaterThanErr: Label '%1 cannot be greater than %2.', Comment = '%1 = Amount; %2 = Max Service Amount'; CannotBeLessThanErr: Label '%1 cannot be less than %2.', Comment = '%1 = Amount; %2 = 0'; OpenContractLinesExistErr: Label 'The Subscription Line cannot be deleted because it is linked to a contract line which is not yet marked as "Closed".'; ClosedContractLineExistErr: Label 'Subscription Lines for closed contract lines may not be edited. Remove the "Finished" indicator in the contract to be able to edit the Subscription Lines.'; DifferentCurrenciesInSerCommitmentErr: Label 'The selected Subscription Lines must be converted into different currencies. Please select only Subscription Lines with the same currency.'; ZeroExchangeRateErr: Label 'The price could not be updated because the exchange rate is 0.'; BillingLineForServiceCommitmentExistErr: Label 'The contract line is in the current billing. Delete the billing line to be able to adjust the Subscription Line start date.'; BillingLineArchiveForServiceCommitmentExistErr: Label 'The contract line has already been billed. The Subscription Line start date can no longer be changed.'; NoManualEntryOfUnitCostLCYForVendorServCommErr: Label 'Please use the fields "Calculation Base Amount" and "Calculation Base %" in order to update the unit cost.'; DeferralsExistErr: Label 'The creation of contract deferrals cannot be changed as there are still unreleased deferrals for this contract line.'; SubscriptionLineStartDateDifferentThanNextBillingDateErr: Label 'The %1 must be the same as the %2 to delete the %3.', Comment = '%1 = Service Start Date; %2 = Next Billing Date; %3 = Service Commitment'; InvoicingItemNoErr: Label 'Subscription Lines for a sales document are not invoiced. No value may be entered in the Invoicing Item No..'; DiscountCanBeInvoicedViaContractErr: Label 'Recurring discounts can only be granted for Invoicing via Contract.'; DiscountCannotBeAssignedErr: Label 'Subscription Package Lines, which are discounts, can only be assigned to Subscription Items.'; RecurringDiscountCannotBeGrantedErr: Label 'Recurring discounts cannot be granted in conjunction with Usage Based Billing'; local procedure ErrorIfInvoicingViaIsNotContractForDiscount() begin if not Rec.Discount then exit; if Rec."Invoicing via" <> Enum::"Invoicing Via"::Contract then Error(DiscountCanBeInvoicedViaContractErr); end; local procedure ErrorIfInvoicingItemIsNotServiceCommitmentItemForDiscount() var Item: Record Item; begin if not Rec.Discount then exit; if not Item.Get(Rec."Invoicing Item No.") then exit; if Item."Subscription Option" <> Enum::"Item Service Commitment Type"::"Service Commitment Item" then Error(DiscountCannotBeAssignedErr); end; local procedure ErrorIfDiscountUsedWithUsageBasedBilling() begin if Rec.Discount then if Rec."Usage Based Billing" then Error(RecurringDiscountCannotBeGrantedErr); end; internal procedure CheckServiceDates() begin CheckServiceDates(Rec."Subscription Line Start Date", Rec."Subscription Line End Date", Rec."Next Billing Date"); end; internal procedure CheckServiceDates(ServiceStartDate: Date; ServiceEndDate: Date; NextBillingDate: Date) var IsHandled: Boolean; begin IsHandled := false; OnBeforeCheckServiceDates(ServiceStartDate, ServiceEndDate, NextBillingDate, IsHandled); if IsHandled then exit; if (ServiceStartDate <> 0D) and (ServiceEndDate <> 0D) then if ServiceStartDate > ServiceEndDate then Error(DateBeforeDateErr, Rec.FieldCaption("Subscription Line End Date"), Rec.FieldCaption("Subscription Line Start Date")); if NextBillingDate <> 0D then begin if (ServiceStartDate <> 0D) and (NextBillingDate < ServiceStartDate) then Error(DateBeforeDateErr, Rec.FieldCaption("Next Billing Date"), Rec.FieldCaption("Subscription Line Start Date")); if (ServiceEndDate <> 0D) and (CalcDate('<-1D>', NextBillingDate) > ServiceEndDate) then Error(OnlyOneDayBeforeErr, Rec.FieldCaption("Subscription Line End Date"), Rec.FieldCaption("Next Billing Date")); end; end; local procedure DisplayErrorIfContractLinesExist(ErrorTxt: Text; CheckContractLineClosed: Boolean) var CustomerContractLine: Record "Cust. Sub. Contract Line"; VendorContractLine: Record "Vend. Sub. Contract Line"; begin case Partner of Partner::Customer: if CustomerContractLine.FindFirstSubscriptionLine(Rec) then if ((CheckContractLineClosed and CustomerContractLine.Closed) or (not CustomerContractLine.Closed and not CheckContractLineClosed)) then Error(ErrorTxt); Partner::Vendor: if VendorContractLine.FindFirstSubscriptionLine(Rec) then if ((CheckContractLineClosed and VendorContractLine.Closed) or (not VendorContractLine.Closed and not CheckContractLineClosed)) then Error(ErrorTxt); end; end; internal procedure ClearTerminationPeriodsWhenServiceEnded() begin if ("Subscription Line End Date" <> 0D) and ("Subscription Line End Date" < WorkDate()) then begin Clear("Term Until"); Clear("Cancellation Possible Until"); end; end; internal procedure CalculateSubscriptionDates() begin CalculateServiceEndDate(); CalculateTermUntilDate(); end; internal procedure CalculateServiceEndDate() begin if IsInitialTermEmpty() then exit; if not IsExtensionTermEmpty() then exit; TestField("Subscription Line Start Date"); "Subscription Line End Date" := CalcDate("Initial Term", "Subscription Line Start Date"); "Subscription Line End Date" := CalcDate('<-1D>', "Subscription Line End Date"); RefreshRenewalTerm(); end; internal procedure CalculateTermUntilDate() begin if "Subscription Line End Date" <> 0D then "Term Until" := "Subscription Line End Date" else if not IsExtensionTermEmpty() then begin TestField("Subscription Line Start Date"); if not IsInitialTermEmpty() then begin "Term Until" := CalcDate("Initial Term", "Subscription Line Start Date"); "Term Until" := CalcDate('<-1D>', "Term Until"); end else if not IsNoticePeriodEmpty() then begin "Term Until" := CalcDate("Notice Period", "Subscription Line Start Date"); "Term Until" := CalcDate('<-1D>', "Term Until"); end; end; CalculateCancellationPossibleUntil(); end; internal procedure CalculateTermUntilUsingExtensionTerm(): Boolean var PreviousTermUntil: Date; begin if IsExtensionTermEmpty() or (("Term Until" = 0D) and ("Subscription Line Start Date" = 0D)) then exit(false); if "Term Until" <> 0D then PreviousTermUntil := "Term Until" else begin PreviousTermUntil := "Subscription Line Start Date"; PreviousTermUntil := CalcDate('<-1D>', PreviousTermUntil); end; "Term Until" := CalcDate("Extension Term", PreviousTermUntil); // Aligning to the end of the month compensates for months of different length. It must not be applied to a Subsequent Term // expressed in days or weeks, where the exact number of days is what was agreed. if DateTimeManagement.IsLastDayOfMonth(PreviousTermUntil) and DateFormulaManagement.IsMonthBasedDateFormula("Extension Term") then DateTimeManagement.MoveDateToLastDayOfMonth("Term until"); exit(true); end; local procedure CalculateTermUntilFromCancellationPossibleUntil() begin if IsNoticePeriodEmpty() then exit; if "Cancellation Possible Until" = 0D then exit; "Term Until" := CalcDate("Notice Period", "Cancellation Possible Until"); // Aligning to the end of the month compensates for months of different length. It must not be applied to a Notice Period // expressed in days or weeks, where the exact number of days is what was agreed. if DateTimeManagement.IsLastDayOfMonth("Cancellation possible until") and DateFormulaManagement.IsMonthBasedDateFormula("Notice Period") then DateTimeManagement.MoveDateToLastDayOfMonth("Term until"); end; internal procedure CalculateCancellationPossibleUntil(): Boolean begin if IsNoticePeriodEmpty() or ("Term until" = 0D) then exit(false); CalendarManagement.ReverseDateFormula(NegativeDateFormula, "Notice Period"); "Cancellation Possible Until" := CalcDate(NegativeDateFormula, "Term Until"); // Aligning to the end of the month compensates for months of different length. It must not be applied to a Notice Period // expressed in days or weeks, where the exact number of days is what was agreed. if DateTimeManagement.IsLastDayOfMonth("Term until") and DateFormulaManagement.IsMonthBasedDateFormula("Notice Period") then DateTimeManagement.MoveDateToLastDayOfMonth("Cancellation possible until"); exit(true); end; internal procedure GetReferenceDate(): Date begin case true of "Cancellation Possible Until" <> 0D: exit("Cancellation Possible Until"); "Term Until" <> 0D: exit("Term Until"); "Subscription Line Start Date" <> 0D: exit("Subscription Line Start Date"); end; end; internal procedure CalculatePrice() begin if "Calculation Base Amount" <> 0 then Validate(Price, "Calculation Base Amount" * "Calculation Base %" / 100) else Validate(Price, 0); if Partner = Partner::Vendor then CalculateUnitCost(); end; internal procedure CalculateServiceAmount(CalledByFieldNo: Integer) var ServiceObject: Record "Subscription Header"; MaxServiceAmount: Decimal; begin ServiceObject.Get("Subscription Header No."); Currency.Initialize("Currency Code"); MaxServiceAmount := Price * ServiceObject.Quantity; if not "Usage Based Billing" then MaxServiceAmount := Round(MaxServiceAmount, Currency."Amount Rounding Precision"); if CalledByFieldNo = FieldNo(Amount) then begin if not "Usage Based Billing" then Amount := Round(Amount, Currency."Amount Rounding Precision"); if Amount > MaxServiceAmount then Error(CannotBeGreaterThanErr, FieldCaption(Amount), Format(MaxServiceAmount)); if Amount < 0 then Error(CannotBeLessThanErr, FieldCaption(Amount), 0); "Discount Amount" := Round(MaxServiceAmount - Amount, Currency."Amount Rounding Precision"); if MaxServiceAmount <> 0 then "Discount %" := Round(100 - (Amount / MaxServiceAmount * 100), 0.00001); end else begin Amount := Price * ServiceObject.Quantity; if not "Usage Based Billing" then Amount := Round(Amount, Currency."Amount Rounding Precision"); if CalledByFieldNo = FieldNo("Discount %") then begin "Discount Amount" := Amount * "Discount %" / 100; if not "Usage Based Billing" then "Discount Amount" := Round("Discount Amount", Currency."Amount Rounding Precision"); end; if CalledByFieldNo = FieldNo("Discount Amount") then 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"; if Amount > MaxServiceAmount then Error(CannotBeGreaterThanErr, FieldCaption(Amount), Format(MaxServiceAmount)); end; SetLCYFields(false); OnAfterCalculateServiceAmount(Rec, CalledByFieldNo); end; internal procedure SetUpdateRequiredOnBillingLines() var BillingLine: Record "Billing Line"; begin BillingLine.SetRange("Subscription Header No.", "Subscription Header No."); BillingLine.SetRange("Subscription Line Entry No.", "Entry No."); if BillingLine.FindSet() then repeat BillingLine.Validate("Update Required", true); BillingLine.Modify(false); until BillingLine.Next() = 0; end; internal procedure UpdateNextBillingDate(LastBillingToDate: Date) var NewNextBillingDate: Date; OriginalInvoicedToDate: Date; begin if ("Subscription Line End Date" >= LastBillingToDate) or ("Subscription Line End Date" = 0D) then NewNextBillingDate := CalcDate('<+1D>', LastBillingToDate) else NewNextBillingDate := CalcDate('<+1D>', "Subscription Line End Date"); "Next Billing Date" := NewNextBillingDate; OriginalInvoicedToDate := GetOriginalInvoicedToDateIfRebillingMetadataExist(); if OriginalInvoicedToDate <> 0D then "Next Billing Date" := OriginalInvoicedToDate; OnAfterUpdateNextBillingDate(Rec, LastBillingToDate); end; local procedure UpdateCustomerContractLineServiceCommitmentDescription() var CustomerContractLine: Record "Cust. Sub. Contract Line"; begin if Description = xRec.Description then exit; CustomerContractLine.SetRange("Subscription Header No.", Rec."Subscription Header No."); CustomerContractLine.SetRange("Subscription Line Entry No.", Rec."Entry No."); CustomerContractLine.FilterOnServiceObjectContractLineType(); CustomerContractLine.ModifyAll("Subscription Line Description", Rec.Description, false); end; internal procedure RecalculateAmountsFromCurrencyData() var Currency2: Record Currency; begin if ((Rec."Currency Factor" = 0) and (Rec."Currency Code" = '')) then exit; Currency2.Initialize("Currency Code"); Rec.Validate("Calculation Base Amount", Round(CurrExchRate.ExchangeAmtLCYToFCY("Currency Factor Date", "Currency Code", "Calculation Base Amount (LCY)", "Currency Factor"), Currency2."Unit-Amount Rounding Precision")); end; internal procedure ResetAmountsAndCurrencyFromLCY() begin Rec.Price := Rec."Price (LCY)"; Rec.Amount := Rec."Amount (LCY)"; Rec."Discount Amount" := Rec."Discount Amount (LCY)"; Rec."Calculation Base Amount" := Rec."Calculation Base Amount (LCY)"; Rec."Unit Cost" := Rec."Unit Cost (LCY)"; Rec.SetCurrencyData(0, 0D, ''); end; internal procedure SetLCYFields(IncludeUnitCost: Boolean) begin if "Currency Code" = '' then begin Rec."Price (LCY)" := Rec.Price; Rec."Amount (LCY)" := Rec.Amount; Rec."Discount Amount (LCY)" := Rec."Discount Amount"; Rec."Calculation Base Amount (LCY)" := Rec."Calculation Base Amount"; if IncludeUnitCost then Rec."Unit Cost (LCY)" := Rec."Unit Cost"; end else begin Currency.Initialize(Rec."Currency Code"); Currency.TestField("Unit-Amount Rounding Precision"); Currency.TestField("Amount Rounding Precision"); Rec."Price (LCY)" := Round(CurrExchRate.ExchangeAmtFCYToLCY("Currency Factor Date", "Currency Code", Rec.Price, "Currency Factor"), Currency."Unit-Amount Rounding Precision"); Rec."Amount (LCY)" := Round(CurrExchRate.ExchangeAmtFCYToLCY("Currency Factor Date", "Currency Code", Rec.Amount, "Currency Factor"), Currency."Amount Rounding Precision"); Rec."Discount Amount (LCY)" := Round(CurrExchRate.ExchangeAmtFCYToLCY("Currency Factor Date", "Currency Code", Rec."Discount Amount", "Currency Factor"), Currency."Amount Rounding Precision"); Rec."Calculation Base Amount (LCY)" := Round(CurrExchRate.ExchangeAmtFCYToLCY("Currency Factor Date", "Currency Code", Rec."Calculation Base Amount", "Currency Factor"), Currency."Unit-Amount Rounding Precision"); if IncludeUnitCost then Rec."Unit Cost (LCY)" := Round(CurrExchRate.ExchangeAmtFCYToLCY("Currency Factor Date", "Currency Code", Rec."Unit Cost", "Currency Factor"), Currency."Unit-Amount Rounding Precision"); end; end; local procedure IsInitialTermEmpty(): Boolean begin exit(Format("Initial Term") = ''); end; local procedure IsExtensionTermEmpty(): Boolean begin exit(Format("Extension Term") = ''); end; internal procedure IsNoticePeriodEmpty(): Boolean begin exit(Format("Notice Period") = ''); end; internal procedure UpdateServiceCommitment(CalledByFieldNo: Integer) var ServiceObject: Record "Subscription Header"; ServiceCommitment: Record "Subscription Line"; MultipleServiceCommitmentsUpdatedMsg: Label 'There are multiple Subscription Lines in the Subscription %1. The quantity was changed for all Subscription Lines.', Comment = '%1 = Object number'; begin case CalledByFieldNo of FieldNo(Quantity): begin ServiceObject.Get(Rec."Subscription Header No."); ServiceObject.Validate(Quantity, Quantity); ServiceObject.Modify(true); ServiceCommitment.SetRange("Subscription Header No.", Rec."Subscription Header No."); if ServiceCommitment.Count > 1 then Message(MultipleServiceCommitmentsUpdatedMsg, Rec."Subscription Header No."); end; FieldNo("Variant Code"): begin ServiceObject.Get(Rec."Subscription Header No."); ServiceObject.Validate("Variant Code", "Variant Code"); ServiceObject.Modify(true); end; else begin case CalledByFieldNo of FieldNo("Invoicing Item No."): Validate("Invoicing Item No.", "Invoicing Item No."); FieldNo("Subscription Line Start Date"): Validate("Subscription Line Start Date", "Subscription Line Start Date"); FieldNo("Subscription Line End Date"): Validate("Subscription Line End Date", "Subscription Line End Date"); FieldNo(Quantity): Validate(Quantity, Quantity); FieldNo("Discount %"): Validate("Discount %", "Discount %"); FieldNo("Discount Amount"): Validate("Discount Amount", "Discount Amount"); FieldNo(Amount): Validate(Amount, Amount); FieldNo("Calculation Base Amount"): Validate("Calculation Base Amount", "Calculation Base Amount"); FieldNo("Calculation Base %"): Validate("Calculation Base %", "Calculation Base %"); FieldNo("Billing Base Period"): Validate("Billing Base Period", "Billing Base Period"); FieldNo("Billing Rhythm"): Validate("Billing Rhythm", "Billing Rhythm"); FieldNo("Cancellation Possible Until"): Validate("Cancellation Possible Until", "Cancellation Possible Until"); FieldNo("Term Until"): Validate("Term Until", "Term Until"); FieldNo(Discount): Validate(Discount); FieldNo("Currency Code"): Validate("Currency Code", "Currency Code"); FieldNo("Exclude from Price Update"): Validate("Exclude from Price Update", "Exclude from Price Update"); FieldNo("Next Price Update"): Validate("Next Price Update", "Next Price Update"); FieldNo("Period Calculation"): Validate("Period Calculation", "Period Calculation"); FieldNo("Notice Period"): Validate("Notice Period", "Notice Period"); FieldNo("Price Binding Period"): Validate("Period Calculation", "Period Calculation"); FieldNo("Unit Cost (LCY)"): Validate("Unit Cost (LCY)", "Unit Cost (LCY)"); FieldNo("Create Contract Deferrals"): Validate("Create Contract Deferrals", "Create Contract Deferrals"); FieldNo("Shortcut Dimension 1 Code"): Validate("Shortcut Dimension 1 Code", "Shortcut Dimension 1 Code"); FieldNo("Shortcut Dimension 2 Code"): Validate("Shortcut Dimension 2 Code", "Shortcut Dimension 2 Code"); end; Modify(true); end; end; OnAfterUpdateSubscriptionLine(Rec); end; internal procedure EditDimensionSet() var OldDimSetID: Integer; begin OnBeforeValidateDimensionSetID(Rec, xRec); OldDimSetID := "Dimension Set ID"; "Dimension Set ID" := DimMgt.EditDimensionSet( "Dimension Set ID", "Subscription Header No." + ' ' + FieldCaption("Entry No.") + ' ' + Format("Entry No."), "Shortcut Dimension 1 Code", "Shortcut Dimension 2 Code"); if OldDimSetID <> "Dimension Set ID" then begin Modify(); UpdateRelatedVendorServiceCommDimensions(OldDimSetID, "Dimension Set ID"); end; OnAfterValidateDimensionSetID(Rec, xRec); end; local procedure ValidateShortcutDimCode(FieldNumber: Integer; var ShortcutDimCode: Code[20]) var OldDimSetID: Integer; begin OnBeforeValidateShortcutDimCode(Rec, xRec, FieldNumber, ShortcutDimCode); OldDimSetID := "Dimension Set ID"; DimMgt.ValidateShortcutDimValues(FieldNumber, ShortcutDimCode, "Dimension Set ID"); if OldDimSetID <> "Dimension Set ID" then begin Modify(); UpdateRelatedVendorServiceCommDimensions(OldDimSetID, "Dimension Set ID"); end; OnAfterValidateShortcutDimCode(Rec, xRec, FieldNumber, ShortcutDimCode); end; internal procedure SetDefaultDimensions(UseSource: Boolean) var DefaultDimSource: List of [Dictionary of [Integer, Code[20]]]; begin AddDefaultDimensionSources(DefaultDimSource, UseSource); "Dimension Set ID" := DimMgt.GetDefaultDimID(DefaultDimSource, '', "Shortcut Dimension 1 Code", "Shortcut Dimension 2 Code", 0, 0); DimMgt.UpdateGlobalDimFromDimSetID("Dimension Set ID", "Shortcut Dimension 1 Code", "Shortcut Dimension 2 Code"); end; internal procedure ApplyContractDimensions(ContractDimSetID: Integer; SourceCode: Code[10]; ContractPartnerTableID: Integer) var DefaultDimSource: List of [Dictionary of [Integer, Code[20]]]; HighPriorityDimSource: List of [Dictionary of [Integer, Code[20]]]; DimSetIDArr: array[10] of Integer; TempDimValue1: Code[20]; TempDimValue2: Code[20]; begin DimSetIDArr[1] := "Dimension Set ID"; DimSetIDArr[2] := ContractDimSetID; AddDefaultDimensionSources(DefaultDimSource, true); if DimMgt.GetTableIDsForHigherPriorities(DefaultDimSource, HighPriorityDimSource, SourceCode, ContractPartnerTableID) then DimSetIDArr[3] := DimMgt.GetRecDefaultDimID( Rec, CurrFieldNo, HighPriorityDimSource, SourceCode, TempDimValue1, TempDimValue2, 0, 0); "Dimension Set ID" := DimMgt.GetCombinedDimensionSetID(DimSetIDArr, "Shortcut Dimension 1 Code", "Shortcut Dimension 2 Code"); OnAfterGetCombinedDimensionSetID(Rec); end; local procedure AddDefaultDimensionSources(var DefaultDimSource: List of [Dictionary of [Integer, Code[20]]]; UseSource: Boolean) var ServiceObject: Record "Subscription Header"; begin if Rec."Invoicing Item No." <> '' then DimMgt.AddDimSource(DefaultDimSource, Database::Item, Rec."Invoicing Item No."); if UseSource then if ServiceObject.Get("Subscription Header No.") then case ServiceObject.Type of ServiceObject.Type::Item: DimMgt.AddDimSource(DefaultDimSource, Database::Item, ServiceObject."Source No."); ServiceObject.Type::"G/L Account": DimMgt.AddDimSource(DefaultDimSource, Database::"G/L Account", ServiceObject."Source No."); end; end; internal procedure GetCombinedDimensionSetID(DimSetID1: Integer; DimSetID2: Integer) var DimSetIDArr: array[10] of Integer; begin DimSetIDArr[1] := DimSetID1; DimSetIDArr[2] := DimSetID2; "Dimension Set ID" := DimMgt.GetCombinedDimensionSetID(DimSetIDArr, "Shortcut Dimension 1 Code", "Shortcut Dimension 2 Code"); OnAfterGetCombinedDimensionSetID(Rec); end; internal procedure UpdateRelatedVendorServiceCommDimensions(OldDimSetID: Integer; NewDimSetID: Integer) var VendorServiceCommitment: Record "Subscription Line"; begin if Rec."Subscription Contract No." = '' then exit; if OldDimSetID = NewDimSetID then exit; VendorServiceCommitment.FilterOnServiceObjectAndPackage(Rec."Subscription Header No.", Rec.Template, Rec."Subscription Package Code", Enum::"Service Partner"::Vendor); if VendorServiceCommitment.FindSet() then repeat VendorServiceCommitment."Dimension Set ID" := DimMgt.GetDeltaDimSetID(VendorServiceCommitment."Dimension Set ID", NewDimSetID, OldDimSetID); DimMgt.UpdateGlobalDimFromDimSetID( VendorServiceCommitment."Dimension Set ID", VendorServiceCommitment."Shortcut Dimension 1 Code", VendorServiceCommitment."Shortcut Dimension 2 Code"); VendorServiceCommitment.Modify(false); until VendorServiceCommitment.Next() = 0; end; internal procedure FilterOnServiceObjectAndTemplate(ServiceObjectNo: Code[20]; ServiceTemplate: Code[20]; ServicePartner: Enum "Service Partner") begin Rec.SetRange(Partner, ServicePartner); Rec.SetRange("Subscription Header No.", ServiceObjectNo); Rec.SetRange(Template, ServiceTemplate); end; local procedure DeleteContractLine() var CustomerContractLine: Record "Cust. Sub. Contract Line"; VendorContractLine: Record "Vend. Sub. Contract Line"; begin if not (Rec."Invoicing via" = Rec."Invoicing via"::Contract) then exit; case Partner of Enum::"Service Partner"::Customer: if CustomerContractLine.Get(Rec."Subscription Contract No.", Rec."Subscription Contract Line No.") then if CustomerContractLine.Closed then CustomerContractLine.Delete(true); Enum::"Service Partner"::Vendor: if VendorContractLine.Get(Rec."Subscription Contract No.", Rec."Subscription Contract Line No.") then if VendorContractLine.Closed then VendorContractLine.Delete(true); end; end; internal procedure FilterOnServiceObjectAndPackage(ServiceObjectNo: Code[20]; ServiceTemplate: Code[20]; PackageCode: Code[20]; ServicePartner: Enum "Service Partner") begin Rec.FilterOnServiceObjectAndTemplate(ServiceObjectNo, ServiceTemplate, ServicePartner); Rec.SetRange("Subscription Package Code", PackageCode); end; internal procedure NewLineForServiceObject() var ServiceContractSetup: Record "Subscription Contract Setup"; ServiceObject: Record "Subscription Header"; ServiceCommitment: Record "Subscription Line"; begin ServiceContractSetup.CheckPrerequisitesForCreatingManualContractLine(); ServiceObject.Get(Rec.GetFilter("Subscription Header No.")); ServiceObject.TestField("Source No."); ServiceCommitment.Init(); ServiceCommitment."Entry No." := 0; ServiceCommitment."Subscription Header No." := ServiceObject."No."; ServiceCommitment.Description := ServiceObject.Description; ServiceCommitment."Invoicing via" := ServiceCommitment."Invoicing via"::Contract; ServiceCommitment.Partner := ServiceCommitment.Partner::Customer; ServiceCommitment."Customer Price Group" := ServiceObject."Customer Price Group"; ServiceCommitment.Validate("Subscription Line Start Date", WorkDate()); ServiceCommitment."Billing Base Period" := ServiceContractSetup."Default Billing Base Period"; ServiceCommitment."Billing Rhythm" := ServiceContractSetup."Default Billing Rhythm"; ServiceCommitment."Period Calculation" := ServiceContractSetup."Default Period Calculation"; ServiceCommitment.SetDefaultDimensions(true); ServiceCommitment.Insert(false); end; internal procedure InitForServiceObject(ServiceObject: Record "Subscription Header"; ServicePartner: enum "Service Partner") var ServiceContractSetup: Record "Subscription Contract Setup"; ItemManagement: Codeunit "Sub. Contracts Item Management"; begin ServiceContractSetup.CheckPrerequisitesForCreatingManualContractLine(); Init(); "Subscription Header No." := ServiceObject."No."; "Entry No." := 0; Description := ServiceObject.Description; "Invoicing via" := "Invoicing via"::Contract; Partner := ServicePartner; "Customer Price Group" := ServiceObject."Customer Price Group"; Validate("Subscription Line Start Date", ServiceObject."Provision Start Date"); "Billing Base Period" := ServiceContractSetup."Default Billing Base Period"; "Billing Rhythm" := ServiceContractSetup."Default Billing Rhythm"; "Renewal Term" := "Initial Term"; "Period Calculation" := ServiceContractSetup."Default Period Calculation"; if ServiceObject.IsItem() then if ItemManagement.IsServiceCommitmentItem(ServiceObject."Source No.") then "Invoicing Item No." := ServiceObject."Source No."; SetDefaultDimensions(true); end; internal procedure UpdateFromCustomerContract(CustomerContract: Record "Customer Subscription Contract") var SourceCodeSetup: Record "Source Code Setup"; begin "Currency Code" := CustomerContract."Currency Code"; InitCurrencyData(); SourceCodeSetup.Get(); ApplyContractDimensions(CustomerContract."Dimension Set ID", SourceCodeSetup.Sales, Database::Customer); "Exclude from Price Update" := CustomerContract.DefaultExcludeFromPriceUpdate; end; internal procedure UpdateFromVendorContract(VendorContract: Record "Vendor Subscription Contract") var SourceCodeSetup: Record "Source Code Setup"; begin "Currency Code" := VendorContract."Currency Code"; InitCurrencyData(); SourceCodeSetup.Get(); ApplyContractDimensions(VendorContract."Dimension Set ID", SourceCodeSetup.Purchases, Database::Vendor); "Exclude from Price Update" := VendorContract.DefaultExcludeFromPriceUpdate; end; internal procedure CopyFromSalesServiceCommitment(SalesServiceCommitment: Record "Sales Subscription Line") begin Rec."Subscription Package Code" := SalesServiceCommitment."Subscription Package Code"; Rec.Template := SalesServiceCommitment.Template; Rec.Partner := SalesServiceCommitment.Partner; Rec.Description := SalesServiceCommitment.Description; Rec."Invoicing via" := SalesServiceCommitment."Invoicing via"; Rec."Invoicing Item No." := SalesServiceCommitment."Item No."; Rec."Price" := SalesServiceCommitment."Price"; Rec.Amount := SalesServiceCommitment.Amount; Rec."Calculation Base Amount" := SalesServiceCommitment."Calculation Base Amount"; Rec."Calculation Base %" := SalesServiceCommitment."Calculation Base %"; Rec."Unit Cost" := SalesServiceCommitment."Unit Cost"; Rec."Discount %" := SalesServiceCommitment."Discount %"; Rec."Discount Amount" := SalesServiceCommitment."Discount Amount"; Rec."Billing Base Period" := SalesServiceCommitment."Billing Base Period"; Rec."Billing Rhythm" := SalesServiceCommitment."Billing Rhythm"; Rec."Initial Term" := SalesServiceCommitment."Initial Term"; Rec."Extension Term" := SalesServiceCommitment."Extension Term"; Rec."Notice Period" := SalesServiceCommitment."Notice Period"; Rec.Discount := SalesServiceCommitment.Discount; Rec."Price Binding Period" := SalesServiceCommitment."Price Binding Period"; Rec."Period Calculation" := SalesServiceCommitment."Period Calculation"; Rec."Usage Based Billing" := SalesServiceCommitment."Usage Based Billing"; Rec."Usage Based Pricing" := SalesServiceCommitment."Usage Based Pricing"; Rec."Pricing Unit Cost Surcharge %" := SalesServiceCommitment."Pricing Unit Cost Surcharge %"; Rec."Create Contract Deferrals" := SalesServiceCommitment."Create Contract Deferrals"; OnAfterCopyFromSalesSubscriptionLine(Rec, SalesServiceCommitment); end; local procedure UpdateCurrencyFactorAndRecalculateAmountsFromExchRate() var UpdateCurrencyExchangeRates: Codeunit "Update Currency Exchange Rates"; begin if "Currency Code" <> '' then begin if UpdateCurrencyExchangeRates.ExchangeRatesForCurrencyExist("Currency Factor Date", "Currency Code") then begin // note: Currency Factor will be filled from OpenExchangeSelectionPage if "Currency Code" <> xRec."Currency Code" then RecalculateAmountsFromCurrencyData(); end else UpdateCurrencyExchangeRates.ShowMissingExchangeRatesNotification("Currency Code"); end else begin "Currency Factor" := 0; "Currency Factor Date" := 0D; if "Currency Code" <> xRec."Currency Code" then RecalculateAmountsFromCurrencyData(); end; end; local procedure RecalculateHarmonizedBillingFieldsOnCustomerContract() var CustomerContract: Record "Customer Subscription Contract"; begin if Rec.IsPartnerVendor() then exit; if "Subscription Contract No." = '' then exit; CustomerContract.Get(Rec."Subscription Contract No."); CustomerContract.RecalculateHarmonizedBillingFieldsBasedOnNextBillingDate(0); end; internal procedure TestServiceCommitmentsCurrencyCode(var ServiceCommitment: Record "Subscription Line" temporary) var PreviousCurrencyCode: Code[10]; begin if ServiceCommitment.FindSet() then begin PreviousCurrencyCode := ServiceCommitment."Currency Code"; repeat if ServiceCommitment."Currency Code" <> PreviousCurrencyCode then Error(DifferentCurrenciesInSerCommitmentErr); PreviousCurrencyCode := ServiceCommitment."Currency Code" until ServiceCommitment.Next() = 0; end; end; procedure OpenExchangeSelectionPage(var NewCurrencyFactorDate: Date; var NewCurrencyFactor: Decimal; CurrencyCode: Code[10]; NewMessageTxt: Text; CalledFromServiceObject: Boolean): Boolean var ExchangeRateSelectionPage: Page "Exchange Rate Selection"; begin if not GuiAllowed then exit(false); Commit(); // Save changes before opening the page ExchangeRateSelectionPage.SetData(WorkDate(), CurrencyCode, NewMessageTxt); ExchangeRateSelectionPage.SetIsCalledFromServiceObject(CalledFromServiceObject); if ExchangeRateSelectionPage.RunModal() = Action::Ok then begin ExchangeRateSelectionPage.GetData(NewCurrencyFactorDate, NewCurrencyFactor); if NewCurrencyFactor = 0 then Error(ZeroExchangeRateErr); exit(true); end; end; internal procedure ResetServiceCommitmentCurrencyLCYFromContract(PartnerType: Enum "Service Partner"; ContractNo: Code[20]) var ServiceCommitment: Record "Subscription Line"; begin ServiceCommitment.FilterOnContract(PartnerType, ContractNo); if ServiceCommitment.FindSet() then repeat #pragma warning disable AA0214 ServiceCommitment.ResetAmountsAndCurrencyFromLCY(); ServiceCommitment.SetUpdateRequiredOnBillingLines(); ServiceCommitment.ArchiveServiceCommitment(); ServiceCommitment.Modify(false); #pragma warning restore AA0214 until ServiceCommitment.Next() = 0; end; internal procedure UpdateAndRecalculateServCommCurrencyFromContract(PartnerType: Enum "Service Partner"; ContractNo: Code[20]; CurrencyFactor: Decimal; CurrencyFactorDate: Date; CurrencyCode: Code[10]) var ServiceCommitment: Record "Subscription Line"; begin ServiceCommitment.FilterOnContract(PartnerType, ContractNo); UpdateCurrencyDataOnServiceCommitments(ServiceCommitment, CurrencyFactor, CurrencyFactorDate, CurrencyCode, true); end; internal procedure UpdateCurrencyDataOnServiceCommitments(var ServiceCommitment: Record "Subscription Line"; CurrencyFactor: Decimal; CurrencyFactorDate: Date; CurrencyCode: Code[10]; UpdateCurrencyCodeOnServiceCommitment: Boolean) begin if ServiceCommitment.FindSet() then repeat if not UpdateCurrencyCodeOnServiceCommitment then CurrencyCode := ServiceCommitment."Currency Code"; ServiceCommitment.SetCurrencyData(CurrencyFactor, CurrencyFactorDate, CurrencyCode); ServiceCommitment.RecalculateAmountsFromCurrencyData(); ServiceCommitment.SetUpdateRequiredOnBillingLines(); ServiceCommitment.ArchiveServiceCommitment(); ServiceCommitment.Modify(false); until ServiceCommitment.Next() = 0; end; internal procedure FilterOnContract(PartnerType: Enum "Service Partner"; ContractNo: Code[20]) begin Rec.SetRange(Partner, PartnerType); Rec.SetRange("Subscription Contract No.", ContractNo); end; local procedure InitCurrencyData() var Currency2: Record Currency; begin if Rec."Currency Code" = '' then begin "Currency Factor" := 0; "Currency Factor Date" := 0D; end else begin Currency2.Get("Currency Code"); if "Currency Factor Date" = 0D then "Currency Factor Date" := WorkDate(); if (Rec."Currency Factor Date" <> xRec."Currency Factor Date") or (Rec."Currency Code" <> xRec."Currency Code") then "Currency Factor" := CurrExchRate.ExchangeRate("Currency Factor Date", "Currency Code"); end end; internal procedure SetCurrencyData(CurrencyFactor: Decimal; CurrencyFactorDate: Date; CurrencyCode: Code[10]) begin Rec."Currency Factor" := CurrencyFactor; Rec."Currency Factor Date" := CurrencyFactorDate; Rec."Currency Code" := CurrencyCode; end; internal procedure EnsureCalculationBaseAmountExcludesVAT(SalesLine: Record "Sales Line"; SalesHeader: Record "Sales Header") var LocalCurrency: Record Currency; NetBaseAmount: Decimal; begin if not SalesHeader."Prices Including VAT" then exit; if SalesLine."VAT Calculation Type" = SalesLine."VAT Calculation Type"::"Full VAT" then exit; if SalesLine.GetVATPct() = 0 then exit; LocalCurrency.Initialize(SalesHeader."Currency Code"); NetBaseAmount := Round( Rec."Calculation Base Amount" / (1 + SalesLine.GetVATPct() / 100), LocalCurrency."Unit-Amount Rounding Precision"); Rec.Validate("Calculation Base Amount", NetBaseAmount); end; internal procedure ErrorIfBillingLineForServiceCommitmentExist() var BillingLine: Record "Billing Line"; begin BillingLine.FilterBillingLineOnServiceCommitment(Rec."Entry No."); if not BillingLine.IsEmpty() then Error(BillingLineForServiceCommitmentExistErr); end; /// /// Checks whether the Subscription Line Start Date may still be changed. /// The change is allowed as long as no billing has taken place for the Subscription Line, or as long as the /// Next Billing Date is still on the Subscription Line Start Date, which is the state after a cancellation /// or a credit memo and the case in which the billing period legitimately has to be corrected. /// local procedure CheckSubscriptionLineStartDateChangeAllowed() begin CheckBillingStateAllowsSubscriptionLineStartDateChange(); OnAfterCheckSubscriptionLineStartDateChangeAllowed(Rec); end; local procedure CheckBillingStateAllowsSubscriptionLineStartDateChange() var SubscriptionLine: Record "Subscription Line"; begin if Rec.IsTemporary() then exit; SubscriptionLine.SetLoadFields("Subscription Line Start Date", "Next Billing Date"); if not SubscriptionLine.Get(Rec."Entry No.") then exit; if SubscriptionLine."Next Billing Date" = SubscriptionLine."Subscription Line Start Date" then exit; ErrorIfBillingLineArchiveForServiceCommitmentExist(); ErrorIfBillingLineForServiceCommitmentExist(); end; internal procedure GetPartnerNoFromContract(): Code[20] var CustomerContract: Record "Customer Subscription Contract"; VendorContract: Record "Vendor Subscription Contract"; begin case Rec.Partner of Rec.Partner::Customer: begin CustomerContract.Get(Rec."Subscription Contract No."); exit(CustomerContract."Sell-to Customer No."); end; Rec.Partner::Vendor: begin VendorContract.Get(Rec."Subscription Contract No."); exit(VendorContract."Buy-from Vendor No."); end; end; end; internal procedure ErrorIfBillingLineArchiveForServiceCommitmentExist() var BillingLineArchive: Record "Billing Line Archive"; begin BillingLineArchive.FilterBillingLineArchiveOnServiceCommitment(Rec."Entry No."); if not BillingLineArchive.IsEmpty() then Error(BillingLineArchiveForServiceCommitmentExistErr); end; internal procedure ArchiveServiceCommitment() begin ArchiveServiceCommitment(0D, "Type Of Price Update"::None); end; internal procedure ArchiveServiceCommitment(PerformUpdateOn: Date; TypeOfPriceUpdate: Enum "Type Of Price Update") var ServiceCommitmentArchive: Record "Subscription Line Archive"; begin if SkipArchiving then exit; xRec.Get(Rec."Entry No."); //Modify trigger has changed value in xRec. if (xRec."Calculation Base %" <> Rec."Calculation Base %") or (xRec."Calculation Base Amount" <> Rec."Calculation Base Amount") or (xRec.Price <> Rec.Price) or (xRec."Discount %" <> Rec."Discount %") or (xRec."Discount Amount" <> Rec."Discount Amount") or (xRec.Amount <> Rec.Amount) or (xRec."Billing Base Period" <> Rec."Billing Base Period") or (xRec."Billing Rhythm" <> Rec."Billing Rhythm") or (xRec."Unit Cost" <> Rec."Unit Cost") or (xRec."Unit Cost (LCY)" <> Rec."Unit Cost (LCY)") then CreateServiceCommitmentArchive(ServiceCommitmentArchive, xRec, PerformUpdateOn, TypeOfPriceUpdate); end; internal procedure ArchiveServiceCommitmentFromServiceObject(xServiceObject: Record "Subscription Header"; ServiceObject: Record "Subscription Header") var ServiceCommitmentArchive: Record "Subscription Line Archive"; begin if (xServiceObject.Quantity <> ServiceObject.Quantity) or (xServiceObject."Serial No." <> ServiceObject."Serial No.") or (xServiceObject."Variant Code" <> ServiceObject."Variant Code") then begin CreateServiceCommitmentArchive(ServiceCommitmentArchive, Rec, 0D, "Type Of Price Update"::None); ServiceCommitmentArchive."Quantity (Sub. Header)" := xServiceObject.Quantity; ServiceCommitmentArchive."Serial No. (Sub. Header)" := xServiceObject."Serial No."; ServiceCommitmentArchive.Modify(false); end; end; internal procedure CreateServiceCommitmentArchive(var ServiceCommitmentArchive: Record "Subscription Line Archive"; xServiceCommitment: Record "Subscription Line"; PerformUpdateOn: Date; TypeOfPriceUpdate: Enum "Type Of Price Update") begin if FindServiceCommitmentArchiveCreatedInLessThanMinute(ServiceCommitmentArchive) then begin ServiceCommitmentArchive.CopyFromServiceCommitment(xServiceCommitment); ServiceCommitmentArchive."Perform Update On" := PerformUpdateOn; ServiceCommitmentArchive."Type Of Update" := TypeOfPriceUpdate; ServiceCommitmentArchive.Modify(false); end else begin ServiceCommitmentArchive.Init(); ServiceCommitmentArchive.CopyFromServiceCommitment(xServiceCommitment); ServiceCommitmentArchive."Perform Update On" := PerformUpdateOn; ServiceCommitmentArchive."Type Of Update" := TypeOfPriceUpdate; ServiceCommitmentArchive."Entry No." := 0; ServiceCommitmentArchive.Insert(true); end; end; local procedure FindServiceCommitmentArchiveCreatedInLessThanMinute(var ServiceCommitmentArchive: Record "Subscription Line Archive"): Boolean begin ServiceCommitmentArchive.FilterOnServiceCommitment(Rec."Entry No."); ServiceCommitmentArchive.SetRange(SystemModifiedAt, CurrentDateTime() - 60000, CurrentDateTime()); exit(ServiceCommitmentArchive.FindLast()); end; internal procedure IsPartnerCustomer(): Boolean begin exit(Rec.Partner = Rec.Partner::Customer); end; internal procedure IsPartnerVendor(): Boolean begin exit(Rec.Partner = Rec.Partner::Vendor); end; internal procedure CalculateCalculationBaseAmount() var ServiceObject: Record "Subscription Header"; TempSalesHeader: Record "Sales Header" temporary; TempSalesLine: Record "Sales Line" temporary; ContractsItemManagement: Codeunit "Sub. Contracts Item Management"; begin ServiceObject.Get(Rec."Subscription Header No."); case Rec.Partner of "Service Partner"::Customer: begin ContractsItemManagement.CreateTempSalesHeader(TempSalesHeader, TempSalesHeader."Document Type"::Order, ServiceObject."End-User Customer No.", ServiceObject."Bill-to Customer No.", Rec."Subscription Line Start Date", Rec."Currency Code"); ContractsItemManagement.CreateTempSalesLine(TempSalesLine, TempSalesHeader, ServiceObject, Rec."Subscription Line Start Date"); Rec."Calculation Base Amount" := ContractsItemManagement.CalculateUnitPrice(TempSalesHeader, TempSalesLine); end; "Service Partner"::Vendor: if ServiceObject.IsItem() then Rec."Calculation Base Amount" := ContractsItemManagement.CalculateUnitCost(ServiceObject."Source No."); end; end; internal procedure RefreshRenewalTerm() var BlankDateFormula: DateFormula; begin if Rec."Subscription Line End Date" = 0D then Rec.Validate("Renewal Term", BlankDateFormula) else if Rec."Renewal Term" = BlankDateFormula then Rec.Validate("Renewal Term", "Initial Term"); end; local procedure ErrorIfPlannedServiceCommitmentExists() var PlannedServiceCommitmentExistsErr: Label 'The Subscription Line End Date cannot be changed as long as there is a Planned Subscription Line.'; begin if not Rec."Planned Sub. Line exists" then Rec.CalcFields("Planned Sub. Line exists"); if "Planned Sub. Line exists" then Error(PlannedServiceCommitmentExistsErr); end; internal procedure IsFullyInvoiced(): Boolean begin if Rec.BillingLineExists() then exit(false); if (Rec."Next Billing Date" = 0D) then exit(false); exit(Rec."Subscription Line End Date" = CalcDate('<-1D>', Rec."Next Billing Date")); end; internal procedure IsBilledUntilEndOfTerm(): Boolean begin if Rec."Subscription Line End Date" = 0D then exit(false); exit(Rec."Next Billing Date" > Rec."Subscription Line End Date"); end; internal procedure SetSkipArchiving(NewSkipArchiving: Boolean) begin SkipArchiving := NewSkipArchiving; end; local procedure DisconnectBillingLineArchive() var BillingLineArchive: Record "Billing Line Archive"; begin BillingLineArchive.FilterBillingLineArchiveOnServiceCommitment(Rec."Entry No."); if BillingLineArchive.FindSet(true) then repeat BillingLineArchive."Subscription Header No." := ''; BillingLineArchive."Subscription Line Entry No." := 0; BillingLineArchive.Modify(false); until BillingLineArchive.Next() = 0; end; internal procedure UpdateNextPriceUpdate() begin if Format(Rec."Price Binding Period") = '' then exit; Rec."Next Price Update" := CalcDate(Rec."Price Binding Period", Rec."Subscription Line Start Date"); end; internal procedure SetSkipTestPackageCode(NewSkipTestPackageCode: Boolean) begin SkipTestPackageCode := NewSkipTestPackageCode; end; internal procedure ModifyExcludeFromPriceUpdateInAllRelatedServiceCommitments(ServicePartner: Enum "Service Partner"; ContractNo: Code[20]; NewExcludeFromPriceUpdate: Boolean) var ServiceCommitment: Record "Subscription Line"; ConfirmManagement: Codeunit "Confirm Management"; begin if not ConfirmManagement.GetResponse(GetChangeExcludeFromPriceUpdateQst(NewExcludeFromPriceUpdate), true) then exit; ServiceCommitment.FilterOnContract(ServicePartner, ContractNo); ServiceCommitment.ModifyAll("Exclude from Price Update", NewExcludeFromPriceUpdate, false); end; local procedure GetChangeExcludeFromPriceUpdateQst(NewExcludeFromPriceUpdate: Boolean): Text var ChangeExcludeFromPriceUpdateToYesQst: Label 'Do you want to include all contract lines in potential price updates? Click Yes to allow price updates for all contracts lines. Click No to keep the value in the contract lines.'; ChangeExcludeFromPriceUpdateToNoQst: Label 'Do you want to exclude all contract lines from potential price updates? Click Yes to ban price updates for all contracts lines. Click No to keep the value in the contract lines.'; begin if NewExcludeFromPriceUpdate then exit(ChangeExcludeFromPriceUpdateToNoQst) else exit(ChangeExcludeFromPriceUpdateToYesQst); end; internal procedure CalculateUnitCost() var ServiceObject: Record "Subscription Header"; ContractsItemManagement: Codeunit "Sub. Contracts Item Management"; begin case Rec.Partner of Partner::Customer: begin ServiceObject.Get("Subscription Header No."); if ServiceObject.Type = ServiceObject.Type::Item then Rec.Validate("Unit Cost (LCY)", ContractsItemManagement.CalculateUnitCost(ServiceObject."Source No.") * Rec."Calculation Base %" / 100); end; Partner::Vendor: if Rec."Currency Code" <> '' then begin Currency.Initialize(Rec."Currency Code"); Currency.TestField("Unit-Amount Rounding Precision"); Rec.Validate("Unit Cost (LCY)", Round( CurrExchRate.ExchangeAmtFCYToLCY( Rec."Currency Factor Date", Rec."Currency Code", Rec.Price, Rec."Currency Factor"), Currency."Unit-Amount Rounding Precision")); end else Rec.Validate("Unit Cost (LCY)", Rec.Price); end; end; local procedure DeleteContractPriceUpdateLines() var ContractPriceUpdateLine: Record "Sub. Contr. Price Update Line"; begin ContractPriceUpdateLine.FilterOnServiceCommitment(Rec."Entry No."); ContractPriceUpdateLine.DeleteAll(false); end; internal procedure UpdateServiceCommitmentFromContractPriceUpdateLine(ContractPriceUpdateLine: Record "Sub. Contr. Price Update Line") var ServiceCommitmentArchive: Record "Subscription Line Archive"; xServiceCommitment: Record "Subscription Line"; PriceUpdateTemplate: Record "Price Update Template"; begin xServiceCommitment := Rec; Rec.Validate("Calculation Base %", ContractPriceUpdateLine."New Calculation Base %"); Rec.Validate("Calculation Base Amount", ContractPriceUpdateLine."New Calculation Base"); Rec.Validate(Price, ContractPriceUpdateLine."New Price"); Rec.Validate(Amount, ContractPriceUpdateLine."New Amount"); Rec.Validate("Discount %", ContractPriceUpdateLine."Discount %"); Rec."Next Price Update" := ContractPriceUpdateLine."Next Price Update"; if PriceUpdateTemplate.Get(ContractPriceUpdateLine."Price Update Template Code") then Rec."Price Binding Period" := PriceUpdateTemplate."Price Binding Period"; //Archiving has to be skipped in OnModify trigger and called afterward in order to set proper Type of Price Update Rec.SetSkipArchiving(true); Rec.Modify(true); Rec.SetSkipArchiving(false); Rec.CreateServiceCommitmentArchive(ServiceCommitmentArchive, xServiceCommitment, CalcDate('<-1D>', ContractPriceUpdateLine."Perform Update On"), Enum::"Type Of Price Update"::"Price Update"); OnAfterUpdateServiceCommitmentFromContractPriceUpdateLine(Rec, ContractPriceUpdateLine); end; internal procedure ServiceCommitmentArchiveExistsForPeriodExists(var ServiceCommitmentArchive: Record "Subscription Line Archive"; RecurringBillingFrom: Date; RecurringBillingTo: Date): Boolean begin ServiceCommitmentArchive.SetCurrentKey("Entry No."); ServiceCommitmentArchive.SetAscending("Entry No.", true); ServiceCommitmentArchive.SetRange("Subscription Header No.", Rec."Subscription Header No."); ServiceCommitmentArchive.SetRange("Original Entry No.", Rec."Entry No."); ServiceCommitmentArchive.SetRange("Perform Update On", RecurringBillingFrom, RecurringBillingTo); ServiceCommitmentArchive.SetRange("Type Of Update", Enum::"Type Of Price Update"::"Price Update"); exit(ServiceCommitmentArchive.FindLast()); end; internal procedure UpdateServiceCommitmentFromServiceCommitmentArchive(ServiceCommitmentArchive: Record "Subscription Line Archive") begin Rec.Price := ServiceCommitmentArchive.Price; Rec."Calculation Base %" := ServiceCommitmentArchive."Calculation Base %"; Rec."Calculation Base Amount" := ServiceCommitmentArchive."Calculation Base Amount"; Rec.Amount := ServiceCommitmentArchive.Amount; Rec."Discount Amount" := ServiceCommitmentArchive."Discount Amount"; Rec."Discount %" := ServiceCommitmentArchive."Discount %"; Rec."Price (LCY)" := ServiceCommitmentArchive."Price (LCY)"; Rec."Calculation Base Amount (LCY)" := ServiceCommitmentArchive."Calculation Base Amount (LCY)"; Rec."Amount (LCY)" := ServiceCommitmentArchive."Amount (LCY)"; Rec."Discount Amount (LCY)" := ServiceCommitmentArchive."Discount Amount (LCY)"; Rec."Next Price Update" := ServiceCommitmentArchive."Next Price Update"; Rec.Closed := ServiceCommitmentArchive.Closed; Rec.Modify(false); end; internal procedure SetPerformUpdateForContractPriceUpdate(var PerformUpdate: Boolean; TypeOfPriceUpdate: Enum "Type Of Price Update"; PerformUpdateOn: Date) begin if TypeOfPriceUpdate <> Enum::"Type Of Price Update"::"Price Update" then exit; PerformUpdate := PerformUpdateOn <= Rec."Next Billing Date"; end; internal procedure UnpostedDocumentExists(): Boolean var BillingLine: Record "Billing Line"; begin BillingLine.FilterBillingLineOnContractLine(Rec.Partner, Rec."Subscription Contract No.", Rec."Subscription Contract Line No."); BillingLine.SetRange("Document Type", "Rec. Billing Document Type"::Invoice, "Rec. Billing Document Type"::"Credit Memo"); exit(not BillingLine.IsEmpty()); end; internal procedure BillingLineExists(): Boolean var BillingLine: Record "Billing Line"; begin BillingLine.FilterBillingLineOnContractLine(Rec.Partner, Rec."Subscription Contract No.", Rec."Subscription Contract Line No."); exit(not BillingLine.IsEmpty()); end; internal procedure DisconnectContractLine(EntryNo: Integer) begin if EntryNo <> 0 then if Get(EntryNo) then begin "Subscription Contract No." := ''; "Subscription Contract Line No." := 0; Modify(false); end; end; internal procedure DeleteOrDisconnectServiceCommitment(EntryNo: Integer) var ServiceObject: Record "Subscription Header"; begin if EntryNo <> 0 then if Get(EntryNo) then if "Created in Contract line" then begin Delete(); DeleteContractPriceUpdateLines(); if ServiceObject.Get("Subscription Header No.") then ServiceObject.DeleteServiceObjectFromContract(); end else DisconnectContractLine(EntryNo); end; internal procedure IsUsageDataBillingFound(var UsageDataBilling: Record "Usage Data Billing"; BillingFromDate: Date; BillingToDate: Date): Boolean begin SetUsageDataBillingFilters(UsageDataBilling, BillingFromDate, BillingToDate); exit(not UsageDataBilling.IsEmpty()); end; internal procedure SetUsageDataBillingFilters(var UsageDataBilling: Record "Usage Data Billing"; BillingFromDate: Date; BillingToDate: Date) begin UsageDataBilling.SetRange("Subscription Line Entry No.", Rec."Entry No."); UsageDataBilling.SetFilter("Usage Base Pricing", '>=%1', Enum::"Usage Based Pricing"::"Usage Quantity"); UsageDataBilling.SetRange("Document Type", "Usage Based Billing Doc. Type"::None); UsageDataBilling.SetFilter("Charge Start Date", '>=%1', BillingFromDate); UsageDataBilling.SetFilter("Charge End Date", '<=%1', CalcDate('<1D>', BillingToDate)); OnAfterSetUsageDataBillingFilters(UsageDataBilling, Rec, BillingFromDate, BillingToDate); end; internal procedure IsUsageBasedBillingValid(): Boolean begin exit(Rec."Usage Based Billing" and (Rec."Subscription Header No." <> '')); end; internal procedure GetUsageDataChargeEndDateCrossingPeriodEnd(BillingFromDate: Date; BillingToDate: Date) ChargeEndDate: Date var UsageDataBilling: Record "Usage Data Billing"; begin if not Rec.IsUsageBasedBillingValid() then exit; UsageDataBilling.SetCurrentKey("Subscription Line Entry No.", "Document Type", "Charge End Date"); UsageDataBilling.SetRange("Subscription Line Entry No.", Rec."Entry No."); UsageDataBilling.SetRange("Usage Base Pricing", Enum::"Usage Based Pricing"::"Usage Quantity", Enum::"Usage Based Pricing"::"Unit Cost Surcharge"); UsageDataBilling.SetRange("Document Type", "Usage Based Billing Doc. Type"::None); UsageDataBilling.SetRange("Charge Start Date", BillingFromDate, BillingToDate); UsageDataBilling.SetFilter("Charge End Date", '>%1', BillingToDate); if UsageDataBilling.FindLast() then ChargeEndDate := UsageDataBilling."Charge End Date"; end; local procedure GetOriginalInvoicedToDateIfRebillingMetadataExist() OriginalInvoicedToDate: Date var UsageDataBillingMetadata: Record "Usage Data Billing Metadata"; begin if not Rec."Usage Based Billing" then exit; UsageDataBillingMetadata.FilterOnServiceCommitment(Rec."Entry No."); UsageDataBillingMetadata.SetRange(Invoiced, false); UsageDataBillingMetadata.SetRange(Rebilling, true); UsageDataBillingMetadata.SetFilter("Supplier Charge Start Date", '>=%1', Rec."Next Billing Date"); if UsageDataBillingMetadata.FindFirst() then OriginalInvoicedToDate := CalcDate('<+1D>', UsageDataBillingMetadata."Original Invoiced to Date"); end; internal procedure GetSupplierChargeEndDateIfRebillingMetadataExist(FromDate: Date) SupplierChargeEndDate: Date var UsageDataBillingMetadata: Record "Usage Data Billing Metadata"; begin if not Rec."Usage Based Billing" then exit; UsageDataBillingMetadata.FilterOnServiceCommitment(Rec."Entry No."); UsageDataBillingMetadata.SetRange(Invoiced, false); UsageDataBillingMetadata.SetRange(Rebilling, true); UsageDataBillingMetadata.SetFilter("Supplier Charge Start Date", '>=%1', FromDate); if UsageDataBillingMetadata.FindFirst() then SupplierChargeEndDate := UsageDataBillingMetadata."Supplier Charge End Date"; end; internal procedure GetLastSupplierChargeEndDateIfMetadataExist() SupplierChargeEndDate: Date var UsageDataBillingMetadata: Record "Usage Data Billing Metadata"; begin UsageDataBillingMetadata.FilterOnServiceCommitment(Rec."Entry No."); if UsageDataBillingMetadata.FindLast() then exit(UsageDataBillingMetadata."Supplier Charge End Date"); end; internal procedure GetSupplierChargeStartDateIfRebillingMetadataExist(FromDate: Date) SupplierChargeStartDate: Date var UsageDataBillingMetadata: Record "Usage Data Billing Metadata"; begin if not Rec."Usage Based Billing" then exit; UsageDataBillingMetadata.FilterOnServiceCommitment(Rec."Entry No."); UsageDataBillingMetadata.SetRange(Invoiced, false); UsageDataBillingMetadata.SetRange(Rebilling, true); if FromDate <> 0D then UsageDataBillingMetadata.SetFilter("Supplier Charge Start Date", '>=%1', FromDate); if UsageDataBillingMetadata.FindFirst() then SupplierChargeStartDate := UsageDataBillingMetadata."Supplier Charge Start Date"; end; internal procedure ShowUsageDataBillingMetadata() var UsageDataBillingMetadata: Record "Usage Data Billing Metadata"; begin UsageDataBillingMetadata.SetRange("Subscription Line Entry No.", Rec."Entry No."); Page.RunModal(Page::"Usage Data Billing Metadata", UsageDataBillingMetadata); end; procedure UnitPriceForPeriod(ChargePeriodStart: Date; ChargePeriodEnd: Date) UnitPrice: Decimal var UnitCost: Decimal; UnitCostLCY: Decimal; BillingReferenceDateChanged: Boolean; begin Rec.UnitPriceAndCostForPeriod(Rec."Billing Rhythm", ChargePeriodStart, ChargePeriodEnd, UnitPrice, UnitCost, UnitCostLCY, BillingReferenceDateChanged); end; internal procedure UnitPriceAndCostForPeriod(BillingRhythm: DateFormula; ChargePeriodStart: Date; ChargePeriodEnd: Date; var UnitPrice: Decimal; var UnitCost: Decimal; var UnitCostLCY: Decimal; var BillingReferenceDateChanged: Boolean) var PeriodFormula: DateFormula; BillingPeriodRatio: Decimal; PeriodPrice, PeriodUnitCost, PeriodUnitCostLCY : Decimal; DayPrice, DayUnitCost, DayUnitCostLCY : Decimal; FollowUpDays: Integer; Periods: Integer; FollowUpPeriodDays: Integer; begin BillingPeriodRatio := Rec.GetBillingPeriodRatio(BillingRhythm, Rec."Billing Base Period"); if BillingPeriodRatio > 1 then begin PeriodPrice := Rec.Price; PeriodUnitCost := Rec."Unit Cost"; PeriodUnitCostLCY := Rec."Unit Cost (LCY)"; PeriodFormula := Rec."Billing Base Period"; end else begin PeriodPrice := Rec.Price * BillingPeriodRatio; PeriodUnitCost := Rec."Unit Cost" * BillingPeriodRatio; PeriodUnitCostLCY := Rec."Unit Cost (LCY)" * BillingPeriodRatio; PeriodFormula := Rec."Billing Rhythm"; end; Rec.CalculatePeriodCountAndDaysCount(PeriodFormula, ChargePeriodStart, ChargePeriodEnd, Periods, FollowUpDays, FollowUpPeriodDays); if FollowUpPeriodDays <> 0 then begin DayPrice := PeriodPrice / FollowUpPeriodDays; DayUnitCost := PeriodUnitCost / FollowUpPeriodDays; DayUnitCostLCY := PeriodUnitCostLCY / FollowUpPeriodDays; BillingReferenceDateChanged := true; end; UnitPrice := PeriodPrice * Periods + DayPrice * FollowUpDays; UnitCost := PeriodUnitCost * Periods + DayUnitCost * FollowUpDays; UnitCostLCY := PeriodUnitCostLCY * Periods + DayUnitCostLCY * FollowUpDays; end; internal procedure CalculatePeriodCountAndDaysCount(PeriodFormula: DateFormula; StartDate: Date; EndDate: Date; var Periods: Integer; var FollowUpDays: Integer; var FollowUpPeriodDays: Integer) var LastDayInPreviousPeriod: Date; LastDayInNextPeriod: Date; FollowUpDaysExist: Boolean; PeriodFormulaInteger: Integer; Letter: Char; begin Periods := 0; FollowUpDays := 0; FollowUpPeriodDays := 0; FollowUpDaysExist := true; LastDayInNextPeriod := StartDate - 1; DateFormulaManagement.FindDateFormulaType(PeriodFormula, PeriodFormulaInteger, Letter); repeat Evaluate(PeriodFormula, '<' + Format((Periods + 1) * PeriodFormulaInteger) + Letter + '>'); LastDayInPreviousPeriod := LastDayInNextPeriod; LastDayInNextPeriod := Rec.CalculateNextToDate(PeriodFormula, StartDate); if LastDayInNextPeriod <= EndDate then Periods += 1; FollowUpDaysExist := LastDayInNextPeriod <> EndDate; until LastDayInNextPeriod >= EndDate; if FollowUpDaysExist then begin FollowUpDays := EndDate - LastDayInPreviousPeriod; if SinglePeriodDaysCount(StartDate, EndDate, PeriodFormula) then FollowUpPeriodDays := Date2DMY(CalcDate('', EndDate), 1) else FollowUpPeriodDays := LastDayInNextPeriod - LastDayInPreviousPeriod; end; end; internal procedure GetBillingPeriodRatio(BillingRhythm: DateFormula; BillingBaseRhytm: DateFormula) BillingPeriodRatio: Decimal var BillingPeriodCount: Integer; BillingBasePeriodCount: Integer; begin if (Format(BillingRhythm) = '') or (Format(BillingBaseRhytm) = '') then exit(0); DateFormulaManagement.FindDateFormulaTypeForComparison(BillingRhythm, BillingPeriodCount); DateFormulaManagement.FindDateFormulaTypeForComparison(BillingBaseRhytm, BillingBasePeriodCount); BillingPeriodRatio := BillingPeriodCount / BillingBasePeriodCount; end; internal procedure CalculateNextToDate(PeriodFormula: DateFormula; FromDate: Date) NextToDate: Date var DistanceToEndOfMonth: Integer; LastDateInLastMonth: Date; PeriodFormulaInteger: Integer; Letter: Char; begin case Rec."Period Calculation" of Rec."Period Calculation"::"Align to Start of Month": NextToDate := CalcDate(PeriodFormula, FromDate) - 1; Rec."Period Calculation"::"Align to End of Month": begin DateFormulaManagement.FindDateFormulaType(PeriodFormula, PeriodFormulaInteger, Letter); if Letter in ['D', 'W'] then // Day/week periods have a fixed length and must not be aligned to the end of the month. NextToDate := CalcDate(PeriodFormula, FromDate) - 1 else begin DistanceToEndOfMonth := CalcDate('', GetBillingReferenceDate()) - GetBillingReferenceDate(); if DistanceToEndOfMonth > 2 then NextToDate := CalcDate(PeriodFormula, FromDate) - 1 else begin LastDateInLastMonth := CalcDate(PeriodFormula, FromDate); LastDateInLastMonth := CalcDate('', LastDateInLastMonth); NextToDate := LastDateInLastMonth - DistanceToEndOfMonth - 1; if NextToDate < FromDate then NextToDate := CalcDate(PeriodFormula, FromDate) - 1; end; end; end; end; if (Rec."Period Calculation" = Rec."Period Calculation"::"Align to End of Month") and not (Letter in ['D', 'W']) and (Rec."Subscription Line End Date" <> 0D) and (NextToDate < Rec."Subscription Line End Date") and (Rec."Subscription Line End Date" <= CalcDate(PeriodFormula, FromDate) - 1) then NextToDate := Rec."Subscription Line End Date"; end; local procedure GetBillingReferenceDate() BillingReferenceDate: Date var BillingLine: Record "Billing Line"; BillingLineArchive: Record "Billing Line Archive"; begin BillingReferenceDate := Rec."Subscription Line Start Date"; BillingLine.SetRange("Subscription Header No.", "Subscription Header No."); BillingLine.SetRange("Subscription Line Entry No.", "Entry No."); BillingLine.SetRange("Billing Reference Date Changed", true); if BillingLine.FindLast() then exit(BillingLine."Billing to" + 1) else begin BillingLineArchive.SetRange("Subscription Header No.", "Subscription Header No."); BillingLineArchive.SetRange("Subscription Line Entry No.", "Entry No."); BillingLineArchive.SetRange("Billing Reference Date Changed", true); if BillingLineArchive.FindLast() then exit(BillingLineArchive."Billing to" + 1); end; end; local procedure NoManualEntryOfUnitCostLCYForVendorServCommError(CurrentFieldNo: Integer) begin if Rec.IsPartnerVendor() and (CurrentFieldNo = Rec.FieldNo("Unit Cost (LCY)")) then Error(NoManualEntryOfUnitCostLCYForVendorServCommErr); end; internal procedure OpenDeferralsExist(): Boolean var CustSubContractDeferral: Record "Cust. Sub. Contract Deferral"; VendSubContractDeferral: Record "Vend. Sub. Contract Deferral"; begin if "Subscription Contract No." = '' then exit(false); case Partner of Enum::"Service Partner"::Customer: begin CustSubContractDeferral.SetRange(Released, false); CustSubContractDeferral.SetRange("Subscription Contract No.", "Subscription Contract No."); exit(not CustSubContractDeferral.IsEmpty()); end; Enum::"Service Partner"::Vendor: begin VendSubContractDeferral.SetRange(Released, false); VendSubContractDeferral.SetRange("Subscription Contract No.", "Subscription Contract No."); exit(not VendSubContractDeferral.IsEmpty()); end; end; end; local procedure CheckRatioBetweenBillingBasePeriodAndRhythm() var begin if (Format("Billing Base Period") <> '') and (Format("Billing Rhythm") <> '') then DateFormulaManagement.CheckIntegerRatioForDateFormulas("Billing Base Period", FieldCaption("Billing Base Period"), "Billing Rhythm", FieldCaption("Billing Rhythm")); end; local procedure DisplayErrorIfStartDateIsDifferentThanNextBillingDate() begin if IsContractLineClosed() then exit; if Rec."Subscription Line Start Date" <> Rec."Next Billing Date" then Error(SubscriptionLineStartDateDifferentThanNextBillingDateErr, Rec.FieldCaption("Subscription Line Start Date"), Rec.FieldCaption("Next Billing Date"), Rec.TableCaption()); end; local procedure IsContractLineClosed(): Boolean var CustomerContractLine: Record "Cust. Sub. Contract Line"; VendorContractLine: Record "Vend. Sub. Contract Line"; begin case Partner of Enum::"Service Partner"::Customer: if CustomerContractLine.FindFirstSubscriptionLine(Rec) then exit(CustomerContractLine.Closed); Enum::"Service Partner"::Vendor: if VendorContractLine.FindFirstSubscriptionLine(Rec) then exit(VendorContractLine.Closed); end; end; local procedure SinglePeriodDaysCount(StartDate: Date; EndDate: Date; PeriodFormula: DateFormula): Boolean begin if Format(PeriodFormula) <> '1M' then exit(false); if (Date2DMY(StartDate, 2) = Date2DMY(EndDate, 2)) and (Date2DMY(StartDate, 3) = Date2DMY(EndDate, 3)) then exit(true); end; [IntegrationEvent(false, false)] local procedure OnAfterUpdateNextBillingDate(var SubscriptionLine: Record "Subscription Line"; LastBillingToDate: Date) begin end; /// /// Raised after the Subscription Line Start Date change has passed the billing state check. /// Subscribers can only tighten the rule, by raising an error of their own. /// /// The Subscription Line carrying the new Subscription Line Start Date. [IntegrationEvent(false, false)] local procedure OnAfterCheckSubscriptionLineStartDateChangeAllowed(SubscriptionLine: Record "Subscription Line") begin end; [IntegrationEvent(false, false)] local procedure OnAfterSetUsageDataBillingFilters(var UsageDataBilling: Record "Usage Data Billing"; SubscriptionLine: Record "Subscription Line"; BillingFromDate: Date; BillingToDate: Date) begin end; [IntegrationEvent(false, false)] local procedure OnAfterCalculateServiceAmount(var SubscriptionLine: Record "Subscription Line"; CalledByFieldNo: Integer) begin end; [IntegrationEvent(false, false)] local procedure OnAfterUpdateSubscriptionLine(var SubscriptionLine: Record "Subscription Line") begin end; [IntegrationEvent(false, false)] local procedure OnAfterGetCombinedDimensionSetID(var SubscriptionLine: Record "Subscription Line") begin end; [IntegrationEvent(false, false)] local procedure OnBeforeValidateShortcutDimCode(var SubscriptionLine: Record "Subscription Line"; xSubscriptionLine: Record "Subscription Line"; FieldNumber: Integer; var ShortcutDimCode: Code[20]) begin end; [IntegrationEvent(false, false)] local procedure OnAfterValidateShortcutDimCode(var SubscriptionLine: Record "Subscription Line"; xSubscriptionLine: Record "Subscription Line"; FieldNumber: Integer; var ShortcutDimCode: Code[20]) begin end; [IntegrationEvent(false, false)] local procedure OnBeforeValidateDimensionSetID(var SubscriptionLine: Record "Subscription Line"; xSubscriptionLine: Record "Subscription Line") begin end; [IntegrationEvent(false, false)] local procedure OnAfterValidateDimensionSetID(var SubscriptionLine: Record "Subscription Line"; xSubscriptionLine: Record "Subscription Line") begin end; [IntegrationEvent(false, false)] local procedure OnAfterCopyFromSalesSubscriptionLine(var SubscriptionLine: Record "Subscription Line"; SalesSubscriptionLine: Record "Sales Subscription Line") begin end; [IntegrationEvent(false, false)] local procedure OnBeforeCheckServiceDates(ServiceStartDate: Date; ServiceEndDate: Date; NextBillingDate: Date; var IsHandled: Boolean) begin end; [IntegrationEvent(false, false)] local procedure OnAfterUpdateServiceCommitmentFromContractPriceUpdateLine(var SubscriptionLine: Record "Subscription Line"; SubContractPriceUpdateLine: Record "Sub. Contr. Price Update Line") begin end; }