using Centron.BusinessLogic; using Centron.BusinessLogic.Administration.Settings; using Centron.BusinessLogic.EmployeeArea; using Centron.BusinessLogic.Sales.Receipts.DataAndResults; using Centron.BusinessLogic.WebServices.Sales.Receipts; using Centron.BusinessLogic.WebServices.Warehousing; using Centron.Data.Entities.Administration.Logins; using Centron.Data.WebServices.Sales.Receipts; using Centron.Data.WebServices.Sales.Receipts.DeliveryLists; using Centron.Data.WebServices.Sales.Receipts.Invoices; using Centron.Data.WebServices.Sales.Receipts.Orders; using Centron.Data.WebServices.Warehousing; using Centron.Interfaces; using Centron.Interfaces.Administration.Environment; using Centron.Interfaces.Sales.Receipts; using Centron.Interfaces.Sales.Receipts.InsertReceipts; using Centron.Tests.EndToEnd.Infrastructure; using CentronSoftware.Centron.WebServices.EntitiesWrongPlace.Sales.Receipts.DataAndResults; using DevExpress.Utils.Filtering.Internal; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CentronSoftware.Centron.WebServices.Extensions; using Xunit; using Xunit.Abstractions; namespace Centron.Tests.EndToEnd.Tests.ReceiptTaxRate { public class ReceiptTaxRateTest : EndToEndTest { public ReceiptTaxRateTest(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { // Ignore preparation-date because it changes depending on DateTime.Now - when the Test is executed this.Verifier.Settings.IgnoreProperty(f => f.PreparationDate); this.Verifier.Settings.IgnoreProperty(f => f.PreparationDate); this.Verifier.Settings.IgnoreProperty(f => f.PreparationDate); this.Verifier.Settings.IgnoreProperty(f => f.PreparationDate); this.Verifier.Settings.IgnoreProperty(f => f.PreparationDate); this.Verifier.Settings.IgnoreProperty(f => f.PreparationDate); } const int CurrentTaxRateI3D = 4; const decimal CurrentTaxRate = 19; const int CurrentYear = 2020; const int OldTaxRateI3D = 1; const decimal OldTaxRate = 16; const int OldYear = 2002; const int ArticleI3D = 4348; const int ProductFamilyI3D = 2; const int OldOfferI3D = 3; public override void Execute() { this.PrepareDatabase(); // The database looks like this // We have a active tax (19%) rate // We also have a previous tax rate (16%) configured that was active until 31.12.2006 // If we create a invoice with date AFTER 31.12.2006 we should get the new tax rate this.ValidateTaxRateInNewReceiptItemsForDate(CurrentTaxRateI3D, CurrentTaxRate, new DateTime(CurrentYear, month: 1, day: 1)); // Also if it was JUST AFTER 31.12.2006 we should get the new tax rate this.ValidateTaxRateInNewReceiptItemsForDate(CurrentTaxRateI3D, CurrentTaxRate, new DateTime(2007, month: 1, day: 1)); // But if we create a invoice with date BEFORE 31.12.2006 we should get the old tax rate this.ValidateTaxRateInNewReceiptItemsForDate(OldTaxRateI3D, OldTaxRate, new DateTime(OldYear, month: 1, day: 1)); // If the invoice is on the LAST DATE 31.12.2006, we should get the old tax rate this.ValidateTaxRateInNewReceiptItemsForDate(OldTaxRateI3D, OldTaxRate, new DateTime(2006, month: 12, day: 31)); // Time component should be ignored this.ValidateTaxRateInNewReceiptItemsForDate(OldTaxRateI3D, OldTaxRate, new DateTime(2006, month: 12, day: 31, hour: 12, minute: 30, second: 12)); // If the article has the old tax rate, it should still go through the chain, and select the newer tax-rate if needed this.ValidateTaxRateInNewReceiptItemsWithOldTaxRateOnArticle(); // Forwarding only takes the origin receipt tax rate, if it's a delivery-list or invoice (customer and supplier) // So we expect the new tax rate for the next 2 tests this.ValidateTaxForwarding(CentronObjectKindNumeric.OfferClass, CentronObjectKindNumeric.OrderClass, expectNewTaxRate: true); this.ValidateTaxForwarding(CentronObjectKindNumeric.OrderClass, CentronObjectKindNumeric.DeliveryListClass, expectNewTaxRate: true); // But we expect the old tax rate from the origin receipt for these 2 tests this.ValidateTaxForwarding(CentronObjectKindNumeric.DeliveryListClass, CentronObjectKindNumeric.InvoiceClass, expectNewTaxRate: false); this.ValidateTaxForwarding(CentronObjectKindNumeric.InvoiceClass, CentronObjectKindNumeric.CreditVoucherClass, expectNewTaxRate: false); // If the receipt was created in old times with the old tax-rates // And you create a new version, of it (ReceiptBL.CreateNewVersion) // You usually get asked, whether you want to update the receipt date // If you select yes, the tax-rates should get updated to now too this.ValidateCreateNewVersionUpdatesTaxRates(); // When creating a new version, and updating the date // The tax rates should be updates, as seen in the method before this // BUT, it should respect origin-receipt dates for the tax-calculation // if the tax is forwarded from the origin-receipt, for example delivery-lists forward their tax-rates this.ValidateCreateNewVersionUpdatesTaxRatesButRespectsDeliveryListDate(); // When the product family updates the tax rates // The product-family duration should be added to the licensedate for tax date calculation this.ValidateTaxRateRespectsProductFamilyLicenseDate(); } private void PrepareDatabase() { // Oops, I wrote this test using this article I3D // But now when creating and saving a invoice, I notice that this article needs barcodes... // So I deactivate it rather than re-create all the *.expected.txt files again this.Sql($"UPDATE ARTIK SET BarcodeScanen = 0 WHERE I3D = {ArticleI3D}"); // And another oops, same with "kommissionieren" this.Sql($"UPDATE ARTIK SET Kommisionieren = 0 WHERE I3D = {ArticleI3D}"); // Prepare the offer to have old tax rates and old date this.Sql($"UPDATE AngKopf SET Datum = '{OldYear}-01-01' WHERE I3D = {OldOfferI3D}"); this.Sql($"UPDATE AngPos SET MwstI3D = {OldTaxRateI3D}, MwstSatz = {OldTaxRate} WHERE AngKopfI3D = {OldOfferI3D} AND MwstI3D IS NOT NULL"); // Make invoices update their date on new-version this.Sql($"UPDATE Stammdat SET Wert = 2 WHERE I3D = {(int)AppSettingsConst.NewInvoiceVersionUpdateDate}"); // 2 is "ask user" // There is no Bankverbindung for the customer 10022 - but we are creating invoices for him // So we just move the existing Bankverbindung to customer 10022, so he can use it for this test this.Sql("UPDATE dbo.Bankverbindungen SET ObjectI3D = 10022"); } private void ValidateTaxRateInNewReceiptItemsForDate(int taxRateI3D, decimal taxRate, DateTime date) { var invoice = this.CreateNewReceipt(CentronObjectKindNumeric.InvoiceClass); invoice.Date = date; var receiptItem = this.CreateReceiptItem(invoice); this.Verifier.Verify($"ReceiptItems{date:ddMMyyyy_HHmmss}", receiptItem); Assert.Equal(taxRateI3D, receiptItem.VATI3D); Assert.Equal(taxRate, receiptItem.VATRate); } private void ValidateTaxRateInNewReceiptItemsWithOldTaxRateOnArticle() { this.Sql($"UPDATE ARTIK SET MWStI3D = {OldTaxRateI3D}, Mwst_Satz = {OldTaxRate} WHERE I3D = {ArticleI3D}"); this.ValidateTaxRateInNewReceiptItemsForDate(CurrentTaxRateI3D, CurrentTaxRate, new DateTime(CurrentYear + 1, month: 1, day: 1)); this.Sql($"UPDATE ARTIK SET MWStI3D = {CurrentTaxRateI3D}, Mwst_Satz = {CurrentTaxRate} WHERE I3D = {ArticleI3D}"); } private void ValidateTaxForwarding(CentronObjectKindNumeric originReceiptKind, CentronObjectKindNumeric targetReceiptKind, bool expectNewTaxRate) { var originReceipt = this.CreateNewReceipt(originReceiptKind); originReceipt.Date = new DateTime(year: OldYear, month: 1, day: 1); var receiptItem = this.CreateReceiptItem(originReceipt); Assert.Equal(OldTaxRateI3D, receiptItem.VATI3D); Assert.Equal(OldTaxRate, receiptItem.VATRate); var items = originReceipt.GetReceiptItems(); items.Add(receiptItem); originReceipt.SetReceiptItems(items); var savedOriginReceipt = this.SaveReceipt(originReceipt); var forwardedReceipt = this.ForwardReceipt(targetReceiptKind, savedOriginReceipt); this.Verifier.Verify($"Forwarded{originReceiptKind.GetReceiptShortName()}To{targetReceiptKind.GetReceiptShortName()}", forwardedReceipt); int expectedTaxRateI3D = expectNewTaxRate ? CurrentTaxRateI3D : OldTaxRateI3D; decimal expectedTaxRate = expectNewTaxRate ? CurrentTaxRate : OldTaxRate; var forwardedReceiptItem = forwardedReceipt.GetReceiptItems().Single(f => f.Kind == ReceiptItemKind.Article); Assert.Equal(forwardedReceiptItem.VATI3D, expectedTaxRateI3D); Assert.Equal(forwardedReceiptItem.VATRate, expectedTaxRate); } private void ValidateCreateNewVersionUpdatesTaxRates() { using (var session = new BLSession()) { var currentReceiptVersion = session.GetBL().GetReceiptByI3D(OldOfferI3D, CentronObjectKindNumeric.OfferClass); var currentReceiptItems = currentReceiptVersion.Data .GetReceiptItems() .Where(f => f.VATI3D != null).ToList(); // Ensure that the receipt curently has the old tax rates foreach (var item in currentReceiptItems) { Assert.Equal(OldTaxRateI3D, item.VATI3D); Assert.Equal(OldTaxRate, item.VATRate); } var newVersionReceipt = this.CreateNewVersion(CentronObjectKindNumeric.OfferClass, OldOfferI3D, updateReceiptDate: true); var receiptItems = newVersionReceipt .GetReceiptItems() .Where(f => f.VATI3D != null) .ToList(); this.Verifier.Verify("ReceiptItemsUpdatedTaxRateAfterNewVersion", receiptItems); // It should now have the new tax rates foreach (var item in receiptItems) { Assert.Equal(CurrentTaxRateI3D, item.VATI3D); Assert.Equal(CurrentTaxRate, item.VATRate); } } } private void ValidateCreateNewVersionUpdatesTaxRatesButRespectsDeliveryListDate() { // Create a old delivery-list var deliveryList = this.CreateNewReceipt(CentronObjectKindNumeric.DeliveryListClass); deliveryList.Date = new DateTime(OldYear, month: 1, day: 1); // Create a delivery-list item var deliveryListReceiptItems = deliveryList.GetReceiptItems().ToList(); var deliveryListReceiptItem = this.CreateReceiptItem(deliveryList); deliveryListReceiptItems.Add(deliveryListReceiptItem); deliveryList.SetReceiptItems(deliveryListReceiptItems); // The delivery-list is old, it should have the old tax-rate Assert.Equal(OldTaxRateI3D, deliveryListReceiptItem.VATI3D); Assert.Equal(OldTaxRate, deliveryListReceiptItem.VATRate); // Save delivery-list var savedDeliveryList = this.SaveReceipt(deliveryList); // Forward the delivery-list to an new invoice var invoice = this.ForwardReceipt(CentronObjectKindNumeric.InvoiceClass, savedDeliveryList); invoice.Date = new DateTime(CurrentYear, month: 1, day: 1); // The invoice should take-over the tax-rate from the delivery-list var forwardedInvoiceReceiptItem = invoice.GetReceiptItems().Single(f => f.VATI3D != null); Assert.Equal(OldTaxRateI3D, forwardedInvoiceReceiptItem.VATI3D); Assert.Equal(OldTaxRate, forwardedInvoiceReceiptItem.VATRate); // Create a new invoice-item that should get the new tax-rate var invoiceReceiptItems = invoice.GetReceiptItems().ToList(); var createdInvoiceReceiptItem = this.CreateReceiptItem(invoice); Assert.Equal(CurrentTaxRateI3D, createdInvoiceReceiptItem.VATI3D); Assert.Equal(CurrentTaxRate, createdInvoiceReceiptItem.VATRate); invoiceReceiptItems.Add(createdInvoiceReceiptItem); invoice.SetReceiptItems(invoiceReceiptItems); // Set tax on the new invoice-item to the old tax-rate, so something will get updated in CreateNewVersion createdInvoiceReceiptItem.VATI3D = OldTaxRateI3D; createdInvoiceReceiptItem.VATRate = OldTaxRate; // Save the invoice var savedInvoice = this.SaveReceipt(invoice); // Create new version with date-update var newInvoiceVersion = (ReceiptInvoiceDTO)this.CreateNewVersion(CentronObjectKindNumeric.InvoiceClass, savedInvoice.I3D, updateReceiptDate: true); // The forwarded receipt-item should keep the origin-receipt tax-rate forwardedInvoiceReceiptItem = newInvoiceVersion.Items.Single(f => f.VATI3D != null && f.OriginKind != null); Assert.Equal(OldTaxRateI3D, forwardedInvoiceReceiptItem.VATI3D); Assert.Equal(OldTaxRate, forwardedInvoiceReceiptItem.VATRate); // The created receipt-item should get updated and get the current tax-rate createdInvoiceReceiptItem = newInvoiceVersion.Items.Single(f => f.VATI3D != null && f.OriginKind == null); Assert.Equal(CurrentTaxRateI3D, createdInvoiceReceiptItem.VATI3D); Assert.Equal(CurrentTaxRate, createdInvoiceReceiptItem.VATRate); // Create new version WITHOUT date-update newInvoiceVersion = (ReceiptInvoiceDTO)this.CreateNewVersion(CentronObjectKindNumeric.InvoiceClass, savedInvoice.I3D, updateReceiptDate: false); // The receipt-items should keep their old values forwardedInvoiceReceiptItem = newInvoiceVersion.Items.Single(f => f.VATI3D != null && f.OriginKind != null); Assert.Equal(OldTaxRateI3D, forwardedInvoiceReceiptItem.VATI3D); Assert.Equal(OldTaxRate, forwardedInvoiceReceiptItem.VATRate); createdInvoiceReceiptItem = newInvoiceVersion.Items.Single(f => f.VATI3D != null && f.OriginKind == null); Assert.Equal(OldTaxRateI3D, createdInvoiceReceiptItem.VATI3D); Assert.Equal(OldTaxRate, createdInvoiceReceiptItem.VATRate); } private void ValidateTaxRateRespectsProductFamilyLicenseDate() { // Prepare database this.Sql($"UPDATE ARTIK SET ProduktfamilieI3D = {ProductFamilyI3D} WHERE I3D = {ArticleI3D}"); this.Sql($"UPDATE Produktfamilie SET LaufzeitMonate = 36, LaufzeitEndeBestimmtMwSt = 1 WHERE I3D = {ProductFamilyI3D}"); // 3 years // Create a delivery-list var deliveryList = this.CreateNewReceipt(CentronObjectKindNumeric.DeliveryListClass); deliveryList.Date = new DateTime(OldYear, month: 1, day: 1); // Create a delivery-list item var deliveryListReceiptItems = deliveryList.GetReceiptItems().ToList(); var deliveryListReceiptItem = (ReceiptDeliveryListItemDTO)this.CreateReceiptItem(deliveryList); deliveryListReceiptItems.Add(deliveryListReceiptItem); deliveryList.SetReceiptItems(deliveryListReceiptItems); // Delivery-list item should have old tax-rates, because the LaufzeitMonate of the product-family is not long enough to get the new tax rate // -> OldYear (2002) + 3 = 2005 // -> New tax rate is after 31.12.2006 Assert.Equal(OldTaxRateI3D, deliveryListReceiptItem.VATI3D); Assert.Equal(OldTaxRate, deliveryListReceiptItem.VATRate); // Now the license-date is 2005, with the 3 more years of the product-family it should get the new tax rate deliveryListReceiptItem.LicenseDate = new DateTime(OldYear + 3, month: 1, day: 1); var updatedTaxRates = this.GetUpdatedTaxRates(deliveryList); Assert.Collection(updatedTaxRates, item1 => { Assert.False(item1.UpdateTaxRate); }, item2 => { Assert.False(item2.UpdateTaxRate); }, item3 => { Assert.False(item3.UpdateTaxRate); }, item4 => { Assert.True(item4.UpdateTaxRate); Assert.Equal(CurrentTaxRateI3D, item4.NextTaxRateI3D); Assert.Equal(CurrentTaxRate, item4.NextTaxRate); }); // Reset database this.Sql($"UPDATE ARTIK SET ProduktfamilieI3D = -1 WHERE I3D = {ArticleI3D}"); } private ReceiptBaseDTO CreateNewReceipt(CentronObjectKindNumeric receiptKind) { using (var session = new BLSession()) { var user = session.GetBL().GetAppUser(f => f.I3D == 11); var result = session.GetBL().CreateNewReceipt(receiptKind, new LoggedInUser(user), 10022, new CreateReceiptData(), null, null, true, false, true); return result.Data.Receipt; } } private ReceiptItemBaseDTO CreateReceiptItem(ReceiptBaseDTO receipt) { using (var session = new BLSession()) { var user = session.GetBL().GetAppUser(f => f.I3D == 11); var receiptItemsResult = session.GetBL().CreateArticleReceiptItems( receipt, false, ArticleI3D, null, null, -1, new LoggedInUser(user)); return receiptItemsResult.Data.ReceiptItems.Single(f => f.ArticleI3D == ArticleI3D); } } private ReceiptBaseDTO SaveReceipt(ReceiptBaseDTO originReceipt) { using (var session = new BLSession()) { var user = session.GetBL().GetAppUser(f => f.I3D == 11); var saveReceiptResult = session.GetBL().SaveReceipt( originReceipt, user, new SaveReceiptData { IgnoreCallbacks = true }, autoLockIfNewReceipt: false, CreatedThroughApplication.WebServices, receiptTemplateFolderI3D: null); Assert.NotNull(saveReceiptResult.Data.Receipt); return saveReceiptResult.Data.Receipt; } } private ReceiptBaseDTO ForwardReceipt(CentronObjectKindNumeric targetReceiptKind, ReceiptBaseDTO originReceipt) { using (var session = new BLSession()) { var user = session.GetBL().GetAppUser(f => f.I3D == 11); var forwardResult = session.GetBL().ForwardReceipt( targetReceiptKind, user, new ForwardReceiptData { CreateReceiptData = new CreateReceiptData { IgnoreCallbacks = true, }, CreateReceiptItemsFromExistingItemsData = new List { new() { IgnoreCallbacks = true, }, }, IgnoreCallbacks = true }, new List { new ReceiptToForward { ReceiptKind = originReceipt.ReceiptKind, ReceiptI3D = originReceipt.I3D } }, null, InsertReceiptTakeoverOptions.Reference, true, false); return forwardResult.Data.Receipt; } } private ReceiptBaseDTO CreateNewVersion(CentronObjectKindNumeric receiptKind, int receiptI3D, bool updateReceiptDate) { using (var session = new BLSession()) { var user = session.GetBL().GetAppUser(f => f.I3D == 11); var createNewVersionResult = session.GetBL().CreateNewVersion( receiptKind, receiptI3D, null, null, user, new CreateNewVersionData { UpdateReceiptDate = updateReceiptDate }); return createNewVersionResult.Data.Receipt; } } private List GetUpdatedTaxRates(ReceiptBaseDTO receipt) { using (var session = new BLSession()) { return session.GetBL().GetUpdatedTaxRates(receipt); } } } }