using Centron.BusinessLogic; using Centron.BusinessLogic.EmployeeArea; using Centron.BusinessLogic.Sales.Customers; using Centron.BusinessLogic.Sales.Receipts.DataAndResults; using Centron.BusinessLogic.WebServices; using Centron.BusinessLogic.WebServices.Sales.Receipts; using Centron.Data.Entities.Administration.Logins; using Centron.Data.Entities.CustomerArea; using Centron.Data.Entities.Sales.Receipts.Invoices.Dunning; using Centron.Data.WebServices.Mail; using Centron.Data.WebServices.Sales.Receipts.CreditVouchers; using Centron.Interfaces; using Centron.Interfaces.Administration.Environment; using Centron.Interfaces.BL; using Centron.Interfaces.Sales.Receipts.InsertReceipts; using Centron.Interfaces.Sales.Receipts.Invoices.Dunning; using Centron.Tests.EndToEnd.Infrastructure; using CentronSoftware.Centron.WebServices.Entities.Mail.Templates; using CentronSoftware.Centron.WebServices.Entities.Sales.Receipts.Invoices.Dunning; using CentronSoftware.Centron.WebServices.EntitiesWrongPlace.Sales.Receipts.DataAndResults; using System; using System.Collections.Generic; using System.Linq; using Xunit; using Xunit.Abstractions; namespace Centron.Tests.EndToEnd.Tests.Dunning { public class DunningTests : EndToEndTest { private readonly int _referenceCustomerI3D = 10000; // NOTE: these tests also test OPOS at the same time public DunningTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { this.Verifier.Settings.IgnoreProperty(f => f.AdvisorEmailTextVariables); this.Verifier.Settings.IgnoreProperty(f => f.CustomerEmailTextVariables); this.Verifier.Settings.IgnoreProperty(f => f.NextDueDateInDays); this.Verifier.Settings.IgnoreProperty(f => f.NextDueDate); this.Verifier.Settings.IgnoreProperty(f => f.ReceiptReports); this.Verifier.Settings.IgnoreProperty(f => f.Report); this.Verifier.Settings.IgnoreProperty(f => f.ReportFileName); this.Verifier.Settings.IgnoreProperty(f => f.DeletedDate); //Test is a bit flaky with this property sometimes being equal to Date, sometimes not this.Verifier.Settings.IgnoreProperty(f => f.MailAttachments); } public override void Execute() { this.PrepareDatabase(); // dunningBL this.GetCustomers(); this.CalculateDunningStatistics(); this.UpdateDunningStopAndInfo(); this.GetAndUpdateSettings(); this.GetAndUpdateCustomerSettings(); // dunningRunBL this.ExecuteDunningRun(); this.LoadDunningRuns(); this.ResetDunningRun(); } private void PrepareDatabase() { // reset all invoices dunning stuff and close all this.Sql(@"UPDATE RechKopf SET Mahnung1Datum = NULL, Mahnung1BearbeiterI3D = NULL, Mahnung2Datum = NULL, Mahnung2BearbeiterI3D = NULL, Mahnung3Datum = NULL, Mahnung3BearbeiterI3D = NULL, MahnInfo = NULL, MahnStop = NULL, Mahnstufe = 0, Datum = '2020-01-01 00:00:00.000', FaelligAm = '2020-01-20 00:00:00.000', Status = 2"); this.Sql(@"UPDATE RechKopf SET Status = 1 WHERE I3D = 1 OR I3D = 2 OR I3D = 11 OR I3D = 13 OR I3D = 14"); this.Sql(@"UPDATE RechKopf SET Status = 1, FaelligAm = DATEADD(day, 5, GETDATE()) WHERE I3D = 3 OR I3D = 4 OR I3D = 26"); this.Sql(@"UPDATE RechKopf SET Status = 1, Mahnung1Datum = '2020-01-25 00:00:00.000', Mahnung1BearbeiterI3D = 26, Mahnstufe = 1 WHERE I3D = 29 OR I3D = 30 OR I3D = 35"); this.Sql(@"UPDATE RechKopf SET Status = 1, Mahnung1Datum = '2020-01-25 00:00:00.000', Mahnung1BearbeiterI3D = 26, Mahnstufe = 1, MahnStop = 1, MahnInfo = 'MahnInfo entry' WHERE I3D = 31 OR I3D = 32"); this.Sql(@"UPDATE RechKopf SET Status = 1, Mahnung1Datum = '2020-01-25 00:00:00.000', Mahnung1BearbeiterI3D = 26, Mahnung2Datum = '2020-02-20 00:00:00.000', Mahnung2BearbeiterI3D = 26, Mahnstufe = 2 WHERE I3D = 36 OR I3D = 38 OR I3D = 39 OR I3D = 37"); this.Sql(@"UPDATE RechKopf SET Status = 1, Mahnung1Datum = '2020-01-25 00:00:00.000', Mahnung1BearbeiterI3D = 26, Mahnung2Datum = '2020-02-20 00:00:00.000', Mahnung2BearbeiterI3D = 26, Mahnung3Datum = '2020-03-20 00:00:00.000', Mahnung3BearbeiterI3D = 26, Mahnstufe = 3 WHERE I3D = 40 OR I3D = 41 OR I3D = 51 OR I3D = 54"); this.Sql(@$"UPDATE AccountCustomers SET DunningInfo = 'DunningInfo', DunningStop = 1, DunningStopBegin = '2018-01-01 00:00:00.000', DunningStopEnd = '2018-02-02 00:00:00.000' WHERE Number = {this._referenceCustomerI3D}"); this.ForwardInvoiceToCreditVoucherPartsByPrice(); this.ForwardInvoiceToCreditVoucherPartsByQuantity(); } private void ForwardInvoiceToCreditVoucherPartsByPrice() { var referenceInvoiceI3D = 51; using var session = new BLSession(); var bl = session.GetBL(); var user = this.GetLoggedInUser(); var invoice = bl.GetReceiptByI3D(referenceInvoiceI3D, CentronObjectKindNumeric.InvoiceClass, user).ThrowIfError(); var forwardedReceipt = bl.ForwardReceipt( CentronObjectKindNumeric.CreditVoucherClass, user.User, new ForwardReceiptData { CreateReceiptData = new CreateReceiptData { IgnoreCallbacks = true, }, CreateReceiptItemsFromExistingItemsData = new List { new() { IgnoreCallbacks = true, }, }, IgnoreCallbacks = true }, new List { new ReceiptToForward { ReceiptKind = invoice.ReceiptKind, ReceiptI3D = invoice.I3D } }, null, InsertReceiptTakeoverOptions.Reference, true, false).ThrowIfError(); var creditVoucher = (ReceiptCreditVoucherDTO)forwardedReceipt.Receipt; var articlePos = creditVoucher.Items.First(f => f.ArticleCode == "SERHM"); articlePos.BasePrice = 100; session.GetBL().SaveReceipt( creditVoucher, user.User, new SaveReceiptData { IgnoreCallbacks = true }, false, CreatedThroughApplication.CentronNet, null).ThrowIfError(); // the invoice is now closed so we need to manually reopen it this.Sql(@$"UPDATE RechKopf SET Status = 1 WHERE I3D = {referenceInvoiceI3D}"); } private void ForwardInvoiceToCreditVoucherPartsByQuantity() { using var session = new BLSession(); var bl = session.GetBL(); var user = this.GetLoggedInUser(); var invoice = bl.GetReceiptByI3D(54, CentronObjectKindNumeric.InvoiceClass, user).ThrowIfError(); var forwardedReceipt = bl.ForwardReceipt( CentronObjectKindNumeric.CreditVoucherClass, user.User, new ForwardReceiptData { CreateReceiptData = new CreateReceiptData { IgnoreCallbacks = true, }, CreateReceiptItemsFromExistingItemsData = new List { new() { IgnoreCallbacks = true, }, }, IgnoreCallbacks = true }, new List { new ReceiptToForward { ReceiptKind = invoice.ReceiptKind, ReceiptI3D = invoice.I3D } }, null, InsertReceiptTakeoverOptions.Reference, true, false).ThrowIfError(); var creditVoucher = (ReceiptCreditVoucherDTO)forwardedReceipt.Receipt; var articlePos = creditVoucher.Items.First(f => f.ArticleCode == "SERHM"); Assert.NotNull(articlePos.QuantityComplete); // the value should be exactly 2.0 but comparing decimals like this is not a great idea Assert.InRange(articlePos.QuantityComplete.Value, (decimal)1.9, (decimal)2.1); articlePos.QuantityComplete = 1; session.GetBL().SaveReceipt( creditVoucher, user.User, new SaveReceiptData { IgnoreCallbacks = true }, false, CreatedThroughApplication.CentronNet, null).ThrowIfError(); } #region DunningBL private void GetCustomers() { using (var session = new BLSession()) { InternalLoad("WithoutFilter", new DunningCustomerFilter()); InternalLoad("IncludeDunningStop", new DunningCustomerFilter { InDunningStop = true }); InternalLoad("IncludingNoDunningLevel", new DunningCustomerFilter { IncludeNoDunningLevel = true }); InternalLoad("IncludingCreditVouchers", new DunningCustomerFilter { IncludeCreditVouchers = true }); InternalLoad("IncludingNotReadyForDunning", new DunningCustomerFilter { IncludeNotReadyForDunning = true }); InternalLoad("NoneLevel", new DunningCustomerFilter { DunningLevels = new List { DunningLevel.None } }); InternalLoad("Level1", new DunningCustomerFilter { DunningLevels = new List { DunningLevel.Level1 } }); InternalLoad("Level2", new DunningCustomerFilter { DunningLevels = new List { DunningLevel.Level2 } }); InternalLoad("Level3", new DunningCustomerFilter { DunningLevels = new List { DunningLevel.Level3 } }); InternalLoad("Level12", new DunningCustomerFilter { DunningLevels = new List { DunningLevel.Level1, DunningLevel.Level2 } }); InternalLoad("Level13", new DunningCustomerFilter { DunningLevels = new List { DunningLevel.Level1, DunningLevel.Level3 } }); void InternalLoad(string name, DunningCustomerFilter filter) { using (var bl2 = new BLSession()) { // we need to set this to always true, otherwise we'll get 0 because this will filter everything before today filter.IncludeBeforeDueDate = true; var customers = bl2.GetBL().GetDunningCustomers(filter, this.GetLoggedInUser()); this.Verifier.Verify("DunningCustomers" + name, customers); } } } } private void UpdateDunningStopAndInfo() { using (var session = new BLSession()) { var invoiceDunningI3D = 2; session.GetBL().UpdateDunningStopAndInfo(invoiceDunningI3D, CentronObjectKindNumeric.InvoiceClass, true, "Updated dunning stop info for receipt", new DateTime(2020, 1, 1), new DateTime(2022, 1, 1), this.GetLoggedInUser()); var invoiceDunning = session.GetBL().GetEntityById(invoiceDunningI3D); this.Verifier.Verify("UpdatedInvoiceDunning", ObjectMapper.Map(invoiceDunning)); } using (var session = new BLSession()) { session.GetBL().UpdateDunningStopAndInfo(this._referenceCustomerI3D, CentronObjectKindNumeric.CustomerClass, true, "Updated dunning stop info for customer", new DateTime(2020, 1, 1), new DateTime(2022, 1, 1), this.GetLoggedInUser()); var customer = session.GetBL().GetDunningCustomers(new DunningCustomerFilter { CustomerI3Ds = new List { this._referenceCustomerI3D } }, this.GetLoggedInUser()).FirstOrDefault(); this.Verifier.Verify("UpdatedCustomerDunningStop", customer); } } private void CalculateDunningStatistics() { using (var session = new BLSession()) { var statistics = session.GetBL().CalculateDunningStatistics(new DunningCustomerFilter { // we need to set this to always true, otherwise we'll get 0 because this will filter everything before today IncludeBeforeDueDate = true }, this.GetLoggedInUser()); this.Verifier.Verify("DunningStatistics", statistics); } } private void GetAndUpdateSettings() { using (var session = new BLSession()) { var bl = session.GetBL(); var settings = bl.GetSettings(); this.Verifier.Verify("DunningSettings", settings); settings.DefaultInterest = 20; settings.ShowLevel1 = true; settings.Level1AfterDays = 20; settings.Level1Fees = 20; settings.Level1Text = "Updated settings text"; settings.ShowLevel2 = true; settings.Level2AfterDays = 20; settings.Level2Fees = 20; settings.Level2Text = "Updated settings text"; settings.ShowLevel3 = true; settings.Level3AfterDays = 20; settings.Level3Fees = 20; settings.Level3Text = "Updated settings text"; settings.PaymentToleranceInDays = 20; bl.UpdateSettings(settings); var updatedSettings = bl.GetSettings(); this.Verifier.Verify("UpdatedDunningSettings", updatedSettings); } } private void GetAndUpdateCustomerSettings() { using (var session = new BLSession()) { session.GetBL().UpdateDunningSettingsForCustomer(this._referenceCustomerI3D, 2, DunningSendType.Print, true, this.GetLoggedInUser()); var updatedCustomer = session.GetBL().GetCustomer(this._referenceCustomerI3D); this.Verifier.Verify("DunningCustomerSettings", new CustomerDunningSettingsTestClass(updatedCustomer)); } } private class CustomerDunningSettingsTestClass { public int CustomerI3D { get; set; } public int? ContactPersonI3D { get; set; } public DunningSendType? SendType { get; set; } public bool UseAlternativeInvoiceAddress { get; set; } public CustomerDunningSettingsTestClass(Customer customer) { this.CustomerI3D = customer.I3D; this.ContactPersonI3D = customer.DunningContactPerson?.I3D; switch (customer.DunningKind) { case "Mail": this.SendType = DunningSendType.Mail; break; case "Print": this.SendType = DunningSendType.Print; break; case "Fax": this.SendType = DunningSendType.Fax; break; default: this.SendType = null; break; } this.UseAlternativeInvoiceAddress = customer.DunningAtAlternateInvoiceAddress; } } #endregion private void ExecuteDunningRun() { DunningCustomerDTO customer; using (var session = new BLSession()) { customer = session.GetBL().GetDunningCustomers(new DunningCustomerFilter { IncludeBeforeDueDate = true, CustomerI3Ds = new List { this._referenceCustomerI3D } }, this.GetLoggedInUser()).FirstOrDefault(); } using (var session = new BLSession()) { var bl = session.GetBL(); bl.EndToEndTestMode = true; var result = bl.ExecuteDunningRun(new DunningRunForCustomerDTO { Customer = customer, Invoices = customer.Invoices.Where(f => f.DunningLevel != DunningLevel.Level3).ToList(), CreditVouchers = customer.CreditVouchers, MailBody = new MailBodyDTO { Text = "Email Body", Type = Interfaces.Mail.Enums.CentronMailBodyType.Plain }, MailSubject = "Email Subject", SendType = DunningSendType.Mail }, this.GetLoggedInUser()); this.Verifier.Verify("DunningRun", result); } } private void LoadDunningRuns() { using (var session = new BLSession()) { var runs = session.GetBL().GetDunningRuns(new DunningRunFilter { TimeSpan = LogTimeSpan.Last30Days, CustomerI3Ds = new List { this._referenceCustomerI3D } }, this.GetLoggedInUser()); this.Verifier.Verify("SingleDunningRun", runs); } } private void ResetDunningRun() { using (var session = new BLSession()) { var dunningRunNumber = 9; session.GetBL().ResetDunningRun(dunningRunNumber, this.GetLoggedInUser()); var resetDunningRun = session.GetBL().GetDunningRuns(new DunningRunFilter { DunningRunNumbers = new List { dunningRunNumber } }, this.GetLoggedInUser()); this.Verifier.Verify("ResetSingleDunningRun", resetDunningRun); } } } }