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 AmazonSettlement { private Logic.Log.LogEvent log = new Logic.Log.LogEvent(); private List lineItemCodeList = new List(); public AmazonSettlement() { } public void ToInvoice() { log.LogInformation("Starting processing of Amazon settlement data into export invoice table..."); // check settlement reports consistancy var consistencyCheck = new Data.Database.Consistency.ImportAmazonSettlement().PeriodDateGaps(); if (consistencyCheck == false) { return; } // get list of unprocssed settlement reports to export var settlementData = new Data.Database.Import.AmazonSettlementRead(); 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(); 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.Validate.AmazonSettlement(); for (int i = 0; i < settlementList.Count(); i++) { if (!validate.IsValid(settlementList[i])) { log.LogError("Error procesing Amazon Settlement data for export.", validate.ValidationResultListToString()); } } if (validate.IsValidResult == false) { return; } // get dictionary of sku-number to taxcodeId Console.Write("\rBuilding SKU list... "); var skuList = new List(); foreach (var settlement in settlementList) { if (settlement.SettlementLineListIsSet) { foreach (var line in settlement.SettlementLineList) { if (line.SkuIsSet && !string.IsNullOrWhiteSpace(line.Sku)) { skuList.Add(line.Sku); } } } } var taxCodeBySkuNumer = new Logic.Account.GetTaxCodeInfo().GetBySkuNumber(skuList); // loop through each settlement and build list of invoices to export Console.Write("\rBuilding invoices to export... "); var invoiceList = new List(); for (int i = 0; i < settlementList.Count(); i++) { // split settlement line list into months // List 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(); foreach (var line in month) { string itemCode = BuildLineItemCode(taxCodeBySkuNumer, 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(); decimal lineNetTotal = 0m; decimal lineTaxTotal = 0m; foreach (var item in itemCodeTotal) { var line = new Model.Account.SalesInvoice.InvoiceLine(invoice.UnitAmountIsTaxExclusive); line.ItemCode = item.Key; line.Quantity = 1; line.UnitAmount = item.Value; lineNetTotal += item.Value; lineTaxTotal += 0; 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.InvoiceDueDate = settlementList[i].DepositDate; invoice.InvoiceReference = settlementList[i].SettlementId; invoice.InvoiceTotalAmount = lineNetTotal + lineTaxTotal; if (invoice.InvoiceTotalAmount < 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(); for (int i = 0; i < invoiceList.Count(); i++) { if (invoiceTotal.ContainsKey(invoiceList[i].InvoiceReference)) { invoiceTotal[invoiceList[i].InvoiceReference] += invoiceList[i].InvoiceTotalAmount.GetValueOrDefault(); } else { invoiceTotal.Add(invoiceList[i].InvoiceReference, invoiceList[i].InvoiceTotalAmount.GetValueOrDefault()); } } 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; } // add invoice item code data to lines // also clean invoices of any disabled lines (remove lines and possibly invoices) var getLineItemInfo = new Logic.Account.GetInvoiceLineItem(); getLineItemInfo.InsertNewOnNoMatch = true; getLineItemInfo.CacheFill(lineItemCodeList); bool newTypeFound = false; string newTypeText = null; for (int i = 0; i < invoiceList.Count(); i++) { for (int j = 0; j < invoiceList[i].InvoiceLineList.Count(); j++) { var itemCode = getLineItemInfo.ByItemCode(invoiceList[i].InvoiceLineList[j].ItemCode); // error! itemCode should never be null if (itemCode == null) { throw new Exception("Item code is null"); } // flag new type and throw exception further on else if (itemCode.IsNewReviewRequired) { newTypeFound = true; if (string.IsNullOrWhiteSpace(itemCode.ItemCode)) { newTypeText = itemCode.Name; } else { newTypeText = itemCode.ItemCode; } } // clean invoices of any disabled lines (remove lines and possibly invoices) else if (itemCode.InvoiceLineEntryEnabled == false) { // remove line invoiceList[i].InvoiceTotalAmount = invoiceList[i].InvoiceTotalAmount - invoiceList[i].InvoiceLineList[j].LineTotalAmount; invoiceList[i].InvoiceLineList.RemoveAt(j); j = j - 1; // remove invoice? if (invoiceList[i].InvoiceLineList.Count == 0) { invoiceList.RemoveAt(i); if (i > 0) { i = i - 1; } } } // get here add info to lines else { invoiceList[i].InvoiceLineList[j].AccountCode = itemCode.DefaultAccountCode; invoiceList[i].InvoiceLineList[j].Description = itemCode.Name; invoiceList[i].InvoiceLineList[j].ItemCode = itemCode.ItemCode; invoiceList[i].InvoiceLineList[j].TaxCode = itemCode.DefaultTaxCode; } } } if (newTypeFound) { if (newTypeFound) { throw new Exception("Parameters required for Invoice line item code '"+ newTypeText + "'. Set in tblAccountInvoiceLineItem."); } return; } // postfix invoices references that span 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 + " invoices where 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 { var saveInv = new Logic.Export.SalesInvoice(); // add temp invoice numbers saveInv.AddTempInvoiceNumber(invoiceList, true); // write to the database (gets validated there) saveInv.SaveSalesInvoice(invoiceList); // set settlements to isprocessed new Data.Database.Import.AmazonSettlementUpdate().SetIsProcessedTrue(settlementIdList); scope.Complete(); } catch (Exception ex) { scope.Dispose(); log.LogError("Exeception caught while writing Amazon settlement invoices to DB. Changes were rolled back." , ex.Message); return; } } Console.Write("\r"); log.LogInformation("\rFinished processing of Amazon settlement data. " + invoiceList.Count() + " invoices created from " + settlementIdList.Count() + " Amazon settlement reports."); } private string BuildLineItemCode(Dictionary taxCodeBySkuNumer, 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 = "<" + match01 + "><" + match02 + "><" + match03 + ">"; // add tax info if required if ((match01 == "Order" || match01 == "Refund") && (match02 == "ItemPrice" || match02 == "Promotion" || match02 == "ItemWithheldTax")) { if (taxCodeBySkuNumer.ContainsKey(skuNumber)) { matchString = matchString + ""; } else { throw new Exception("Sku#" + skuNumber + " tax info not found in dictionary list."); } } // add to list of generated line item codes lineItemCodeList.Add(matchString); // return value return matchString; } } }