context: // <copyright file="SalesOrderItemPricingTests.cs" company="Allors bvba">
  // Copyright (c) Allors bvba. All rights reserved.
  // Licensed under the LGPL license. See LICENSE file in the project root for full license information.
  // </copyright>
  // <summary>Defines the MediaTests type.</summary>
  
  namespace Allors.Domain
  {
      using System;
      using Allors.Meta;
      using Xunit;
  
      public class SalesOrderItemPricingTests : DomainTest
      {
          private ProductCategory productCategory;
          private ProductCategory ancestorProductCategory;
          private ProductCategory parentProductCategory;
          private Good good;
          private readonly Good variantGood;
          private readonly Good variantGood2;
          private readonly Good virtualGood;
          private Part part;
          private Colour feature1;
          private Colour feature2;
          private Singleton internalOrganisation;
          private Organisation shipToCustomer;
          private Organisation billToCustomer;
          private Organisation supplier;
          private City kiev;
          private PostalAddress shipToContactMechanismMechelen;
          private PostalAddress shipToContactMechanismKiev;
          private BasePrice currentBasePriceGeoBoundary;
          private BasePrice currentGoodBasePrice;
          private readonly BasePrice currentVirtualGoodBasePrice;
          private BasePrice currentGood1Feature1BasePrice;
          private BasePrice currentFeature2BasePrice;
          private SupplierOffering goodPurchasePrice;
          private SalesOrder order;
          private VatRate vatRate21;
  
          public SalesOrderItemPricingTests()
          {
              var euro = new Currencies(this.Session).FindBy(M.Currency.IsoCode, "EUR");
  
              this.internalOrganisation = this.Session.GetSingleton();
  
              this.supplier = new OrganisationBuilder(this.Session).WithName("supplier").Build();
  
              this.vatRate21 = new VatRateBuilder(this.Session).WithRate(21).Build();
  
              var mechelen = new CityBuilder(this.Session).WithName("Mechelen").Build();
              this.kiev = new CityBuilder(this.Session).WithName("Kiev").Build();
  
              this.shipToContactMechanismMechelen = new PostalAddressBuilder(this.Session).WithPostalAddressBoundary(mechelen).WithAddress1("Haverwerf 15").Build();
              this.shipToContactMechanismKiev = new PostalAddressBuilder(this.Session).WithPostalAddressBoundary(this.kiev).WithAddress1("Dnieper").Build();
              this.shipToCustomer = new OrganisationBuilder(this.Session).WithName("shipToCustomer").Build();
              this.shipToCustomer.AddPartyContactMechanism(new PartyContactMechanismBuilder(this.Session)
                                                              .WithContactMechanism(this.shipToContactMechanismKiev)
                                                              .WithContactPurpose(new ContactMechanismPurposes(this.Session).ShippingAddress)
                                                              .WithUseAsDefault(true)
                                                              .Build());
              this.Session.Derive();
  
              this.billToCustomer = new OrganisationBuilder(this.Session)
                  .WithName("billToCustomer")
                  .WithPreferredCurrency(euro)
                  .Build();
  
              new CustomerRelationshipBuilder(this.Session).WithFromDate(this.Session.Now()).WithCustomer(this.billToCustomer).Build();
              new CustomerRelationshipBuilder(this.Session).WithFromDate(this.Session.Now()).WithCustomer(this.shipToCustomer).Build();
  
              this.ancestorProductCategory = new ProductCategoryBuilder(this.Session)
                  .WithName("ancestor")
                  .Build();
  
              this.parentProductCategory = new ProductCategoryBuilder(this.Session)
                  .WithName("parent")
                  .WithPrimaryParent(this.ancestorProductCategory)
                  .Build();
  
              this.productCategory = new ProductCategoryBuilder(this.Session)
                  .WithName("gizmo")
                  .Build();
  
              this.productCategory.AddSecondaryParent(this.parentProductCategory);
  
              this.part = new NonUnifiedPartBuilder(this.Session)
                  .WithProductIdentification(new PartNumberBuilder(this.Session)
                      .WithIdentification("1")
                      .WithProductIdentificationType(new ProductIdentificationTypes(this.Session).Part).Build())
                  .WithInventoryItemKind(new InventoryItemKinds(this.Session).NonSerialised)
                  .Build();
  
              this.good = new NonUnifiedGoodBuilder(this.Session)
                  .WithProductIdentification(new ProductNumberBuilder(this.Session)
                      .WithIdentification("10101")
                      .WithProductIdentificationType(new ProductIdentificationTypes(this.Session).Good).Build())
                  .WithName("good")
                  .WithPart(this.part)
                  .WithUnitOfMeasure(new UnitsOfMeasure(this.Session).Piece)
                  .Build();
  
              this.productCategory.AddProduct(this.good);
  
              this.variantGood = new NonUnifiedGoodBuilder(this.Session)
                  .WithProductIdentification(new ProductNumberBuilder(this.Session)
                      .WithIdentification("v10101")
                      .WithProductIdentificationType(new ProductIdentificationTypes(this.Session).Good).Build())
                  .WithName("variant good")
                  .WithUnitOfMeasure(new UnitsOfMeasure(this.Session).Piece)
                  .WithPart(new NonUnifiedPartBuilder(this.Session)
                      .WithProductIdentification(new PartNumberBuilder(this.Session)
                          .WithIdentification("p1")
                          .WithProductIdentificationType(new ProductIdentificationTypes(this.Session).Part).Build())
                      .WithInventoryItemKind(new InventoryItemKinds(this.Session).NonSerialised).Build())
                  .Build();
  
              this.parentProductCategory.AddProduct(this.variantGood);
  
              this.variantGood2 = new NonUnifiedGoodBuilder(this.Session)
                  .WithProductIdentification(new ProductNumberBuilder(this.Session)
                      .WithIdentification("v10102")
                      .WithProductIdentificationType(new ProductIdentificationTypes(this.Session).Good).Build())
                  .WithName("variant good2")
                  .WithPart(new NonUnifiedPartBuilder(this.Session)
                      .WithProductIdentification(new PartNumberBuilder(this.Session)
                          .WithIdentification("p2")
                          .WithProductIdentificationType(new ProductIdentificationTypes(this.Session).Part).Build())
                      .WithInventoryItemKind(new InventoryItemKinds(this.Session).NonSerialised).Build())
                  .Build();
  
              this.parentProductCategory.AddProduct(this.variantGood2);
  
              this.virtualGood = new NonUnifiedGoodBuilder(this.Session)
                  .WithProductIdentification(new ProductNumberBuilder(this.Session)
                      .WithIdentification("v101")
                      .WithProductIdentificationType(new ProductIdentificationTypes(this.Session).Good).Build())
                  .WithName("virtual good")
                  .WithVariant(this.variantGood)
                  .WithVariant(this.variantGood2)
                  .WithUnitOfMeasure(new UnitsOfMeasure(this.Session).Piece)
                  .Build();
  
              this.parentProductCategory.AddProduct(this.virtualGood);
  
              this.goodPurchasePrice = new SupplierOfferingBuilder(this.Session)
                  .WithPart(this.part)
                  .WithSupplier(this.supplier)
                  .WithUnitOfMeasure(new UnitsOfMeasure(this.Session).Piece)
                  .WithFromDate(this.Session.Now().AddMinutes(-1))
                  .WithPrice(7)
                  .WithCurrency(euro)
                  .Build();
  
              this.feature1 = new ColourBuilder(this.Session)
                  .WithName("white")
                  .Build();
  
              this.feature2 = new ColourBuilder(this.Session)
                  .WithName("black")
                  .Build();
  
              this.currentBasePriceGeoBoundary = new BasePriceBuilder(this.Session)
                  .WithDescription("current BasePriceGeoBoundary ")
                  .WithGeographicBoundary(mechelen)
                  .WithProduct(this.good)
                  .WithPrice(8)
                  .WithFromDate(this.Session.Now().AddMinutes(-1))
                  .Build();
  
              // previous basePrice for good
              new BasePriceBuilder(this.Session).WithDescription("previous good")
                  .WithProduct(this.good)
                  .WithPrice(8)
                  .WithFromDate(this.Session.Now().AddYears(-1))
                  .WithThroughDate(this.Session.Now().AddDays(-1))
                  .Build();
  
              // future basePrice for good
              new BasePriceBuilder(this.Session).WithDescription("future good")
                  .WithProduct(this.good)
                  .WithPrice(11)
                  .WithFromDate(this.Session.Now().AddYears(1))
                  .Build();
  
              this.currentGoodBasePrice = new BasePriceBuilder(this.Session)
                  .WithDescription("current good")
                  .WithProduct(this.good)
                  .WithPrice(10)
                  .WithFromDate(this.Session.Now().AddMinutes(-1))
                  .WithThroughDate(this.Session.Now().AddYears(1).AddDays(-1))
                  .Build();
  
              // previous basePrice for feature1
              new BasePriceBuilder(this.Session).WithDescription("previous feature1")
                  .WithProductFeature(this.feature1)
                  .WithPrice(0.5M)
                  .WithFromDate(this.Session.Now().AddYears(-1))
                  .WithThroughDate(this.Session.Now().AddDays(-1))
                  .Build();
  
              // future basePrice for feature1
              new BasePriceBuilder(this.Session).WithDescription("future feature1")
                  .WithProductFeature(this.feature1)
                  .WithPrice(2.5M)
                  .WithFromDate(this.Session.Now().AddYears(1))
                  .Build();
  
              new BasePriceBuilder(this.Session)
                  .WithDescription("current feature1")
                  .WithProductFeature(this.feature1)
                  .WithPrice(2)
                  .WithFromDate(this.Session.Now().AddMinutes(-1))
                  .WithThroughDate(this.Session.Now().AddYears(1).AddDays(-1))
                  .Build();
  
              // previous basePrice for feature2
              new BasePriceBuilder(this.Session).WithDescription("previous feature2")
                  .WithProductFeature(this.feature2)
                  .WithPrice(2)
                  .WithFromDate(this.Session.Now().AddYears(-1))
                  .WithThroughDate(this.Session.Now().AddDays(-1))
                  .Build();
  
              // future basePrice for feature2
              new BasePriceBuilder(this.Session)
                  .WithDescription("future feature2")
                  .WithProductFeature(this.feature2)
                  .WithPrice(4)
                  .WithFromDate(this.Session.Now().AddYears(1))
                  .Build();
  
              this.currentFeature2BasePrice = new BasePriceBuilder(this.Session)
                  .WithDescription("current feature2")
                  .WithProductFeature(this.feature2)
                  .WithPrice(3)
                  .WithFromDate(this.Session.Now().AddMinutes(-1))
                  .WithThroughDate(this.Session.Now().AddYears(1).AddDays(-1))
                  .Build();
  
              // previous basePrice for good with feature1
              new BasePriceBuilder(this.Session).WithDescription("previous good/feature1")
                  .WithProduct(this.good)
                  .WithProductFeature(this.feature1)
                  .WithPrice(4)
                  .WithFromDate(this.Session.Now().AddYears(-1))
                  .WithThroughDate(this.Session.Now().AddDays(-1))
                  .Build();
  
              // future basePrice for good with feature1
              new BasePriceBuilder(this.Session)
                  .WithDescription("future good/feature1")
                  .WithProduct(this.good)
                  .WithProductFeature(this.feature1)
                  .WithPrice(6)
                  .WithFromDate(this.Session.Now().AddYears(1))
                  .Build();
  
              this.currentGood1Feature1BasePrice = new BasePriceBuilder(this.Session)
                  .WithDescription("current good/feature1")
                  .WithProduct(this.good)
                  .WithProductFeature(this.feature1)
                  .WithPrice(5)
                  .WithFromDate(this.Session.Now().AddMinutes(-1))
                  .WithThroughDate(this.Session.Now().AddYears(1).AddDays(-1))
                  .Build();
  
              this.currentVirtualGoodBasePrice = new BasePriceBuilder(this.Session)
                  .WithDescription("current virtual good")
                  .WithProduct(this.virtualGood)
                  .WithPrice(10)
                  .WithFromDate(this.Session.Now().AddMinutes(-1))
                  .Build();
  
              new BasePriceBuilder(this.Session)
                  .WithDescription("current variant good2")
                  .WithProduct(this.variantGood2)
                  .WithPrice(11)
                  .WithFromDate(this.Session.Now().AddMinutes(-1))
                  .Build();
  
              this.Session.Derive();
              this.Session.Commit();
  
              this.order = new SalesOrderBuilder(this.Session)
                  .WithShipToCustomer(this.shipToCustomer)
                  .WithBillToCustomer(this.billToCustomer)
                  .WithAssignedBillToContactMechanism(this.shipToContactMechanismMechelen)
                  .Build();
  
              this.Session.Derive();
              this.Session.Commit();
  
              this.order.SetReadyForPosting();
              this.Session.Derive();
  
              this.order.Post();
              this.Session.Derive();
  
              order.Accept();
              this.Session.Derive(true);
  
              this.Session.Commit();
          }
  
          [Fact]
          public void GivenOrderItem_WhenBuild_ThenPostBuildRelationsMustExist()
          {
              this.InstantiateObjects(this.Session);
  
              var item = new SalesOrderItemBuilder(this.Session).WithInvoiceItemType(new InvoiceItemTypes(this.Session).ProductItem).Build();
  
              Assert.Equal(new SalesOrderItemStates(this.Session).Provisional, item.SalesOrderItemState);
          }
  
          [Fact]
          public void GivenOrderItemForGood1WithActualPrice_WhenDerivingPrices_ThenUseActualPrice()
          {
              this.InstantiateObjects(this.Session);
  
              var item1 = new SalesOrderItemBuilder(this.Session)
                  .WithProduct(this.good)
                  .WithQuantityOrdered(3)
                  .WithAssignedUnitPrice(15)
                  .WithAssignedVatRegime(new VatRegimes(this.Session).DutchStandardTariff)
                  .Build();
  
              this.order.AddSalesOrderItem(item1);
              this.Session.Derive();
  
              Assert.Equal(10, item1.UnitBasePrice);
              Assert.Equal(0, item1.UnitDiscount);
              Assert.Equal(0, item1.UnitSurcharge);
              Assert.Equal(15, item1.UnitPrice);
              Assert.Equal(3.15m, item1.UnitVat);
              Assert.Equal(30, item1.TotalBasePrice);
              Assert.Equal(0, item1.TotalDiscount);
              Assert.Equal(0, item1.TotalSurcharge);
              Assert.Equal(45, item1.TotalExVat);
              Assert.Equal(9.45m, item1.TotalVat);
              Assert.Equal(54.45m, item1.TotalIncVat);
  
              Assert.Equal(30, this.order.TotalBasePrice);
              Assert.Equal(0, this.order.TotalDiscount);
              Assert.Equal(0, this.order.TotalSurcharge);
              Assert.Equal(45, this.order.TotalExVat);
              Assert.Equal(9.45m, this.order.TotalVat);
              Assert.Equal(54.45m, this.order.TotalIncVat);
              Assert.Equal(45, this.order.TotalListPrice);
          }
  
          [Fact]
          public void GivenOrderItemForGood1_WhenDerivingPrices_ThenUsePriceComponentsForGood1()
          {
              this.InstantiateObjects(this.Session);
  
              const decimal quantityOrdered = 3;
              var item1 = new SalesOrderItemBuilder(this.Session)
                  .WithProduct(this.good)
                  .WithQuantityOrdered(quantityOrdered)
                  .WithAssignedVatRegime(new VatRegimes(this.Session).DutchStandardTariff)
                  .Build();
  
              this.order.AddSalesOrderItem(item1);
              this.Session.Derive();
  
              Assert.Equal(this.currentGoodBasePrice.Price, item1.UnitBasePrice);
              Assert.Equal(0, item1.UnitDiscount);
              Assert.Equal(0, item1.UnitSurcharge);
              Assert.Equal(this.currentGoodBasePrice.Price, item1.UnitPrice);
              Assert.Equal(Math.Round(item1.UnitPrice * this.vatRate21.Rate / 100, 2), item1.UnitVat);
  
              Assert.Equal(this.currentGoodBasePrice.Price * quantityOrdered, item1.TotalBasePrice);
              Assert.Equal(0, item1.TotalDiscount);
              Assert.Equal(0, item1.TotalSurcharge);
              Assert.Equal(this.currentGoodBasePrice.Price * quantityOrdered, item1.TotalExVat);
              Assert.Equal(Math.Round(item1.UnitPrice * this.vatRate21.Rate / 100, 2) * quantityOrdered, item1.TotalVat);
  
              Assert.Equal(this.currentGoodBasePrice.Price * quantityOrdered, this.order.TotalBasePrice);
              Assert.Equal(0, this.order.TotalDiscount);
              Assert.Equal(0, this.order.TotalSurcharge);
              Assert.Equal(this.currentGoodBasePrice.Price * quantityOrdered, this.order.TotalExVat);
              Assert.Equal(Math.Round(item1.UnitPrice * this.vatRate21.Rate / 100, 2) * quantityOrdered, this.order.TotalVat);
          }
  
          [Fact]
          public void GivenOrderItemForGood1WithFeature1_WhenDerivingPrices_ThenUsePriceComponentsForGood1WithFeature1()
          {
              this.InstantiateObjects(this.Session);
  
              const decimal quantityOrdered = 3;
              var item1 = new SalesOrderItemBuilder(this.Session).WithProduct(this.good).WithQuantityOrdered(quantityOrdered).Build();
              var item2 = new SalesOrderItemBuilder(this.Session).WithInvoiceItemType(new InvoiceItemTypes(this.Session).ProductFeatureItem).WithProductFeature(this.feature1).WithQuantityOrdered(quantityOrdered).Build();
              item1.AddOrderedWithFeature(item2);
              this.order.AddSalesOrderItem(item1);
              this.order.AddSalesOrderItem(item2);
              this.Session.Derive();
  
              var expectedCalculatedUnitPrice = this.currentGoodBasePrice.Price;
              expectedCalculatedUnitPrice += this.currentGood1Feature1BasePrice.Price;
  
              var expectedTotalBasePrice = this.currentGoodBasePrice.Price * quantityOrdered;
              expectedTotalBasePrice += this.currentGood1Feature1BasePrice.Price * quantityOrdered;
  
              var expectedTotalExVat = this.currentGoodBasePrice.Price * quantityOrdered;
              expectedTotalExVat += this.currentGood1Feature1BasePrice.Price * quantityOrdered;
  
              Assert.Equal(expectedCalculatedUnitPrice, item1.UnitBasePrice);
              Assert.Equal(0, item1.UnitDiscount);
              Assert.Equal(0, item1.UnitSurcharge);
              Assert.Equal(expectedCalculatedUnitPrice, item1.UnitPrice);
              Assert.Equal(expectedTotalBasePrice, item1.TotalBasePrice);
              Assert.Equal(0, item1.TotalDiscount);
              Assert.Equal(0, item1.TotalSurcharge);
              Assert.Equal(expectedTotalExVat, item1.TotalExVat);
  
              Assert.Equal(this.currentGood1Feature1BasePrice.Price, item2.UnitBasePrice);
              Assert.Equal(0, item2.UnitDiscount);
              Assert.Equal(0, item2.UnitSurcharge);
              Assert.Equal(this.currentGood1Feature1BasePrice.Price, item2.UnitPrice);
              Assert.Equal(this.currentGood1Feature1BasePrice.Price * quantityOrdered, item2.TotalBasePrice);
              Assert.Equal(0, item2.TotalDiscount);
              Assert.Equal(0, item2.TotalSurcharge);
              Assert.Equal(this.currentGood1Feature1BasePrice.Price * quantityOrdered, item2.TotalExVat);
  
              Assert.Equal(expectedTotalBasePrice, this.order.TotalBasePrice);
              Assert.Equal(0, this.order.TotalDiscount);
              Assert.Equal(0, this.order.TotalSurcharge);
              Assert.Equal(expectedTotalExVat, this.order.TotalExVat);
          }
  
          [Fact]
          public void GivenOrderItemForGood1WithFeature2_WhenDerivingPrices_ThenUsePriceComponentsForGood1AndFeature2()
          {
              this.InstantiateObjects(this.Session);
  
              const decimal quantityOrdered = 3;
              var item1 = new SalesOrderItemBuilder(this.Session).WithProduct(this.good).WithQuantityOrdered(quantityOrdered).Build();
              var item2 = new SalesOrderItemBuilder(this.Session).WithInvoiceItemType(new InvoiceItemTypes(this.Session).ProductFeatureItem).WithProductFeature(this.feature2).WithQuantityOrdered(quantityOrdered).Build();
              item1.AddOrderedWithFeature(item2);
              this.order.AddSalesOrderItem(item1);
              this.order.AddSalesOrderItem(item2);
  
              this.Session.Derive();
  
              var expectedCalculatedUnitPrice = this.currentGoodBasePrice.Price;
              expectedCalculatedUnitPrice += this.currentFeature2BasePrice.Price;
  
              var expectedTotalBasePrice = this.currentGoodBasePrice.Price * quantityOrdered;
              expectedTotalBasePrice += this.currentFeature2BasePrice.Price * quantityOrdered;
  
              var expectedTotalExVat = this.currentGoodBasePrice.Price * quantityOrdered;
              expectedTotalExVat += this.currentFeature2BasePrice.Price * quantityOrdered;
  
              Assert.Equal(expectedCalculatedUnitPrice, item1.UnitBasePrice);
              Assert.Equal(0, item1.UnitDiscount);
              Assert.Equal(0, item1.UnitSurcharge);
              Assert.Equal(expectedCalculatedUnitPrice, item1.UnitPrice);
              Assert.Equal(expectedTotalBasePrice, item1.TotalBasePrice);
              Assert.Equal(0, item1.TotalDiscount);
              Assert.Equal(0, item1.TotalSurcharge);
              Assert.Equal(expectedTotalExVat, item1.TotalExVat);
  
              Assert.Equal(this.currentFeature2BasePrice.Price, item2.UnitBasePrice);
              Assert.Equal(0, item2.UnitDiscount);
              Assert.Equal(0, item2.UnitSurcharge);
              Assert.Equal(this.currentFeature2BasePrice.Price, item2.UnitPrice);
              Assert.Equal(this.currentFeature2BasePrice.Price * quantityOrdered, item2.TotalBasePrice);
              Assert.Equal(0, item2.TotalDiscount);
              Assert.Equal(0, item2.TotalSurcharge);
              Assert.Equal(this.currentFeature2BasePrice.Price * quantityOrdered, item2.TotalExVat);
  
              Assert.Equal(expectedTotalBasePrice, this.order.TotalBasePrice);
              Assert.Equal(0, this.order.TotalDiscount);
              Assert.Equal(0, this.order.TotalSurcharge);
              Assert.Equal(expectedTotalExVat, this.order.TotalExVat);
          }
  
          [Fact]
          public void GivenProductWithMultipleBasePrices_WhenDeriving_ThenLowestUnitPriceMustBeCalculated()
          {
              this.InstantiateObjects(this.Session);
  
              this.order.Revise();
              this.Session.Derive();
  
              this.order.AssignedShipToAddress = this.shipToContactMechanismMechelen;
              this.Session.Derive();
  
              var item1 = new SalesOrderItemBuilder(this.Session).WithProduct(this.good).WithQuantityOrdered(3).Build();
              this.order.AddSalesOrderItem(item1);
  
              this.Session.Derive();
  
              Assert.Equal(this.currentBasePriceGeoBoundary.Price, item1.UnitPrice);
  
  
gt:             var order2 = new SalesOrderBuilder(this.Session)