Skip to content

Commit 6d0d957

Browse files
committed
Pause Subscriptions
1 parent a32b71c commit 6d0d957

14 files changed

Lines changed: 1309 additions & 883 deletions

src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingProposal.Codeunit.al

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -390,11 +390,17 @@ codeunit 8062 "Billing Proposal"
390390
UsageDataBilling.FindLast();
391391
if UsageDataBilling.Rebilling or (UsageDataBilling."Usage Base Pricing" = Enum::"Usage Based Pricing"::"Usage Quantity") then
392392
BillingLine."Service Object Quantity" := UsageDataBilling.Quantity;
393-
BillingLine."Unit Price" := BillingLine.Amount / BillingLine."Service Object Quantity";
393+
if BillingLine."Service Object Quantity" <> 0 then
394+
BillingLine."Unit Price" := BillingLine.Amount / BillingLine."Service Object Quantity"
395+
else
396+
BillingLine."Unit Price" := UsageDataBilling."Unit Price";
394397
BillingLine."Discount %" := ServiceCommitment."Discount %";
395398
// Apply discount from Subscription Line
396399
BillingLine.Amount := BaseAmount * (1 - ServiceCommitment."Discount %" / 100);
397-
BillingLine."Unit Cost" := UsageDataBilling."Cost Amount" / UsageDataBilling.Quantity;
400+
if UsageDataBilling.Quantity <> 0 then
401+
BillingLine."Unit Cost" := UsageDataBilling."Cost Amount" / UsageDataBilling.Quantity
402+
else
403+
BillingLine."Unit Cost" := UsageDataBilling."Unit Cost";
398404
Currency.Initialize(ServiceCommitment."Currency Code");
399405
Currency.TestField("Unit-Amount Rounding Precision");
400406
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"
421427
BillingLine."Unit Cost" := Round(BillingLine."Unit Cost", Currency."Unit-Amount Rounding Precision");
422428
BillingLine."Unit Cost (LCY)" := Round(BillingLine."Unit Cost (LCY)", GLSetup."Unit-Amount Rounding Precision");
423429

424-
BillingLine.Amount := CalculateBillingLineServiceAmount(BillingLine);
425-
BillingLine.Amount := Round(BillingLine.Amount, Currency."Amount Rounding Precision");
430+
BillingLine.Amount := Round(BillingLine."Unit Price" * BillingLine."Service Object Quantity" * (1 - BillingLine."Discount %" / 100), Currency."Amount Rounding Precision");
426431
end;
427432

428-
internal procedure CalculateBillingLineServiceAmount(var BillingLine: Record "Billing Line") ServiceAmount: Decimal
429-
begin
430-
BillingLine.TestField("Service Object Quantity");
431-
ServiceAmount := BillingLine."Unit Price" * BillingLine."Service Object Quantity" * (1 - BillingLine."Discount %" / 100);
432-
end;
433433

434434
local procedure UpdateBillingLineFromServiceCommitment(var BillingLine: Record "Billing Line"; ServiceCommitment: Record "Subscription Line")
435435
var

src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,6 @@ codeunit 8060 "Create Billing Documents"
317317
var
318318
UsageDataBilling: Record "Usage Data Billing";
319319
ServiceCommitment: Record "Subscription Line";
320-
NewSalesLineQuantity: Decimal;
321-
NewSalesLineAmount: Decimal;
322320
begin
323321
if not ServiceCommitment.Get(BillingLine."Subscription Line Entry No.") then
324322
exit;
@@ -328,15 +326,21 @@ codeunit 8060 "Create Billing Documents"
328326
if not ServiceCommitment.IsUsageDataBillingFound(UsageDataBilling, BillingLine."Billing from", BillingLine."Billing to") then
329327
exit;
330328

331-
UsageDataBilling.CalcSums(Amount, Quantity);
332-
NewSalesLineQuantity := SalesLine.Quantity;
333-
NewSalesLineAmount := UsageDataBilling.Amount;
334329
UsageDataBilling.FindLast();
335330
if UsageDataBilling.Rebilling then
336-
NewSalesLineQuantity := UsageDataBilling.Quantity;
331+
SalesLine.Validate(Quantity, UsageDataBilling.Quantity);
332+
if SalesLine.Quantity = 0 then begin
333+
UsageDataBilling.SetFilter(Quantity, '<>0');
334+
if UsageDataBilling.FindLast() then
335+
SalesLine.Validate(Quantity, UsageDataBilling.Quantity);
336+
end;
337337

