context: // <copyright file="ProductQuote.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>
  
  namespace Allors.Domain
  {
      using System;
      using System.Collections.Generic;
      using System.Linq;
  
      using Allors.Meta;
      using Allors.Services;
  
      using Microsoft.Extensions.DependencyInjection;
  
      public partial class ProductQuote
      {
          public bool IsDeletable =>
                (this.QuoteState.Equals(new QuoteStates(this.Strategy.Session).Created)
                    || this.QuoteState.Equals(new QuoteStates(this.Strategy.Session).Cancelled)
                    || this.QuoteState.Equals(new QuoteStates(this.Strategy.Session).Rejected))
                && !this.ExistRequest
                && !this.ExistSalesOrderWhereQuote
                && this.QuoteItems.All(v => v.IsDeletable);
  
          public static readonly TransitionalConfiguration[] StaticTransitionalConfigurations =
              {
                  new TransitionalConfiguration(M.ProductQuote, M.ProductQuote.QuoteState),
              };
  
          public TransitionalConfiguration[] TransitionalConfigurations => StaticTransitionalConfigurations;
  
          private bool BaseNeedsApproval => false;
  
          public void BaseSetReadyForProcessing(ProductQuoteSetReadyForProcessing method)
          {
              if (!method.Result.HasValue)
              {
                  this.QuoteState = this.BaseNeedsApproval
                      ? new QuoteStates(this.Strategy.Session).AwaitingApproval : new QuoteStates(this.Strategy.Session).InProcess;
  
                  method.Result = true;
              }
          }
  
          public void BaseOrder(ProductQuoteOrder method)
          {
              this.QuoteState = new QuoteStates(this.Strategy.Session).Ordered;
  
              var quoteItemStates = new QuoteItemStates(this.Session());
              foreach (QuoteItem quoteItem in this.QuoteItems)
              {
                  if (Equals(quoteItem.QuoteItemState, quoteItemStates.Accepted))
                  {
                      quoteItem.QuoteItemState = quoteItemStates.Ordered;
                  }
              }
  
              this.OrderThis();
          }
  
          public void BasePrint(PrintablePrint method)
          {
              if (!method.IsPrinted)
              {
                  var singleton = this.Strategy.Session.GetSingleton();
                  var logo = this.Issuer?.ExistLogoImage == true ?
                                 this.Issuer.LogoImage.MediaContent.Data :
                                 singleton.LogoImage.MediaContent.Data;
  
                  var images = new Dictionary<string, byte[]>
                                   {
                                       { "Logo1", logo },
                                       { "Logo2", logo },
                                   };
  
                  if (this.ExistQuoteNumber)
                  {
                      var session = this.Strategy.Session;
                      var barcodeService = session.ServiceProvider.GetRequiredService<IBarcodeService>();
                      var barcode = barcodeService.Generate(this.QuoteNumber, BarcodeType.CODE_128, 320, 80, pure: true);
                      images.Add("Barcode", barcode);
                  }
  
                  var printModel = new Print.ProductQuoteModel.Model(this, images);
                  this.RenderPrintDocument(this.Issuer?.ProductQuoteTemplate, printModel, images);
  
                  this.PrintDocument.Media.InFileName = $"{this.QuoteNumber}.odt";
              }
          }
  
          public void BaseOnDerive(ObjectOnDerive method)
          {
              var derivation = method.Derivation;
              var session = this.Session();
  
              this.ValidQuoteItems = this.QuoteItems.Where(v => v.IsValid).ToArray();
  
              var currentPriceComponents = new PriceComponents(session).CurrentPriceComponents(this.IssueDate);
  
              var quantityOrderedByProduct = this.ValidQuoteItems
                  .Where(v => v.ExistProduct)
                  .GroupBy(v => v.Product)
                  .ToDictionary(v => v.Key, v => v.Sum(w => w.Quantity));
  
              // First run to calculate price
              foreach (QuoteItem quoteItem in this.ValidQuoteItems)
              {
                  decimal quantityOrdered = 0;
  
                  if (quoteItem.ExistProduct)
                  {
                      quantityOrderedByProduct.TryGetValue(quoteItem.Product, out quantityOrdered);
                  }
  
                  foreach (QuoteItem featureItem in quoteItem.QuotedWithFeatures)
                  {
                      featureItem.Quantity = quoteItem.Quantity;
                      this.CalculatePrices(derivation, featureItem, currentPriceComponents, quantityOrdered, 0);
                  }
  
                  this.CalculatePrices(derivation, quoteItem, currentPriceComponents, quantityOrdered, 0);
              }
  
              var totalBasePriceByProduct = this.QuoteItems
                  .Where(v => v.ExistProduct)
                  .GroupBy(v => v.Product)
                  .ToDictionary(v => v.Key, v => v.Sum(w => w.TotalBasePrice));
  
              // Second run to calculate price (because of order value break)
              foreach (QuoteItem quoteItem in this.ValidQuoteItems)
              {
                  decimal quantityOrdered = 0;
                  decimal totalBasePrice = 0;
  
                  if (quoteItem.ExistProduct)
                  {
                      quantityOrderedByProduct.TryGetValue(quoteItem.Product, out quantityOrdered);
                      totalBasePriceByProduct.TryGetValue(quoteItem.Product, out totalBasePrice);
                  }
  
                  foreach (QuoteItem featureItem in quoteItem.QuotedWithFeatures)
                  {
                      this.CalculatePrices(derivation, featureItem, currentPriceComponents, quantityOrdered, totalBasePrice);
                  }
  
                  this.CalculatePrices(derivation, quoteItem, currentPriceComponents, quantityOrdered, totalBasePrice);
              }
  
              // SalesOrderItem Derivations and Validations
              foreach (QuoteItem quoteItem in this.ValidQuoteItems)
              {
                  var isSubTotalItem = quoteItem.ExistInvoiceItemType && (quoteItem.InvoiceItemType.IsProductItem || quoteItem.InvoiceItemType.IsPartItem);
                  if (isSubTotalItem)
                  {
                      if (quoteItem.Quantity == 0)
                      {
                          derivation.Validation.AddError(quoteItem, M.QuoteItem.Quantity, "Quantity is Required");
                      }
                  }
                  else
                  {
                      if (quoteItem.UnitPrice == 0)
                      {
                          derivation.Validation.AddError(quoteItem, M.QuoteItem.UnitPrice, "Price is Required");
                      }
                  }
              }
  
              // Calculate Totals
              this.TotalBasePrice = 0;
              this.TotalDiscount = 0;
              this.TotalSurcharge = 0;
              this.TotalExVat = 0;
              this.TotalFee = 0;
              this.TotalShippingAndHandling = 0;
              this.TotalExtraCharge = 0;
              this.TotalVat = 0;
              this.TotalIrpf = 0;
              this.TotalIncVat = 0;
              this.TotalListPrice = 0;
              this.GrandTotal = 0;
  
              foreach (QuoteItem quoteItem in this.ValidQuoteItems)
              {
                  if (!quoteItem.ExistQuoteItemWhereQuotedWithFeature)
                  {
                      this.TotalBasePrice += quoteItem.TotalBasePrice;
                      this.TotalDiscount += quoteItem.TotalDiscount;
                      this.TotalSurcharge += quoteItem.TotalSurcharge;
                      this.TotalExVat += quoteItem.TotalExVat;
                      this.TotalVat += quoteItem.TotalVat;
                      this.TotalIrpf += quoteItem.TotalIrpf;
                      this.TotalIncVat += quoteItem.TotalIncVat;
                      this.TotalListPrice += quoteItem.TotalExVat;
                      this.GrandTotal += quoteItem.GrandTotal;
                  }
              }
  
              var discount = 0M;
              var discountVat = 0M;
              var discountIrpf = 0M;
              var surcharge = 0M;
              var surchargeVat = 0M;
              var surchargeIrpf = 0M;
              var fee = 0M;
              var feeVat = 0M;
              var feeIrpf = 0M;
              var shipping = 0M;
              var shippingVat = 0M;
              var shippingIrpf = 0M;
              var miscellaneous = 0M;
              var miscellaneousVat = 0M;
              var miscellaneousIrpf = 0M;
  
              if (this.ExistIssueDate)
              {
                  this.DerivedVatRate = this.DerivedVatRegime?.VatRates.First(v => v.FromDate <= this.IssueDate && (!v.ExistThroughDate || v.ThroughDate >= this.IssueDate));
                  this.DerivedIrpfRate = this.DerivedIrpfRegime?.IrpfRates.First(v => v.FromDate <= this.IssueDate && (!v.ExistThroughDate || v.ThroughDate >= this.IssueDate));
              }
  
              foreach (OrderAdjustment orderAdjustment in this.OrderAdjustments)
              {
                  if (orderAdjustment.GetType().Name.Equals(typeof(DiscountAdjustment).Name))
                  {
                      discount = orderAdjustment.Percentage.HasValue ?
                                      this.TotalExVat * orderAdjustment.Percentage.Value / 100 :
                                      orderAdjustment.Amount ?? 0;
  
                      this.TotalDiscount += discount;
  
                      if (this.ExistDerivedVatRegime)
                      {
                          discountVat = discount * this.DerivedVatRate.Rate / 100;
                      }
  
                      if (this.ExistDerivedIrpfRegime)
                      {
  
gt:                         discountIrpf = discount * this.DerivedIrpfRate.Rate / 100;