mirror of
https://github.com/stokebob/bnhtrade.git
synced 2026-05-18 19:48:23 +00:00
Added invoice export function and started implementation of unitofwork pattern (#43)
* complete read invoices from db * wip * wip * wip * wip * wip * wip * wip * wip * updated nuget package spapi * WIP * wip, now test * wip, jut need to fix tax inclusive line amounts not supported * wip * wip, before I f everything up * no, it complies now, this is the one before I f everything up * wip * wip * wip, logic ready for testing * wip it builds!!!! * wip tested, working, need to complete the gui section * wip * wip * wip - created export invoice data delete, time for testing * wip testing phase * wip - delete function fully tested and working * wip on to sorting out the issue with settlement invoices not tallying * wip * wip * wip * wip * wip before I complete change the ReadInvoiceLineItem sections * that appears to have worked, on with the main quest * no it's doesn't work, saving before i remove the confusing cache system (just use a dictionary!!) * wipping picadilli * wip * wip * implemented uow on inovice export, now for testing * wip * wip all tested do invoice currency convertion fearure * wip * pretty much done so long as xero accepts the exported invoices * Complete!
This commit is contained in:
@@ -8,11 +8,20 @@ namespace bnhtrade.Core.Model.Account
|
||||
{
|
||||
public class CurrencyExchangeRate
|
||||
{
|
||||
public CurrencyExchangeRate(CurrencyCode currencyCode, int exchangeRateSource, DateTime dateTimeStartUtc, DateTime dateTimeEndUtc)
|
||||
public CurrencyExchangeRate(CurrencyCode currencyCode, int exchangeRateSource, decimal currencyUnitsPerGbp, DateTime dateTimeStartUtc, DateTime dateTimeEndUtc)
|
||||
{
|
||||
this.CurrencyCode = currencyCode;
|
||||
this.ExchangeRateSource = exchangeRateSource;
|
||||
|
||||
if (currencyUnitsPerGbp <= 0)
|
||||
{
|
||||
throw new ArgumentException("Currency units per GBP must be greater than zero.");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.CurrencyUnitsPerGbp = currencyUnitsPerGbp;
|
||||
}
|
||||
|
||||
if (dateTimeEndUtc > dateTimeStartUtc)
|
||||
{
|
||||
this.DateTimeStartUtc = dateTimeStartUtc;
|
||||
@@ -50,6 +59,10 @@ namespace bnhtrade.Core.Model.Account
|
||||
|
||||
public DateTime DateTimeEndUtc { get; private set; }
|
||||
|
||||
public decimal ConvertToGbp(decimal amountToConvert)
|
||||
{
|
||||
return amountToConvert / CurrencyUnitsPerGbp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a given datetime falls within the the exchange rate period
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using FikaAmazonAPI.AmazonSpApiSDK.Models.FulfillmentInbound;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
@@ -28,41 +29,33 @@ namespace bnhtrade.Core.Model.Account
|
||||
|
||||
decimal? UnitAmount { get; set; }
|
||||
|
||||
Model.Account.Account AccountCode { get; set; }
|
||||
Model.Account.Account Account { get; set; }
|
||||
|
||||
Model.Account.TaxCodeInfo TaxCode { get; set; }
|
||||
|
||||
decimal? TaxAmountAdjust { get; set; }
|
||||
decimal TaxAmountAdjust { get; set; }
|
||||
|
||||
decimal? TaxAmount { get; }
|
||||
decimal? TaxAmountTotal { get; }
|
||||
|
||||
decimal LineTotalAmount { get; }
|
||||
}
|
||||
|
||||
|
||||
public abstract class Invoice : InvoiceHeader, IInvoice
|
||||
public abstract class Invoice : InvoiceHeader, IInvoice, IValidatableObject
|
||||
{
|
||||
private bool unitAmountIsTaxExclusive = false;
|
||||
protected Invoice(Model.Account.InvoiceType invoiceType, bool invoiceLineUnitAmountIsTaxExclusive) : base (invoiceType)
|
||||
{
|
||||
// force the UnitAmountIsTaxExclusive to be set at invoice creation to avoid issues,
|
||||
InvoiceLineUnitAmountIsTaxExclusive = invoiceLineUnitAmountIsTaxExclusive;
|
||||
}
|
||||
|
||||
public decimal InvoiceNetAmount { get; }
|
||||
|
||||
public decimal InvoiceTaxAmount { get; }
|
||||
|
||||
public bool UnitAmountIsTaxExclusive
|
||||
{
|
||||
get { return unitAmountIsTaxExclusive; }
|
||||
set
|
||||
{
|
||||
unitAmountIsTaxExclusive = value;
|
||||
if (InvoiceLineList != null)
|
||||
{
|
||||
for (int i = 0; i < InvoiceLineList.Count; i++)
|
||||
{
|
||||
InvoiceLineList[i].UnitAmountIsTaxExclusive = unitAmountIsTaxExclusive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Invoice line unit amount is tax exclusive
|
||||
/// </summary>
|
||||
public bool InvoiceLineUnitAmountIsTaxExclusive { get; set; }
|
||||
|
||||
public List<InvoiceLine> InvoiceLineList { get; set; } = new List<InvoiceLine>();
|
||||
|
||||
@@ -75,16 +68,18 @@ namespace bnhtrade.Core.Model.Account
|
||||
}
|
||||
}
|
||||
|
||||
public class InvoiceLine : IInvoiceLine, IValidatableObject
|
||||
public class InvoiceLine : IInvoiceLine, IValidatableObject
|
||||
{
|
||||
private Invoice parentInvoice;
|
||||
private decimal? unitAmount;
|
||||
private Model.Account.TaxCodeInfo taxCode;
|
||||
private decimal? taxAmountAdjust;
|
||||
private decimal taxAmountAdjust;
|
||||
private decimal? quantity;
|
||||
|
||||
public InvoiceLine(bool unitAmountIsTaxExclusive)
|
||||
public InvoiceLine(Invoice parentInvoice)
|
||||
{
|
||||
UnitAmountIsTaxExclusive = unitAmountIsTaxExclusive;
|
||||
if (parentInvoice == null) { throw new ArgumentNullException(nameof(parentInvoice), "Parent invoice cannot be null"); }
|
||||
this.parentInvoice = parentInvoice;
|
||||
}
|
||||
|
||||
public string ItemCode { get; set; }
|
||||
@@ -92,158 +87,333 @@ namespace bnhtrade.Core.Model.Account
|
||||
public string Description { get; set; }
|
||||
|
||||
[Required(), Range(0, 9999999.99)]
|
||||
public decimal? Quantity
|
||||
public decimal? Quantity
|
||||
{
|
||||
get { return quantity; }
|
||||
set
|
||||
set
|
||||
{
|
||||
if (value == null) { quantity = null; }
|
||||
else { quantity = decimal.Round((decimal)value, 4, MidpointRounding.AwayFromZero); }
|
||||
else { quantity = decimal.Round(value.GetValueOrDefault(), 4, MidpointRounding.AwayFromZero); }
|
||||
}
|
||||
}
|
||||
|
||||
[Required()]
|
||||
public decimal? UnitAmount
|
||||
{
|
||||
public decimal? UnitAmount
|
||||
{
|
||||
get { return unitAmount; }
|
||||
set
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == null) { unitAmount = null; }
|
||||
else { unitAmount = decimal.Round(value.GetValueOrDefault(), 4, MidpointRounding.AwayFromZero); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool UnitAmountIsTaxExclusive { get; set; }
|
||||
|
||||
[Required()]
|
||||
public Model.Account.Account AccountCode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
public bool UnitAmountIsTaxExclusive
|
||||
{
|
||||
get
|
||||
{
|
||||
return parentInvoice.InvoiceLineUnitAmountIsTaxExclusive;
|
||||
}
|
||||
}
|
||||
|
||||
[Required()]
|
||||
public Model.Account.TaxCodeInfo TaxCode
|
||||
{
|
||||
public Model.Account.Account Account
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Required()]
|
||||
public Model.Account.TaxCodeInfo TaxCode
|
||||
{
|
||||
get { return taxCode; }
|
||||
set
|
||||
{
|
||||
if (value == null || TaxCode == null || value.TaxCode != taxCode.TaxCode)
|
||||
{
|
||||
TaxAmountAdjust = 0;
|
||||
if (value == null || TaxCode == null || value.TaxCode != taxCode.TaxCode)
|
||||
{
|
||||
TaxAmountAdjust = 0;
|
||||
}
|
||||
|
||||
taxCode = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Required()]
|
||||
/// <summary>
|
||||
/// Tax amount calculated from line amount and the taxcode rate (before any adjustments)
|
||||
/// </summary>
|
||||
public decimal? TaxAmount
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
return TaxAmountAdjust + GetCalculatedTax();
|
||||
decimal calculatedTax = 0;
|
||||
if (CanCalculateLineTotalAmount)
|
||||
{
|
||||
if (UnitAmountIsTaxExclusive)
|
||||
{
|
||||
calculatedTax = ((decimal)Quantity * (decimal)UnitAmount) * (TaxCode.TaxRate / 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
calculatedTax = ((decimal)Quantity * (decimal)UnitAmount) * (TaxCode.TaxRate / (100 + TaxCode.TaxRate));
|
||||
}
|
||||
return RoundAmount(calculatedTax);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public decimal? TaxAmountAdjust
|
||||
/// <summary>
|
||||
/// Will adjust the calculated the tax total amount for the line
|
||||
/// </summary>
|
||||
public decimal TaxAmountAdjust
|
||||
{
|
||||
get { return taxAmountAdjust; }
|
||||
set
|
||||
set { taxAmountAdjust = RoundAmount(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The calculated total tax amount for the line (including any adjustments)
|
||||
/// </summary>
|
||||
[Required()]
|
||||
public decimal? TaxAmountTotal
|
||||
{
|
||||
get
|
||||
{
|
||||
if (value == null) { taxAmountAdjust = null; }
|
||||
else { taxAmountAdjust = decimal.Round((decimal)value, 2, MidpointRounding.AwayFromZero); }
|
||||
if (CanCalculateLineTotalAmount)
|
||||
{
|
||||
return taxAmountAdjust + TaxAmount.GetValueOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public decimal LineTotalAmount
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (CanCalculateLine())
|
||||
if (CanCalculateLineTotalAmount)
|
||||
{
|
||||
return ((decimal)Quantity * (decimal)UnitAmount) + (decimal)TaxAmount;
|
||||
}
|
||||
else { return 0; }
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanCalculateLine()
|
||||
{
|
||||
if (Quantity == null || UnitAmount == null || TaxCode == null) { return false; }
|
||||
else { return true; }
|
||||
}
|
||||
|
||||
private decimal GetCalculatedTax()
|
||||
{
|
||||
decimal calculatedTax = 0;
|
||||
if (CanCalculateLine())
|
||||
{
|
||||
if (UnitAmountIsTaxExclusive)
|
||||
{
|
||||
calculatedTax = ((decimal)Quantity * (decimal)UnitAmount) * (TaxCode.TaxRate / 100);
|
||||
return ((decimal)Quantity * (decimal)UnitAmount) + (decimal)TaxAmountTotal;
|
||||
}
|
||||
else
|
||||
{
|
||||
calculatedTax = ((decimal)Quantity * (decimal)UnitAmount) * (TaxCode.TaxRate / (100 + TaxCode.TaxRate));
|
||||
throw new InvalidOperationException("Cannot calculate line amount, ensure te Quantity, UnitAmount and TaxCode info is set.");
|
||||
}
|
||||
return decimal.Round(calculatedTax, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTaxAmountAdjust(decimal lineTaxAmount)
|
||||
public bool CanCalculateLineTotalAmount
|
||||
{
|
||||
decimal adjustedAmount = lineTaxAmount - GetCalculatedTax();
|
||||
if (adjustedAmount == 0) { TaxAmountAdjust = null; }
|
||||
else { TaxAmountAdjust = adjustedAmount; }
|
||||
get
|
||||
{
|
||||
if (Quantity == null || UnitAmount == null || TaxCode == null) { return false; }
|
||||
else { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will calculate and update the tax adjustment property from a given tax amount total
|
||||
/// </summary>
|
||||
/// <param name="taxAmountTotal">The required tax amount total</param>
|
||||
public void SetTaxAdjustmentByTaxTotal(decimal taxAmountTotal)
|
||||
{
|
||||
if (CanCalculateLineTotalAmount)
|
||||
{
|
||||
TaxAmountAdjust = taxAmountTotal - TaxAmount.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the unit amount based on the specified line total amount.
|
||||
/// </summary>
|
||||
/// <remarks>This method calculates the unit amount for the current item using the
|
||||
/// provided line total amount. The calculation considers whether the unit amount is tax-exclusive or
|
||||
/// tax-inclusive, as well as the associated tax rate and quantity. If the quantity is not set, it defaults
|
||||
/// to 1.</remarks>
|
||||
/// <param name="lineTotalAmount">The total amount for the line item. Must be a whole number to two decimal places.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="lineTotalAmount"/> is not a whole number to two decimal places.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the <c>TaxCode</c> is null or the quantity is not set.</exception>
|
||||
public void SetUnitAmountByLineTotal(decimal lineTotalAmount)
|
||||
{
|
||||
//checks
|
||||
if (lineTotalAmount * 100m != Math.Truncate(lineTotalAmount * 100m))
|
||||
{
|
||||
throw new ArgumentException("Line total amount must be a whole number to two decimal places", nameof(lineTotalAmount));
|
||||
}
|
||||
if (TaxCode == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot set unit amount, ensure the Quantity is set.");
|
||||
}
|
||||
else if (Quantity == null)
|
||||
{
|
||||
Quantity = 1;
|
||||
}
|
||||
|
||||
decimal taxRate = (TaxCode.TaxRate / 100); // convert to decimal rate
|
||||
|
||||
if (UnitAmountIsTaxExclusive)
|
||||
{
|
||||
// set unit amount to net
|
||||
UnitAmount = (lineTotalAmount - TaxAmountAdjust) / (Quantity.Value * taxRate + Quantity.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set unit amount to gross
|
||||
UnitAmount = (lineTotalAmount - TaxAmountAdjust) / Quantity.Value;
|
||||
}
|
||||
|
||||
if (LineTotalAmount != lineTotalAmount)
|
||||
{
|
||||
throw new InvalidOperationException("SetUnitAmountByLineTotal() has a bug, check code");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rounds decimals to two decimal places inline with Xero specs
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount to round</param>
|
||||
/// <returns>Rounded amount to two decimal places</returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private decimal RoundAmount(decimal amount)
|
||||
{
|
||||
return decimal.Round(amount, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
public bool ParentInvoiceCheck(Invoice invoice)
|
||||
{
|
||||
if (invoice == null) { return false; }
|
||||
return Object.ReferenceEquals(invoice, parentInvoice);
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
var resultList = new List<ValidationResult>();
|
||||
|
||||
if (TaxAmount > LineTotalAmount / 2)
|
||||
if (CanCalculateLineTotalAmount == false)
|
||||
{
|
||||
resultList.Add(new ValidationResult("Line tax amount is greater than net amount"));
|
||||
resultList.Add(new ValidationResult("Cannot calulate line amount, data missing"));
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ConvertToLineUnitAmountIsTaxExclusive()
|
||||
{
|
||||
if (InvoiceLineUnitAmountIsTaxExclusive)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// test whether there's enough info to do the convertion
|
||||
foreach (var line in this.InvoiceLineList)
|
||||
{
|
||||
if (line.CanCalculateLineTotalAmount == false)
|
||||
{
|
||||
// can't do it return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// do the convertion
|
||||
InvoiceLineUnitAmountIsTaxExclusive = true;
|
||||
foreach (var line in this.InvoiceLineList)
|
||||
{
|
||||
decimal lineTotal = line.LineTotalAmount;
|
||||
decimal taxTotal = line.TaxAmountTotal.Value;
|
||||
|
||||
// set unit amount to net and update
|
||||
line.UnitAmount = ((line.Quantity.Value * line.UnitAmount.Value) - line.TaxAmountTotal.Value) / line.Quantity.Value;
|
||||
line.SetTaxAdjustmentByTaxTotal(taxTotal);
|
||||
|
||||
//fail safe
|
||||
if (line.LineTotalAmount != lineTotal)
|
||||
{
|
||||
throw new Exception("ConvertToLineUnitAmountIsTaxExclusive() has a bug, check code");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ConvertToLineUnitAmountIsTaxInclusive()
|
||||
{
|
||||
if (!InvoiceLineUnitAmountIsTaxExclusive)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// test whether there's enough info to do the convertion
|
||||
foreach (var line in this.InvoiceLineList)
|
||||
{
|
||||
if (line.CanCalculateLineTotalAmount == false)
|
||||
{
|
||||
// can't do it return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// do the convertion
|
||||
InvoiceLineUnitAmountIsTaxExclusive = false;
|
||||
foreach (var line in this.InvoiceLineList)
|
||||
{
|
||||
decimal lineTotal = line.LineTotalAmount;
|
||||
decimal taxTotal = line.TaxAmountTotal.Value;
|
||||
|
||||
// set unit amount to gross and update
|
||||
line.UnitAmount = (line.UnitAmount / line.Quantity) + (line.TaxAmountTotal / line.Quantity);
|
||||
line.SetTaxAdjustmentByTaxTotal(taxTotal);
|
||||
|
||||
//fail safe
|
||||
if (line.LineTotalAmount != lineTotal)
|
||||
{
|
||||
throw new Exception("ConvertToLineUnitAmountIsTaxExclusive() has a bug, check code");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public decimal GetCalculatedInvoiceTotal()
|
||||
{
|
||||
decimal lineTotal = 0;
|
||||
foreach (var line in InvoiceLineList)
|
||||
{
|
||||
lineTotal += line.LineTotalAmount;
|
||||
}
|
||||
return lineTotal;
|
||||
}
|
||||
|
||||
public new IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
var subResult = base.Validate(validationContext);
|
||||
var resultList = new List<ValidationResult>();
|
||||
resultList.AddRange(subResult);
|
||||
|
||||
// add results from invoice header
|
||||
resultList.AddRange(base.Validate(validationContext));
|
||||
|
||||
// loop though lines and check sum totals
|
||||
// loop though invoice lines and add results from there
|
||||
if (!InvoiceLineListIsSet)
|
||||
{
|
||||
resultList.Add(new ValidationResult("No lines set on Invoice"));
|
||||
resultList.Add(new ValidationResult("No lines set on Invoice (InvoiceNumber:" + InvoiceNumber + ")"));
|
||||
}
|
||||
else
|
||||
foreach (var line in InvoiceLineList)
|
||||
{
|
||||
decimal lineTotal = 0;
|
||||
foreach (var line in InvoiceLineList)
|
||||
if (line.ParentInvoiceCheck(this) == false)
|
||||
{
|
||||
lineTotal += line.LineTotalAmount;
|
||||
|
||||
if (UnitAmountIsTaxExclusive != line.UnitAmountIsTaxExclusive)
|
||||
{
|
||||
resultList.Add(new ValidationResult("Invalid UnitAmountIsTaxExclusive"));
|
||||
}
|
||||
resultList.Add(new ValidationResult("Incorrect parent invoice set on invoice line"));
|
||||
}
|
||||
resultList.AddRange(line.Validate(validationContext));
|
||||
}
|
||||
|
||||
// check totals
|
||||
if (InvoiceTotalAmount != lineTotal)
|
||||
{ resultList.Add(new ValidationResult("Invoice line total does not equal invoice total")); }
|
||||
// check invoice total against calculated total from invoice lines
|
||||
decimal calculatedInvoiceTotal = GetCalculatedInvoiceTotal(); // don't know why this needs to be here, but it won't pickup a failed validation otherwise
|
||||
if (InvoiceTotalAmount != calculatedInvoiceTotal)
|
||||
{
|
||||
resultList.Add(new ValidationResult("Invoice line total does not equal invoice total (InvoiceNumber:" + InvoiceNumber +")"));
|
||||
}
|
||||
|
||||
return resultList;
|
||||
|
||||
@@ -9,11 +9,13 @@ namespace bnhtrade.Core.Model.Account
|
||||
{
|
||||
public interface IInvoiceHeader
|
||||
{
|
||||
Model.Account.InvoiceType InvoiceType { get; }
|
||||
|
||||
string ContactName { get; set; }
|
||||
|
||||
decimal? InvoiceTotalAmount { get; set; }
|
||||
|
||||
string InvoiceCurrencyCode { get; set; }
|
||||
Model.Account.CurrencyCode InvoiceCurrencyCode { get; set; }
|
||||
|
||||
DateTime? InvoiceDate { get; set; }
|
||||
|
||||
@@ -30,23 +32,27 @@ namespace bnhtrade.Core.Model.Account
|
||||
{
|
||||
private decimal? invoiceAmount;
|
||||
|
||||
public InvoiceHeader()
|
||||
public InvoiceHeader(Model.Account.InvoiceType invoiceType)
|
||||
{
|
||||
this.InvoiceType = invoiceType;
|
||||
IsCreditNote = false;
|
||||
}
|
||||
|
||||
[Required()]
|
||||
public Model.Account.InvoiceType InvoiceType { get; private set; }
|
||||
|
||||
[Required()]
|
||||
public string ContactName { get; set; }
|
||||
|
||||
[Required()]
|
||||
public Model.Account.CurrencyCode InvoiceCurrencyCode { get; set; }
|
||||
|
||||
[Required()]
|
||||
public DateTime? InvoiceDate { get; set; }
|
||||
|
||||
[Required()]
|
||||
public DateTime? InvoiceDueDate { get; set; }
|
||||
|
||||
[Required(), MinLength(3), MaxLength(3)]
|
||||
public string InvoiceCurrencyCode { get; set; }
|
||||
|
||||
[Required()]
|
||||
public string InvoiceNumber { get; set; }
|
||||
|
||||
@@ -55,7 +61,7 @@ namespace bnhtrade.Core.Model.Account
|
||||
[Required()]
|
||||
public decimal? InvoiceTotalAmount
|
||||
{
|
||||
get { return (decimal)invoiceAmount; }
|
||||
get { return invoiceAmount.GetValueOrDefault(); }
|
||||
set
|
||||
{
|
||||
if (value == null) { invoiceAmount = null; }
|
||||
|
||||
@@ -43,14 +43,12 @@ namespace bnhtrade.Core.Model.Account
|
||||
set;
|
||||
}
|
||||
|
||||
[Required()]
|
||||
public Model.Account.Account DefaultAccountCode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Required()]
|
||||
public Model.Account.TaxCodeInfo DefaultTaxCode
|
||||
{
|
||||
get;
|
||||
@@ -61,7 +59,27 @@ namespace bnhtrade.Core.Model.Account
|
||||
{
|
||||
var resultList = new List<ValidationResult>();
|
||||
|
||||
resultList.AddRange(DefaultTaxCode.Validate(validationContext));
|
||||
if (InvoiceLineEntryEnabled)
|
||||
{
|
||||
if (DefaultAccountCode == null)
|
||||
{
|
||||
resultList.Add(new ValidationResult("DefaultAccountCode is required when InvoiceLineEntryEnabled is true."));
|
||||
}
|
||||
if (DefaultTaxCode == null)
|
||||
{
|
||||
resultList.Add(new ValidationResult("DefaultTaxCode is required when InvoiceLineEntryEnabled is true."));
|
||||
}
|
||||
}
|
||||
|
||||
if (DefaultAccountCode != null)
|
||||
{
|
||||
resultList.AddRange(DefaultTaxCode.Validate(validationContext));
|
||||
}
|
||||
|
||||
if (DefaultTaxCode != null)
|
||||
{
|
||||
resultList.AddRange(DefaultTaxCode.Validate(validationContext));
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bnhtrade.Core.Model.Account
|
||||
{
|
||||
public enum InvoiceType
|
||||
{
|
||||
Purchase = 1,
|
||||
Sale = 2
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,11 @@ namespace bnhtrade.Core.Model.Account
|
||||
{
|
||||
public class PurchaseInvoice
|
||||
{
|
||||
// the purchase/sales invoice models are fragmented. The sales invoice inherits from an abstract invoice class. However, this purchase invoice
|
||||
// model doesn't, it was setup in the early development to mirror an Amazon report.
|
||||
// At some point I would like to correct this, i.e. have the purchase invoice model inherit from the abstract invoice class.
|
||||
// this will take some unpicking
|
||||
|
||||
public int PurchaseID { get; set; }
|
||||
|
||||
public int PurchaseNumber { get; set; }
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace bnhtrade.Core.Model.Account
|
||||
{
|
||||
public class SalesInvoice : Invoice
|
||||
{
|
||||
|
||||
public SalesInvoice(bool invoiceLineUnitAmountIsTaxExclusive) : base(Model.Account.InvoiceType.Sale, invoiceLineUnitAmountIsTaxExclusive)
|
||||
{
|
||||
// the purchase/sales invoice models are fragmented. This sales invoice inherits from an abstract invoice. However, the purchase invoice
|
||||
// model doesn't, it was setup in the early development to mirror an Amazon report.
|
||||
// At some point I would like to correct this, i.e. have the purchase invoice model inherit from the abstract invoice class.
|
||||
// this will take some unpicking
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bnhtrade.Core.Model.Amazon
|
||||
{
|
||||
public enum MarketPlaceEnum
|
||||
{
|
||||
NONE = 0,
|
||||
AmazonUK = 1,
|
||||
AmazonDE = 2,
|
||||
AmazonES = 3,
|
||||
AmazonFR = 4,
|
||||
AmazonIT = 5,
|
||||
AmazonBE = 6,
|
||||
AmazonIE = 7,
|
||||
AmazonNL = 8,
|
||||
AmazonPL = 9,
|
||||
AmazonSE = 10
|
||||
}
|
||||
|
||||
public static class MarketPlaceEnumExtensions
|
||||
{
|
||||
public static string GetMarketplaceUrl(this MarketPlaceEnum marketPlace)
|
||||
{
|
||||
return marketPlace switch
|
||||
{
|
||||
MarketPlaceEnum.AmazonUK => "Amazon.co.uk",
|
||||
MarketPlaceEnum.AmazonDE => "Amazon.de",
|
||||
MarketPlaceEnum.AmazonES => "Amazon.es",
|
||||
MarketPlaceEnum.AmazonFR => "Amazon.fr",
|
||||
MarketPlaceEnum.AmazonIT => "Amazon.it",
|
||||
MarketPlaceEnum.AmazonBE => "Amazon.com.be",
|
||||
MarketPlaceEnum.AmazonIE => "Amazon.ie",
|
||||
MarketPlaceEnum.AmazonNL => "Amazon.nl",
|
||||
MarketPlaceEnum.AmazonPL => "Amazon.pl",
|
||||
MarketPlaceEnum.AmazonSE => "Amazon.se",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(marketPlace), marketPlace, null)
|
||||
};
|
||||
}
|
||||
|
||||
public static MarketPlaceEnum FromMarketplaceUrl(string url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
|
||||
return url.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"amazon.co.uk" => MarketPlaceEnum.AmazonUK,
|
||||
"amazon.de" => MarketPlaceEnum.AmazonDE,
|
||||
"amazon.es" => MarketPlaceEnum.AmazonES,
|
||||
"amazon.fr" => MarketPlaceEnum.AmazonFR,
|
||||
"amazon.it" => MarketPlaceEnum.AmazonIT,
|
||||
"amazon.com.be" => MarketPlaceEnum.AmazonBE,
|
||||
"amazon.ie" => MarketPlaceEnum.AmazonIE,
|
||||
"amazon.nl" => MarketPlaceEnum.AmazonNL,
|
||||
"amazon.pl" => MarketPlaceEnum.AmazonPL,
|
||||
"amazon.se" => MarketPlaceEnum.AmazonSE,
|
||||
_ => throw new ArgumentException($"Unknown marketplace URL: {url}", nameof(url))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bnhtrade.Core.Model.Export
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Sale or purchase invoice, id matches the database id
|
||||
/// </summary>
|
||||
public enum AccountInvoiceType
|
||||
{
|
||||
Purchase = 1,
|
||||
Sale = 2
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using bnhtrade.Core.Model.Amazon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
@@ -9,7 +10,6 @@ namespace bnhtrade.Core.Model.Import
|
||||
{
|
||||
public class AmazonSettlementHeader
|
||||
{
|
||||
private string marketplaceName = null;
|
||||
private string settlementId = null;
|
||||
private string currencyCode = null;
|
||||
private DateTime? startDate = null;
|
||||
@@ -26,7 +26,7 @@ namespace bnhtrade.Core.Model.Import
|
||||
|
||||
public AmazonSettlementHeader(AmazonSettlementHeader toCopy)
|
||||
{
|
||||
this.marketplaceName = toCopy.MarketPlaceName;
|
||||
this.MarketPlace = toCopy.MarketPlace;
|
||||
this.settlementId = toCopy.SettlementId;
|
||||
this.currencyCode = toCopy.CurrencyCode;
|
||||
if (toCopy.StartDateIsSet) { this.startDate = toCopy.StartDate; }
|
||||
@@ -37,15 +37,21 @@ namespace bnhtrade.Core.Model.Import
|
||||
if (toCopy.SpapiReportIdIsSet) { this.spapiReportId = toCopy.SpapiReportId; }
|
||||
}
|
||||
|
||||
public string MarketPlaceName
|
||||
{
|
||||
get { return marketplaceName; }
|
||||
set { this.marketplaceName = value; }
|
||||
}
|
||||
public Model.Amazon.MarketPlaceEnum MarketPlace { get; set; }
|
||||
|
||||
public bool MarketPlaceNameIsSet
|
||||
{
|
||||
get { return marketplaceName != null; }
|
||||
get
|
||||
{
|
||||
if (MarketPlace == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SettlementId
|
||||
|
||||
Reference in New Issue
Block a user