338-
SalesLine.Validate(Quantity, NewSalesLineQuantity);
339-
SalesLine.Validate("Unit Price", SalesLine.GetSalesDocumentSign() * NewSalesLineAmount / NewSalesLineQuantity);
338+
UsageDataBilling.SetRange(Quantity);
339+
UsageDataBilling.CalcSums(Amount);
340+
if SalesLine.Quantity <> 0 then
341+
SalesLine.Validate("Unit Price", SalesLine.GetSalesDocumentSign() * UsageDataBilling.Amount / SalesLine.Quantity)
342+
else
343+
SalesLine.Validate("Unit Price", UsageDataBilling."Unit Price");
340344
SalesLine.Validate("Line Discount %", ServiceCommitment."Discount %");
341345
end;
342346

@@ -419,8 +423,6 @@ codeunit 8060 "Create Billing Documents"
419423
var
420424
UsageDataBilling: Record "Usage Data Billing";
421425
ServiceCommitment: Record "Subscription Line";
422-
NewPurchaseLineQuantity: Decimal;
423-
NewPurchaseLineAmount: Decimal;
424426
begin
425427
if not ServiceCommitment.Get(BillingLine."Subscription Line Entry No.") then
426428
exit;
@@ -430,15 +432,21 @@ codeunit 8060 "Create Billing Documents"
430432
if not ServiceCommitment.IsUsageDataBillingFound(UsageDataBilling, BillingLine."Billing from", BillingLine."Billing to") then
431433
exit;
432434

433-
UsageDataBilling.CalcSums("Cost Amount", Quantity);
434-
NewPurchaseLineQuantity := PurchLine.Quantity;
435-
NewPurchaseLineAmount := UsageDataBilling."Cost Amount";
436435
UsageDataBilling.FindLast();
437436
if UsageDataBilling.Rebilling then
438-
NewPurchaseLineQuantity := UsageDataBilling.Quantity;
437+
PurchLine.Validate(Quantity, UsageDataBilling.Quantity);
438+
if PurchLine.Quantity = 0 then begin
439+
UsageDataBilling.SetFilter(Quantity, '<>0');
440+
if UsageDataBilling.FindLast() then
441+
PurchLine.Validate(Quantity, UsageDataBilling.Quantity);
442+
end;
439443

440-
PurchLine.Validate(Quantity, NewPurchaseLineQuantity);
441-
PurchLine.Validate("Direct Unit Cost", PurchLine.GetPurchaseDocumentSign() * NewPurchaseLineAmount / NewPurchaseLineQuantity);
444+
UsageDataBilling.SetRange(Quantity);
445+
UsageDataBilling.CalcSums("Cost Amount");
446+
if PurchLine.Quantity <> 0 then
447+
PurchLine.Validate("Direct Unit Cost", PurchLine.GetPurchaseDocumentSign() * UsageDataBilling."Cost Amount" / PurchLine.Quantity)
448+
else
449+
PurchLine.Validate("Direct Unit Cost", 0);
442450
PurchLine.Validate("Line Discount %", ServiceCommitment."Discount %");
443451
end;
444452

src/Apps/W1/Subscription Billing/App/Billing/Codeunits/PurchaseDocuments.Codeunit.al

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,41 @@ codeunit 8066 "Purchase Documents"
104104
end
105105
end;
106106

