mirror of
https://github.com/stokebob/bnhtrade.git
synced 2026-03-21 15:27:15 +00:00
Export amazon settlement report fix
This commit is contained in:
270
src/bnhtrade.Core/Logic/Export/AmazonSettlementData.cs
Normal file
270
src/bnhtrade.Core/Logic/Export/AmazonSettlementData.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Transactions;
|
||||
|
||||
namespace bnhtrade.Core.Logic.Export
|
||||
{
|
||||
public class AmazonSettlementData
|
||||
{
|
||||
private string sqlConnectionString;
|
||||
private Dictionary<string, Model.Account.TaxCode> taxCodeBySkuNumer;
|
||||
|
||||
public AmazonSettlementData(string sqlConnectionString)
|
||||
{
|
||||
this.sqlConnectionString = sqlConnectionString;
|
||||
}
|
||||
public void ToInvoice()
|
||||
{
|
||||
var console = new UI.Console.Update();
|
||||
var log = new Logic.Log.LogEvent();
|
||||
log.LogInformation("Starting processing of Amazon settlement data into export invoice table...");
|
||||
|
||||
// get list of unprocssed settlement reports to export
|
||||
var settlementData = new Data.Database.Import.ReadAmazonSettlement(sqlConnectionString);
|
||||
var settlementList = settlementData.AllUnprocessed();
|
||||
settlementData = null;
|
||||
|
||||
if (settlementList == null)
|
||||
{
|
||||
log.LogInformation("No new settlements to process, exiting import...");
|
||||
return;
|
||||
}
|
||||
|
||||
// create list of settlement ids
|
||||
var settlementIdList = new List<string>();
|
||||
for (int i = 0; i < settlementList.Count(); i++)
|
||||
{
|
||||
settlementIdList.Add(settlementList[i].SettlementId);
|
||||
}
|
||||
|
||||
// test marketplace-name has been sucsessfully entered into settlement table --
|
||||
// as this is not supplied in the original report from Amazon and has to be inferred from settlement line data
|
||||
// this is not picked up in validate stage as null value is valid
|
||||
for (int i = 0; i < settlementList.Count(); i++)
|
||||
{
|
||||
if (!settlementList[i].MarketPlaceNameIsSet)
|
||||
{
|
||||
log.LogError(
|
||||
"Action required: Enter market place name for settlelment report id " + settlementList[i].SettlementId + "."
|
||||
, "Unable to process settlement data from one settlement report '" + settlementList[i].SettlementId +
|
||||
"'. Report header table requires a market place name, which is not supplied in original " +
|
||||
"report from Amazon. This is useually inferred from settlement lines. " +
|
||||
"However, in this case, it was not not possible. Manual edit/entry for database table required."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// validate settlelments
|
||||
var validate = new Logic.Import.ValidateAmazonSettlement();
|
||||
for (int i = 0; i < settlementList.Count(); i++)
|
||||
{
|
||||
if (!validate.IsValid(settlementList[i]))
|
||||
{
|
||||
log.LogError("Error procesing Amazon Settlement data for export.", validate.ErrorListToString());
|
||||
}
|
||||
}
|
||||
if (validate.ErrorListIsSet) { return; }
|
||||
|
||||
// get dictionary of sku-number to taxcodeId
|
||||
Console.Write("\rBuilding SKU list... ");
|
||||
var dicSkuNumberToTaxCodeId = new Dictionary<string, string>();
|
||||
for (int i = 0; i < settlementList.Count(); i++)
|
||||
{
|
||||
if (settlementList[i].SettlementLineListIsSet)
|
||||
{
|
||||
for (int j = 0; j < settlementList[i].SettlementLineList.Count(); j++)
|
||||
{
|
||||
if (settlementList[i].SettlementLineList[j].SkuIsSet
|
||||
&& !string.IsNullOrWhiteSpace(settlementList[i].SettlementLineList[j].Sku))
|
||||
{
|
||||
if (!dicSkuNumberToTaxCodeId.ContainsKey(settlementList[i].SettlementLineList[j].Sku))
|
||||
{
|
||||
dicSkuNumberToTaxCodeId.Add(settlementList[i].SettlementLineList[j].Sku, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var readTaxCode = new Data.Database.Account.ReadTaxCode(sqlConnectionString);
|
||||
taxCodeBySkuNumer = readTaxCode.BySkuNumber(dicSkuNumberToTaxCodeId.Keys.ToList());
|
||||
|
||||
// loop through each settlement and build list of invoices to export
|
||||
Console.Write("\rBuilding invoices to export... ");
|
||||
var invoiceList = new List<Model.Account.SalesInvoice>();
|
||||
for (int i = 0; i < settlementList.Count(); i++)
|
||||
{
|
||||
// split settlement line list into months
|
||||
// List<Model.Account.SalesInvoice.InvoiceLine>
|
||||
var monthList = settlementList[i].SettlementLineList
|
||||
.GroupBy(x => new DateTime(x.PostDateTime.Year, x.PostDateTime.Month, 1, 0, 0, 0, x.PostDateTime.Kind));
|
||||
//.GroupBy(x => string.Format("{0}-{1}", x.PostDateTime.Year, x.PostDateTime.Month));
|
||||
//.GroupBy(x => new { x.PostDateTime.Month, x.PostDateTime.Year });
|
||||
|
||||
int monthCount = 0;
|
||||
foreach (var month in monthList)
|
||||
{
|
||||
monthCount++;
|
||||
var itemCodeTotal = new Dictionary<string, decimal>();
|
||||
foreach (var line in month)
|
||||
{
|
||||
string itemCode = BuildLineItemCode(line.Sku, line.TransactionType, line.AmountType, line.AmountDescription);
|
||||
if (itemCodeTotal.ContainsKey(itemCode))
|
||||
{
|
||||
itemCodeTotal[itemCode] += line.Amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemCodeTotal.Add(itemCode, line.Amount);
|
||||
}
|
||||
}
|
||||
|
||||
// create invoice, one for each month
|
||||
var invoice = new Model.Account.SalesInvoice();
|
||||
|
||||
// create invoice lines forsy
|
||||
invoice.InvoiceLineList = new List<Model.Account.SalesInvoice.InvoiceLine>();
|
||||
decimal lineNetTotal = 0m;
|
||||
decimal lineTaxTotal = 0m;
|
||||
foreach (var item in itemCodeTotal)
|
||||
{
|
||||
var line = new Model.Account.SalesInvoice.InvoiceLine();
|
||||
line.ItemCode = item.Key;
|
||||
line.TotalNetAmount = item.Value;
|
||||
lineNetTotal += item.Value;
|
||||
line.TaxAmount = 0;
|
||||
lineTaxTotal += 0;
|
||||
line.Quantity = 1;
|
||||
invoice.InvoiceLineList.Add(line);
|
||||
}
|
||||
|
||||
invoice.ContactName = settlementList[i].MarketPlaceName;
|
||||
invoice.InvoiceCurrencyCode = settlementList[i].CurrencyCode;
|
||||
if (monthList.Count() == 1 || monthList.Count() == monthCount)
|
||||
{ invoice.InvoiceDate = settlementList[i].EndDate; }
|
||||
else
|
||||
{ invoice.InvoiceDate = new DateTime(month.Key.Year, month.Key.Month, 1, 0, 0, 0, DateTimeKind.Utc).AddMonths(1).AddDays(-1); }
|
||||
invoice.InvoiceDateKind = DateTimeKind.Utc;
|
||||
invoice.InvoiceDueDate = settlementList[i].DepositDate;
|
||||
invoice.InvoiceReference = settlementList[i].SettlementId;
|
||||
invoice.InvoiceAmount = lineNetTotal + lineTaxTotal;
|
||||
if (invoice.InvoiceAmount < 0) { invoice.IsCreditNote = true; }
|
||||
else { invoice.IsCreditNote = false; }
|
||||
|
||||
// invoice complete, add to list
|
||||
invoiceList.Add(invoice);
|
||||
}
|
||||
}
|
||||
|
||||
// sort list of invoices
|
||||
invoiceList = invoiceList.OrderBy(x => x.InvoiceReference).ThenBy(x => x.InvoiceDate).ToList();
|
||||
|
||||
// check invoice total against settlement totals
|
||||
var invoiceTotal = new Dictionary<string, decimal>();
|
||||
for (int i = 0; i < invoiceList.Count(); i++)
|
||||
{
|
||||
if (invoiceTotal.ContainsKey(invoiceList[i].InvoiceReference))
|
||||
{
|
||||
invoiceTotal[invoiceList[i].InvoiceReference] += invoiceList[i].InvoiceAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
invoiceTotal.Add(invoiceList[i].InvoiceReference, invoiceList[i].InvoiceAmount);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < settlementList.Count(); i++)
|
||||
{
|
||||
if (settlementList[i].TotalAmount != invoiceTotal[settlementList[i].SettlementId])
|
||||
{
|
||||
throw new Exception("invoice totals does not match settlement total.");
|
||||
}
|
||||
}
|
||||
if (settlementIdList.Count != invoiceTotal.Count())
|
||||
{
|
||||
log.LogError("Stopping Settlement export. Not all settlements have been transposed into invoices.");
|
||||
return;
|
||||
}
|
||||
|
||||
// postfix invoices spanning multiple months with -n
|
||||
if (invoiceList.Count() > 1)
|
||||
{
|
||||
string lastRef = invoiceList[0].InvoiceReference;
|
||||
int countRef = 1;
|
||||
for (int i = 1; i < invoiceList.Count(); i++)
|
||||
{
|
||||
if (invoiceList[i].InvoiceReference == lastRef)
|
||||
{
|
||||
if (countRef == 1)
|
||||
{
|
||||
invoiceList[i - 1].InvoiceReference = lastRef + "-" + countRef;
|
||||
}
|
||||
invoiceList[i].InvoiceReference = lastRef + "-" + (countRef += 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// shouldn't normally be more than 2 date ranges, log and move on.
|
||||
if (countRef > 2)
|
||||
{
|
||||
log.LogError(
|
||||
countRef + " total numner of export invoices created from Amazon Settlement Id" + lastRef + "."
|
||||
, "Settlement period appears to span more 3 months or more. Whilst this is possible, it is unsual. Confirm his is correct.");
|
||||
}
|
||||
|
||||
lastRef = invoiceList[i].InvoiceReference;
|
||||
countRef = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.Write("\rWriting to database... ");
|
||||
using (TransactionScope scope = new TransactionScope())
|
||||
{
|
||||
try
|
||||
{
|
||||
// write to the database (gets validated there)
|
||||
new Data.Database.Export.CreateSalesInvoice(sqlConnectionString).SaveInvoice(invoiceList);
|
||||
|
||||
// set settlements to isprocessed
|
||||
new Data.Database.Import.UpdateAmazonSettlement(sqlConnectionString).SetIsProcessedTrue(settlementIdList);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogError(ex.Message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Console.Write("\r");
|
||||
log.LogInformation("Finished processing of Amazon settlement data. " + invoiceList.Count() + " invoices created from "+ settlementIdList.Count() + " Amazon settlement reports.");
|
||||
}
|
||||
|
||||
private string BuildLineItemCode(string skuNumber, string transactionType, string amountType, string amountDescription)
|
||||
{
|
||||
// build the match string
|
||||
// NB special case for global accounting sale and refunds (also note Goodlwill is included) and sku's where tax is included
|
||||
string match01 = transactionType;
|
||||
string match02 = amountType;
|
||||
string match03 = amountDescription;
|
||||
string matchString = "<AmazonReport><SettlementReportLine><" + match01 + "><" + match02 + "><" + match03 + ">";
|
||||
|
||||
// add tax info if required
|
||||
if ((match01 == "Order" || match01 == "Refund")
|
||||
&& (match02 == "ItemPrice" || match02 == "Promotion"))
|
||||
{
|
||||
if (taxCodeBySkuNumer.ContainsKey(skuNumber))
|
||||
{
|
||||
matchString = matchString + "<TaxCode=" + taxCodeBySkuNumer[skuNumber].TaxCodeId + ">";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Sku#" + skuNumber + " tax info not found in dictionary list.");
|
||||
}
|
||||
}
|
||||
return matchString;
|
||||
}
|
||||
}
|
||||
}
|
||||
134
src/bnhtrade.Core/Logic/Export/ValidateSalesInvoice.cs
Normal file
134
src/bnhtrade.Core/Logic/Export/ValidateSalesInvoice.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bnhtrade.Core.Logic.Export
|
||||
{
|
||||
class ValidateSalesInvoice : Logic.Account.ValidateInvoice
|
||||
{
|
||||
//public bool IsValidInvoice(Model.Export.SetSalesInvoice invoice)
|
||||
//{
|
||||
// ErrorMessage = null;
|
||||
|
||||
// var baseInvoice = CopyInvoiceToBase(invoice);
|
||||
// if (!base.IsValidInvoice(baseInvoice))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // check each match string only appears once
|
||||
// var dicLookup = new Dictionary<string, string>();
|
||||
// foreach (var line in invoice.InvoiceLineList)
|
||||
// {
|
||||
// if (dicLookup.ContainsKey(line.LineTypeMatchString))
|
||||
// {
|
||||
// ErrorMessage = "'Line type match string' duplication.";
|
||||
// return false;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// dicLookup.Add(line.LineTypeMatchString, null);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return true;
|
||||
//}
|
||||
//public bool IsValidInvoiceHeader(Model.Export.SetSalesInvoice invoice)
|
||||
//{
|
||||
// ErrorMessage = null;
|
||||
|
||||
// var baseInvoice = CopyInvoiceHeaderToBase(invoice);
|
||||
// if (base.IsValidInvoiceHeader(baseInvoice))
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
//public bool IsValidInvoiceLine(Model.Export.SetSalesInvoiceLine invoiceLine)
|
||||
//{
|
||||
// ErrorMessage = null;
|
||||
|
||||
// var baseLine = CopyInvoiceLineToBase(invoiceLine);
|
||||
// if (!IsValidLineTypeMatchString(invoiceLine.LineTypeMatchString)
|
||||
// || !base.IsValidInvoiceLine(baseLine))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// return true;
|
||||
//}
|
||||
//public bool IsValidLineTypeMatchString(string matchString)
|
||||
//{
|
||||
// ErrorMessage = null;
|
||||
// var stringCheck = new Logic.Utilities.StringCheck();
|
||||
// if (!stringCheck.MaxLength(matchString, 250))
|
||||
// {
|
||||
// ErrorMessage = "Invalid Line Type Match String: " + stringCheck.ErrorMessage;
|
||||
// return false;
|
||||
// }
|
||||
// return true;
|
||||
//}
|
||||
//private Model.Account.SalesInvoice CopyInvoiceToBase(Model.Export.SetSalesInvoice invoice)
|
||||
//{
|
||||
// var baseInvoice = CopyInvoiceHeaderToBase(invoice);
|
||||
// if (invoice.IsSetInvoiceLineList)
|
||||
// {
|
||||
// var baseLineList = new List<Model.Account.SalesInvoiceLine>();
|
||||
// foreach (var line in invoice.InvoiceLineList)
|
||||
// {
|
||||
// baseLineList.Add(CopyInvoiceLineToBase(line));
|
||||
// }
|
||||
// baseInvoice.InvoiceLineList = baseLineList;
|
||||
// }
|
||||
// return baseInvoice;
|
||||
//}
|
||||
|
||||
//private Model.Account.SalesInvoice CopyInvoiceHeaderToBase(Model.Export.SetSalesInvoice invoice)
|
||||
//{
|
||||
// var baseInvoice = new Model.Account.SalesInvoice();
|
||||
|
||||
// if (invoice.IsSetContactName)
|
||||
// { baseInvoice.ContactName = invoice.ContactName; }
|
||||
// if (invoice.IsSetInvoiceAmount)
|
||||
// { baseInvoice.InvoiceAmount = invoice.InvoiceAmount; }
|
||||
// if (invoice.IsSetInvoiceCurrencyCode)
|
||||
// { baseInvoice.InvoiceCurrencyCode = invoice.InvoiceCurrencyCode; }
|
||||
// if (invoice.IsSetInvoiceDate)
|
||||
// { baseInvoice.InvoiceDate = invoice.InvoiceDate; }
|
||||
// if (invoice.IsSetInvoiceNumber)
|
||||
// { baseInvoice.InvoiceNumber = invoice.InvoiceNumber; }
|
||||
// if (invoice.IsSetInvoiceReference)
|
||||
// { baseInvoice.InvoiceReference = invoice.InvoiceReference; }
|
||||
// if (invoice.IsSetIsComplete)
|
||||
// { baseInvoice.IsComplete = invoice.IsComplete; }
|
||||
|
||||
// return baseInvoice;
|
||||
//}
|
||||
//private Model.Account.SalesInvoiceLine CopyInvoiceLineToBase(Model.Export.SetSalesInvoiceLine invoiceLine)
|
||||
//{
|
||||
// var baseLine = new Model.Account.SalesInvoiceLine();
|
||||
|
||||
// if (invoiceLine.IsSetAccountCode)
|
||||
// { baseLine.AccountCode = invoiceLine.AccountCode; }
|
||||
|
||||
// // something missing here ??????
|
||||
|
||||
// if (invoiceLine.IsSetDescription)
|
||||
// { baseLine.Description = invoiceLine.Description; }
|
||||
// if (invoiceLine.IsSetNetAmount)
|
||||
// { baseLine.TotalNetAmount = invoiceLine.NetAmount; }
|
||||
|
||||
// // tax adjustment to set ??????????
|
||||
|
||||
// if (invoiceLine.IsSetTaxCode)
|
||||
// { baseLine.TaxCode = invoiceLine.TaxCode; }
|
||||
|
||||
// return baseLine;
|
||||
//}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user