using System; using System.Collections.Generic; using Centron.BusinessLogic; using Centron.BusinessLogic.Sales.Receipts.DataAndResults; using Centron.BusinessLogic.WebServices.Sales.Receipts; using Centron.Core.Extensions; using Centron.Data.WebServices.Sales.Receipts.Invoices; using Centron.Interfaces; using Centron.Interfaces.Administration.Environment; using Centron.Interfaces.BL; using Centron.Interfaces.Sales.Receipts; using Centron.Tests.EndToEnd.Infrastructure; using CentronSoftware.Centron.WebServices.Entities.Sales.Receipts; using Xunit.Abstractions; namespace Centron.Tests.EndToEnd.Tests.ReceiptProvisionTiers; public class ReceiptProvisionEmployeeLevelTests : EndToEndTest { public ReceiptProvisionEmployeeLevelTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } private const int EmployeeI3D = 10; private const int Invoice1I3D = 4408; private const int Invoice2I3D = 5414; public override void Execute() { this.PrepareDatabase(); this.SaveLevels(step: 1); this.LoadLevels(step: 2); this.SingleInvoice(step: 3); this.MultiInvoice(step: 4); this.MoveProvisionToOtherEmployee(step: 5); this.ChangeInvoiceDate(step: 6); // Ticket 163976: tiers without FromPrice=0 used to yield [null] in GetLevels and NRE in PrepareLevelsOverwriteLowerLevels this.LevelsWithoutZeroTier_BelowFirstTier(step: 7); // Ticket 163972: bonus of just-reached tier was dropped when invoice sum exactly equaled tier FromPrice this.InvoiceExactlyOnTierBoundary(step: 8); } private void PrepareDatabase() { // Fix broken tables this.Sql("ALTER TABLE RechKopf DROP COLUMN LcmStatus"); this.Sql("ALTER TABLE GutKopf DROP COLUMN LcmStatus"); // Create a dummy provision-schema to enable provision-schemas instead of old ones this.WithSession(s => s.GetBL().SaveProvisionSchema(new ReceiptProvisionSchemaDTO { Name = "Dummy", }, this.GetLoggedInUser())); // Prepare some invoices for later this.Sql($"DELETE FROM RechPos WHERE RechKopfI3D = {Invoice1I3D} AND ISNULL(ArtikelI3D, 0) <> 117"); } private void SaveLevels(int step) { using var session = new BLSession(); var tiers = new List { new() { FromPrice = 0, ProvisionPercentage = 2, OverwriteLowerLevels = false, Bonus = null }, new() { FromPrice = 500, ProvisionPercentage = 3, OverwriteLowerLevels = true, Bonus = 100 }, new() { FromPrice = 1000, ProvisionPercentage = 4, OverwriteLowerLevels = false, Bonus = 200 }, new() { FromPrice = 2000, ProvisionPercentage = 5, OverwriteLowerLevels = false, Bonus = 300 }, }; session.GetBL().SaveEmployeeLevels(EmployeeI3D, tiers); this.Verifier.VerifyTable($"{step}_Levels_Table", "ReceiptProvisionEmployeeLevels"); } private void LoadLevels(int step) { using var session = new BLSession(); var tiers = session.GetBL().GetLevels(new() { EmployeeI3Ds = new List { EmployeeI3D } }); this.Verifier.Verify($"{step}_Levels", tiers); } private void SingleInvoice(int step) { this.UpdateInvoice(Invoice1I3D, 400); this.Verifier.VerifyTable($"{step}_Price_400", "ReceiptProvisionItems"); this.UpdateInvoice(Invoice1I3D, 800); this.Verifier.VerifyTable($"{step}_Price_800", "ReceiptProvisionItems"); this.UpdateInvoice(Invoice1I3D, 3000); this.Verifier.VerifyTable($"{step}_Price_3000", "ReceiptProvisionItems"); } private void MultiInvoice(int step) { // With the of Invoice-2 200 € we reach the 500 € level // That level overwrites the lower tiers, so we should should also get 3 % for the 400 € invoice this.UpdateInvoice(Invoice1I3D, 400); this.UpdateInvoice(Invoice2I3D, 200); this.Verifier.VerifyTable($"{step}.1_Price_600", "ReceiptProvisionItems"); // Now reduce it back down to 450 €, should go back to 2 % and no bonus this.UpdateInvoice(Invoice2I3D, 50); this.Verifier.VerifyTable($"{step}.2_Price_450", "ReceiptProvisionItems"); } private void MoveProvisionToOtherEmployee(int step) { // Cleanup first this.RemoveProvision(Invoice1I3D); this.RemoveProvision(Invoice2I3D); this.Verifier.VerifyTable($"{step}.1_Before", "ReceiptProvisionItems"); // Lets start with 1000 € for the first employee this.UpdateInvoice(Invoice1I3D, 1000); this.Verifier.VerifyTable($"{step}.2_OldEmployee", "ReceiptProvisionItems"); // Now we move the provision to another employee this.UpdateInvoiceAndChangeEmployee(Invoice1I3D, 1000, alternativeEmployeeI3D: 20); this.Verifier.VerifyTable($"{step}.3_NewEmployee", "ReceiptProvisionItems"); } private void ChangeInvoiceDate(int step) { // Prepare a test case, both invoices together in one month go over 500 € this.UpdateInvoice(Invoice1I3D, 400); this.UpdateInvoice(Invoice2I3D, 200); // Now we change the date of the first invoice to a new month this.UpdateInvoiceAndChangeDate(Invoice1I3D, new DateTime(2022, 1, 1)); // The provision should be recalculated for both invoices this.Verifier.VerifyTable($"{step}.1_NewMonth", "ReceiptProvisionItems"); } private void LevelsWithoutZeroTier_BelowFirstTier(int step) { using var session = new BLSession(); var tiers = new List { new() { FromPrice = 500, ProvisionPercentage = 3, OverwriteLowerLevels = false, Bonus = null }, new() { FromPrice = 1000, ProvisionPercentage = 4, OverwriteLowerLevels = false, Bonus = null }, }; session.GetBL().SaveEmployeeLevels(EmployeeI3D, tiers); this.Verifier.VerifyTable($"{step}_Levels_Table_NoZero", "ReceiptProvisionEmployeeLevels", $"EmployeeI3D = {EmployeeI3D}"); this.UpdateInvoice(Invoice1I3D, 400); this.Verifier.VerifyTable($"{step}_Price_400_BelowFirstTier", "ReceiptProvisionItems", $"ReceiptI3D = {Invoice1I3D}"); } private void InvoiceExactlyOnTierBoundary(int step) { // Stefan's repro from ticket 163972 (TestDB centronwes, RE 42321): tiers 1000/5000/10000 € with bonuses 100/500/1000 €. // Invoice exactly on a tier threshold (sum == FromPrice) used to drop that tier's bonus because of strict < check. using var session = new BLSession(); var tiers = new List { new() { FromPrice = 1000, ProvisionPercentage = 5, OverwriteLowerLevels = false, Bonus = 100 }, new() { FromPrice = 5000, ProvisionPercentage = 7, OverwriteLowerLevels = false, Bonus = 500 }, new() { FromPrice = 10000, ProvisionPercentage = 2, OverwriteLowerLevels = false, Bonus = 1000 }, }; session.GetBL().SaveEmployeeLevels(EmployeeI3D, tiers); this.Verifier.VerifyTable($"{step}_Levels_Stefan", "ReceiptProvisionEmployeeLevels", $"EmployeeI3D = {EmployeeI3D}"); // Invoice exactly on tier 2 boundary (5000): expected = 5%*1000 + 100 + 7%*4000 + 500 = 930 this.UpdateInvoice(Invoice1I3D, 5000); this.Verifier.VerifyTable($"{step}.1_Price_5000_OnTier2", "ReceiptProvisionItems", $"ReceiptI3D = {Invoice1I3D}"); // Invoice exactly on tier 1 boundary (1000): expected = 5%*1000 + 100 = 150 (count==1 short-circuit case) this.UpdateInvoice(Invoice1I3D, 1000); this.Verifier.VerifyTable($"{step}.2_Price_1000_OnTier1", "ReceiptProvisionItems", $"ReceiptI3D = {Invoice1I3D}"); } private void RemoveProvision(int invoiceI3D) { this.UpdateInvoice(invoiceI3D, 0, invoice => { invoice.Provision = []; }); } private void UpdateInvoiceAndChangeDate(int invoiceI3D, DateTime date) { this.UpdateInvoice(invoiceI3D, 0, invoice => { invoice.Date = date; }); } private void UpdateInvoiceAndChangeEmployee(int invoiceI3D, decimal price, int alternativeEmployeeI3D) { this.UpdateInvoice(invoiceI3D, price, invoice => { invoice.Provision[0].EmployeeI3D = alternativeEmployeeI3D; }); } private void UpdateInvoice(int invoiceI3D, decimal price, Action changeInvoice = null) { var invoice = (ReceiptInvoiceDTO)this.WithSession(s => s.GetBL().CreateNewVersion( CentronObjectKindNumeric.InvoiceClass, invoiceI3D, version: null, newContactPersonI3D: null, this.GetLoggedInUser().User, new CreateNewVersionData { IgnoreCallbacks = true, })).ThrowIfError().Receipt; invoice.Provision = [ new ReceiptProvision { Receiver = ReceiptProvisionReceiver.FixedEmployee, EmployeeI3D = EmployeeI3D, ProvisionPercentage = null, SharePercentage = 100, Source = ReceiptProvisionSource.All, Value = ReceiptProvisionValue.Sales, } ]; invoice.Items[0].BasePrice = price; invoice.Items[0].QuantityComplete = 1; changeInvoice?.Invoke(invoice); var savedInvoice = (ReceiptInvoiceDTO)this.WithSession(s => s.GetBL().SaveReceipt( invoice, this.GetLoggedInUser().User, new SaveReceiptData { IgnoreCallbacks = true, }, autoLockIfNewReceipt: false, CreatedThroughApplication.WebServices, receiptTemplateFolderI3D: null)).ThrowIfError().Receipt; } }