107+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnBeforeCheckHeaderPostingType, '', false, false)]
108+
local procedure SkipInvoiceOrShipFlagCheckForSubscriptionBillingOnBeforeCheckHeaderPostingType(var PurchaseHeader: Record "Purchase Header"; var IsHandled: Boolean)
109+
begin
110+
// Allow posting without Invoice or Ship flags being set for subscription billing documents
111+
if PurchaseHeader."Recurring Billing" then
112+
IsHandled := true;
113+
end;
114+
115+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Release Purchase Document", OnCodeOnAfterPurchLineSetFilters, '', false, false)]
116+
local procedure SkipQuantityCheckForSubscriptionBillingOnCodeOnAfterPurchLineSetFilters(PurchaseHeader: Record "Purchase Header"; var PurchaseLine: Record "Purchase Line"; var IsHandled: Boolean)
117+
begin
118+
// Skip quantity check for subscription billing documents
119+
if PurchaseHeader."Recurring Billing" then
120+
IsHandled := true;
121+
end;
122+
123+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnBeforeCalcInvoice, '', false, false)]
124+
local procedure ForceInvoiceCreationForZeroQtyDocumentOnBeforeCalcInvoice(var PurchHeader: Record "Purchase Header"; var NewInvoice: Boolean; var IsHandled: Boolean)
125+
begin
126+
// For subscription billing documents with zero quantity lines, force invoice creation
127+
// so that the posted invoice header is always generated
128+
if PurchHeader."Recurring Billing" then begin
129+
NewInvoice := true;
130+
IsHandled := true;
131+
end;
132+
end;
133+
134+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnPostPurchLineOnAfterSetEverythingInvoiced, '', false, false)]
135+
local procedure SetEverythingInvoicedForZeroQtyDocumentOnAfterSetEverythingInvoiced(PurchaseHeader: Record "Purchase Header"; var EverythingInvoiced: Boolean)
136+
begin
137+
// Treat zero-qty subscription billing lines as fully invoiced so BC cleans up the source document
138+
if PurchaseHeader."Recurring Billing" then
139+
EverythingInvoiced := true;
140+
end;
141+
107142
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnBeforeDeleteAfterPosting, '', false, false)]
108143
local procedure PurchasePostOnBeforePurchaseLineDeleteAll(var PurchaseHeader: Record "Purchase Header"; var PurchInvHeader: Record "Purch. Inv. Header"; var PurchCrMemoHdr: Record "Purch. Cr. Memo Hdr.")
109144
var

src/Apps/W1/Subscription Billing/App/Billing/Codeunits/SalesDocuments.Codeunit.al

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,41 @@ codeunit 8063 "Sales Documents"
428428
TempSalesLine.Validate("Qty. to Invoice", 0);
429429
end;
430430

431+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnBeforeCheckHeaderPostingType, '', false, false)]
432+
local procedure SkipInvoiceOrShipFlagCheckForSubscriptionBillingOnBeforeCheckHeaderPostingType(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean)
433+
begin
434+
// Allow posting without Invoice or Ship flags being set for subscription billing documents
435+
if SalesHeader."Recurring Billing" then
436+
IsHandled := true;
437+
end;
438+
439+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Release Sales Document", OnBeforeSalesLineFind, '', false, false)]
440+
local procedure SkipQuantityCheckForSubscriptionBillingOnBeforeSalesLineFind(var SalesHeader: Record "Sales Header"; var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
441+
begin
442+
// Skip quantity check for subscription billing documents
443+
if SalesHeader."Recurring Billing" then
444+
IsHandled := true;
445+
end;
446+
447+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnBeforeCalcInvoice, '', false, false)]
448+
local procedure ForceInvoiceCreationForZeroQtyDocumentOnBeforeCalcInvoice(SalesHeader: Record "Sales Header"; var TempSalesLineGlobal: Record "Sales Line" temporary; var NewInvoice: Boolean; var IsHandled: Boolean)
449+
begin
450+
// For subscription billing documents with zero quantity lines, force invoice creation
451+
// so that the posted invoice header is always generated
452+
if SalesHeader."Recurring Billing" then begin
453+
NewInvoice := true;
454+
IsHandled := true;
455+
end;
456+
end;
457+
458+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnPostSalesLineOnAfterSetEverythingInvoiced, '', false, false)]
459+
local procedure SetEverythingInvoicedForZeroQtyDocumentOnAfterSetEverythingInvoiced(SalesHeader: Record "Sales Header"; var EverythingInvoiced: Boolean)
460+
begin
461+
// Treat zero-qty subscription billing lines as fully invoiced so BC cleans up the source document
462+
if SalesHeader."Recurring Billing" then
463+
EverythingInvoiced := true;
464+
end;
465+
431466
local procedure CheckResetValueForServiceCommitmentItems(var TempSalesLine: Record "Sales Line") ResetValueForServiceCommitmentItems: Boolean
432467
var
433468
ContractRenewalMgt: Codeunit "Sub. Contract Renewal Mgt.";

src/Apps/W1/Subscription Billing/App/Sales Service Commitments/Tables/SalesSubscriptionLine.Table.al

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,13 +466,17 @@ table 8068 "Sales Subscription Line"
466466
if Amount > MaxServiceAmount then
467467
Error(ServiceAmountIncreaseErr, FieldCaption(Amount), Format(MaxServiceAmount));
468468
"Discount Amount" := Round(MaxServiceAmount - Amount, Currency."Amount Rounding Precision");
469-
"Discount %" := Round(100 - (Amount / MaxServiceAmount * 100), 0.00001);
469+
if MaxServiceAmount <> 0 then
470+
"Discount %" := Round(100 - (Amount / MaxServiceAmount * 100), 0.00001);
470471
end else begin
471472
Amount := Round((Price * SalesLine.Quantity), Currency."Amount Rounding Precision");
472473
if CalledByFieldNo = FieldNo("Discount %") then
473474
"Discount Amount" := Round(Amount * "Discount %" / 100, Currency."Amount Rounding Precision");
474475
if CalledByFieldNo = FieldNo("Discount Amount") then
475-
"Discount %" := Round("Discount Amount" / Amount * 100, 0.00001);
476+
if Amount <> 0 then
477+
"Discount %" := Round("Discount Amount" / Amount * 100, 0.00001)
478+
else
479+
"Discount %" := 0;
476480
Amount := Round((Price * SalesLine.Quantity) - "Discount Amount", Currency."Amount Rounding Precision");
477481
if Amount > MaxServiceAmount then
478482
Error(ServiceAmountIncreaseErr, FieldCaption(Amount), Format(MaxServiceAmount));

src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,6 @@ table 8059 "Subscription Line"
857857
if MaxServiceAmount <> 0 then
858858
"Discount %" := Round(100 - (Amount / MaxServiceAmount * 100), 0.00001);
859859
end else begin
860-
ServiceObject.TestField(Quantity);
861860
Amount := Price * ServiceObject.Quantity;
862861
if not "Usage Based Billing" then
863862
Amount := Round(Amount, Currency."Amount Rounding Precision");
@@ -867,7 +866,10 @@ table 8059 "Subscription Line"
867866
"Discount Amount" := Round("Discount Amount", Currency."Amount Rounding Precision");
868867
end;
869868
if CalledByFieldNo = FieldNo("Discount Amount") then
870-
"Discount %" := Round("Discount Amount" / Amount * 100, 0.00001);
869+
if Amount <> 0 then
870+
"Discount %" := Round("Discount Amount" / Amount * 100, 0.00001)
871+
else
872+
"Discount %" := 0;
871873
if ("Discount Amount" > MaxServiceAmount) and ("Discount Amount" <> 0) then
872874
Error(CannotBeGreaterThanErr, FieldCaption("Discount Amount"), Format(MaxServiceAmount));
873875
Amount := Amount - "Discount Amount";

src/Apps/W1/Subscription Billing/App/Service Objects/Tables/SubscriptionHeader.Table.al

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -369,14 +369,13 @@ table 8057 "Subscription Header"
369369
{
370370
Caption = 'Quantity';
371371
InitValue = 1;
372-
NotBlank = true;
373372
AutoFormatType = 0;
374373
DecimalPlaces = 0 : 5;
375374

376375
trigger OnValidate()
377376
begin
378-
if Quantity <= 0 then
379-
Error(QtyZeroOrNegativeErr);
377+
if Quantity < 0 then
378+
Error(QtyNegativeErr);
380379
if (Quantity <> 1) and ("Serial No." <> '') then
381380
Error(SerialQtyErr);
382381
Rec.ArchiveServiceCommitments();
@@ -948,7 +947,7 @@ table 8057 "Subscription Header"
948947
SkipBillToContact: Boolean;
949948
SkipInsertServiceCommitments: Boolean;
950949
ConfirmChangeQst: Label 'Do you want to change %1?', Comment = '%1 = a Field Caption like Currency Code';
951-
QtyZeroOrNegativeErr: Label 'The quantity cannot be zero or negative.';
950+
QtyNegativeErr: Label 'The quantity cannot be negative.';
952951
EndUserCustomerTxt: Label 'End-User Customer';
953952
BillToCustomerTxt: Label 'Bill-to Customer';
954953
SerialQtyErr: Label 'Only Subscriptions with quantity 1 may have a serial number.';
@@ -2216,14 +2215,18 @@ table 8057 "Subscription Header"
22162215
local procedure GetRecalculateLinesDialog(ChangedFieldName: Text): Text
22172216
var
22182217
RecalculateLinesQst: Label 'If you change %1, the existing Subscription Lines prices will be recalculated.\\Do you want to continue?', Comment = '%1: FieldCaption';
2219-
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.';
2218+
RecalculateLinesFromQuantityQst: Label 'If you change the %1, the amount for open subscription lines will be recalculated.\\Do you want to continue?', Comment = '%1: FieldCaption';
2219+
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';
22202220
RecalculateLinesFromVariantCodeQst: Label 'The %1 has been changed.\\Do you want to update the price?', Comment = '%1= Changed Field Name.';
22212221
begin
22222222
case ChangedFieldName of
22232223
Rec.FieldName(Rec."Variant Code"):
22242224
exit(StrSubstNo(RecalculateLinesFromVariantCodeQst, ChangedFieldName));
22252225
Rec.FieldName(Rec.Quantity):
2226-
exit(StrSubstNo(RecalculateLinesFromQuantityQst, ChangedFieldName));
2226+
if Rec.Quantity = 0 then
2227+
exit(StrSubstNo(PauseSubscriptionQst, ChangedFieldName))
2228+
else
2229+
exit(StrSubstNo(RecalculateLinesFromQuantityQst, ChangedFieldName));
22272230
else
22282231
exit(StrSubstNo(RecalculateLinesQst, ChangedFieldName));
22292232
end;

src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/GenericConnectorProcessing.Codeunit.al

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ codeunit 8033 "Generic Connector Processing" implements "Usage Data Processing"
1111
ProcessingSetupErr: Label 'You must specify either a reading/writing XMLport or a reading/writing codeunit.';
1212
UsageDataLinesProcessingErr: Label 'Errors were found while processing the Usage Data Lines.';
1313
NoDataFoundErr: Label 'No data found for processing step %1.', Comment = '%1 = Name of the processing step';
14-
UsageDataWithZeroQuantityCannotBeProcessedErr: Label 'Usage data with Quantity 0 cannot be processed.';
1514
NoServiceObjectErr: Label 'The %1 ''%2'' is not linked to an %3.', Comment = '%1 = Table name, %2 = Entry number, %3 = Table name';
1615
ServiceObjectProvisionEndDateErr: Label 'The %1 ''%2'' is deinstalled.', Comment = '%1 = Table name, %2 = Entry number';
1716
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"
8584
if UsageDataGenericImport.FindSet() then
8685
repeat
8786
UsageDataGenericImport.Validate("Processing Status", Enum::"Processing Status"::None);
88-
ErrorIfUsageDataGenericImportQuantityIsZero(UsageDataGenericImport);
8987
GenericImportSettings.Get(UsageDataImport."Supplier No.");
9088
CreateUsageDataCustomers(GenericImportSettings, UsageDataGenericImport, UsageDataSupplierReference, UsageDataImport."Supplier No.");
9189
CreateUsageDataSubscriptions(GenericImportSettings, UsageDataGenericImport, UsageDataSupplierReference, UsageDataImport);
@@ -160,14 +158,6 @@ codeunit 8033 "Generic Connector Processing" implements "Usage Data Processing"
160158
end;
161159
end;
162160

163-
local procedure ErrorIfUsageDataGenericImportQuantityIsZero(var UsageDataGenericImport: Record "Usage Data Generic Import")
164-
begin
165-
if UsageDataGenericImport.Quantity <> 0 then
166-
exit;
167-
UsageDataGenericImport."Processing Status" := UsageDataGenericImport."Processing Status"::Error;
168-
UsageDataGenericImport.SetReason(UsageDataWithZeroQuantityCannotBeProcessedErr);
169-
end;
170-
171161
local procedure CheckServiceCommitment(var UsageDataGenericImport: Record "Usage Data Generic Import"; var UsageDataImport: Record "Usage Data Import"; var ServiceCommitment: Record "Subscription Line")
172162
begin
173163
if ImportAndProcessUsageData.GetServiceCommitmentForSubscription(UsageDataImport."Supplier No.", UsageDataGenericImport."Supp. Subscription ID", ServiceCommitment) then

0 commit comments

Comments
 (0)