using Comal.Classes; using CsvHelper.Configuration.Attributes; using CsvHelper.Configuration; using CsvHelper; using InABox.Core.Postable; using InABox.Core; using InABox.Poster.Timberline; using InABox.Scripting; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using Microsoft.Win32; using CsvHelper.TypeConversion; using NPOI.SS.Formula.Functions; namespace PRS.Shared; public interface IStockMovementTimberlineLine { DateTime TransactionDate { get; set; } DateTime AccountingDate { get; set; } string Description { get; set; } double Amount { get; set; } string DebitAccount { get; set; } string CreditAccount { get; set; } string Reference1 { get; set; } string Reference2 { get; set; } } public enum StockMovementTimberlineTransactionType { APCost = 1, JCCost = 2, PRCost = 3, EQCost = 4, IVCost = 5 } public class StockMovementTimberlineTransactionTypeConverter : DefaultTypeConverter { public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) { if (Enum.TryParse(text, out var type)) { return type; } return base.ConvertFromString(text, row, memberMapData); } public override string? ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData) { if (value is StockMovementTimberlineTransactionType type) { return ((int)type).ToString(); } return ""; } } public class StockMovementTimberlineDirectCost : IStockMovementTimberlineLine { [Ignore] public StockMovementBatchType BatchType { get; set; } [Index(0)] public string RecordID { get; set; } = "DC"; [Index(1)] [TypeConverter(typeof(TimberlinePosterStringConverter), 10)] public string Job { get; set; } [Index(2)] [TypeConverter(typeof(TimberlinePosterStringConverter), 10)] public string Extra { get; set; } [Index(3)] [TypeConverter(typeof(TimberlinePosterStringConverter), 12)] public string CostCode { get; set; } [Index(4)] [TypeConverter(typeof(TimberlinePosterStringConverter), 3)] public string Category { get; set; } [Index(5)] [TypeConverter(typeof(StockMovementTimberlineTransactionTypeConverter))] public StockMovementTimberlineTransactionType TransactionType { get; set; } [Index(6)] [TypeConverter(typeof(TimberlinePosterDateConverter))] public DateTime TransactionDate { get; set; } [Index(7)] [TypeConverter(typeof(TimberlinePosterDateConverter))] public DateTime AccountingDate { get; set; } [Index(8)] [TypeConverter(typeof(TimberlinePosterStringConverter), 30)] public string Description { get; set; } [Index(9)] public double Units { get; set; } [Index(10)] public double UnitCost { get; set; } [Index(11)] public double Amount { get; set; } [Index(12)] [TypeConverter(typeof(TimberlinePosterStringConverter), 25)] public string DebitAccount { get; set; } [Index(13)] [TypeConverter(typeof(TimberlinePosterStringConverter), 25)] public string CreditAccount { get; set; } [Index(14)] [TypeConverter(typeof(TimberlinePosterStringConverter), 10)] public string Reference1 { get; set; } [Index(15)] [TypeConverter(typeof(TimberlinePosterStringConverter), 10)] public string Reference2 { get; set; } [Index(16)] [TypeConverter(typeof(TimberlinePosterStringConverter), 10)] public string StandardItem { get; set; } } public class StockMovementTimberlineGL : IStockMovementTimberlineLine { [Ignore] public StockMovementBatchType BatchType { get; set; } [Index(0)] public string RecordID { get; set; } = "GL"; [Index(1)] [TypeConverter(typeof(TimberlinePosterDateConverter))] public DateTime TransactionDate { get; set; } [Index(2)] [TypeConverter(typeof(TimberlinePosterDateConverter))] public DateTime AccountingDate { get; set; } [Index(3)] [TypeConverter(typeof(TimberlinePosterStringConverter), 30)] public string Description { get; set; } [Index(4)] public double Amount { get; set; } [Index(5)] [TypeConverter(typeof(TimberlinePosterStringConverter), 25)] public string DebitAccount { get; set; } [Index(6)] [TypeConverter(typeof(TimberlinePosterStringConverter), 25)] public string CreditAccount { get; set; } [Index(7)] [TypeConverter(typeof(TimberlinePosterStringConverter), 10)] public string Reference1 { get; set; } [Index(8)] [TypeConverter(typeof(TimberlinePosterStringConverter), 10)] public string Reference2 { get; set; } } public class StockMovementTimberlineSettings : TimberlinePosterSettings { protected override string DefaultScript() { return @" using PRS.Shared; using InABox.Core; using System.Collections.Generic; public class Module { public void BeforePost(IDataModel model) { // Perform pre-processing } public bool ProcessDirectCostLine(IDataModel model, StockMovement stockMovement, StockMovementTimberlineDirectCost line) { // Do extra processing for a direct cost line; return false to fail this movement return true; } public bool ProcessGLLine(IDataModel model, StockMovement stockMovement, StockMovementTimberlineGL line) { // Do extra processing for a GL line; return false to fail this movement return true; } public void AfterPost(IDataModel model) { // Perform post-processing } }"; } } public class StockMovementTimberlineResult : TimberlinePostResult { } public class StockMovementTimberlinePoster : ITimberlinePoster { public ScriptDocument? Script { get; set; } public StockMovementTimberlineSettings Settings { get; set; } public bool BeforePost(IDataModel model) { model.SetIsDefault(false, alias: "CompanyLogo"); model.SetIsDefault(false, alias: "CompanyInformation"); model.SetIsDefault(false); model.SetColumns(new Columns(x => x.ID).Add(x => x.Transaction)); model.AddChildTable(x => x.Transaction, x => x.Transaction, parentalias: "StockMovement", childalias: "FullTransactions", isdefault: true, columns: new Columns(x => x.ID) .Add(x => x.Transaction) .Add(x => x.Job.ID) .Add(x => x.Product.ID) .Add(x => x.Type) .Add(x => x.Units) .Add(x => x.Cost) .Add(x => x.Value) .Add(x => x.Date) .Add(x => x.Batch.ID) ); model.AddLookupTable(x => x.Product.ID, x => x.ID, sourcealias: "FullTransactions", isdefault: true, columns: new Columns(x => x.ID) .Add(x => x.Name) .Add(x => x.CostCentre.Code) .Add(x => x.PurchaseGL.Code)); model.AddLookupTable(x => x.Job.ID, x => x.ID, sourcealias: "FullTransactions", isdefault: true, columns: new Columns(x => x.ID) .Add(x => x.JobNumber)); Script?.Execute(methodname: "BeforePost", parameters: new object[] { model }); return true; } private bool ProcessDirectCostLine(IDataModel model, StockMovement stockMovement, StockMovementTimberlineDirectCost line) { return Script?.Execute(methodname: "ProcessDirectCostLine", parameters: new object[] { model, stockMovement, line }) != false; } private bool ProcessGLLine(IDataModel model, StockMovement stockMovement, StockMovementTimberlineGL line) { return Script?.Execute(methodname: "ProcessGLLine", parameters: new object[] { model, stockMovement, line }) != false; } private StockMovementTimberlineResult DoProcess(IDataModel model) { var result = new StockMovementTimberlineResult(); var firstMovements = model.GetTable(); var full = model.GetTable("FullTransactions") .ToObjects().GroupBy(x => x.Transaction); var products = model.GetTable().ToObjects() .ToDictionary(x => x.ID, x => x); var jobs = model.GetTable().ToObjects() .ToDictionary(x => x.ID, x => x); StockMovementTimberlineDirectCost CreateDirectCost(StockMovement movement) { var job = jobs[movement.Job.ID]; var product = products[movement.Product.ID]; var directCost = new StockMovementTimberlineDirectCost { Job = job.JobNumber, Extra = "", CostCode = product.CostCentre.Code, Category = "", Units = movement.Units, UnitCost = movement.Cost, TransactionType = StockMovementTimberlineTransactionType.IVCost }; return ModifyLine(directCost, movement); } T ModifyLine(T line, StockMovement movement) where T : IStockMovementTimberlineLine { var product = products[movement.Product.ID]; line.TransactionDate = movement.Date; line.AccountingDate = movement.Date; line.Description = product.Name; line.Amount = movement.Value; line.CreditAccount = product.PurchaseGL.Code; return line; } foreach (var transaction in full) { var movements = new List(); foreach(var movement in transaction) { if (movement.Type == StockMovementType.Issue || movement.Type == StockMovementType.Receive) { // Ignore these ones. result.AddSuccess(movement, null); } else { // So we only care about transfers and stocktakes movements.Add(movement); } } if(movements.Count == 1) { var mvt = movements[0]; if(mvt.Type == StockMovementType.StockTake) { if(mvt.Job.ID == Guid.Empty) { var gl = new StockMovementTimberlineGL { }; gl = ModifyLine(gl, mvt); if (ProcessGLLine(model, mvt, gl)) { result.AddSuccess(mvt, gl); } else { result.AddFailed(mvt, "Failed by script."); } } else { var dc = CreateDirectCost(mvt); if (ProcessDirectCostLine(model, mvt, dc)) { result.AddSuccess(mvt, dc); } else { result.AddFailed(mvt, "Failed by script."); } } } else { result.AddSuccess(mvt, null); } } else if(movements.Count == 2) { var mvtFrom = movements[0]; var mvtTo = movements[1]; if(mvtFrom.Job.ID == mvtTo.Job.ID) { // Ignore these ones. result.AddSuccess(mvtFrom, null); result.AddSuccess(mvtTo, null); } else if(mvtFrom.Job.ID == Guid.Empty || mvtTo.Job.ID == Guid.Empty) { var jobMvt = mvtFrom.Job.ID == Guid.Empty ? mvtTo : mvtFrom; var directCost = CreateDirectCost(jobMvt); if(ProcessDirectCostLine(model, jobMvt, directCost)) { result.AddSuccess(mvtFrom, directCost); result.AddSuccess(mvtTo, directCost); } else { result.AddFailed(mvtFrom, "Failed by script."); result.AddFailed(mvtTo, "Failed by script."); } } else { var directCostFrom = CreateDirectCost(mvtFrom); var directCostTo = CreateDirectCost(mvtTo); if (ProcessDirectCostLine(model, mvtFrom, directCostFrom)) { result.AddSuccess(mvtFrom, directCostFrom); } else { result.AddFailed(mvtFrom, "Failed by script."); } if (ProcessDirectCostLine(model, mvtTo, directCostTo)) { result.AddSuccess(mvtTo, directCostTo); } else { result.AddFailed(mvtTo, "Failed by script."); } } } } return result; } public IPostResult Process(IDataModel model) { var result = DoProcess(model); var dlg = new SaveFileDialog() { Title = "Select Output File", Filter = "CSV Files (*.csv)|*.csv" }; if (dlg.ShowDialog() == true) { using (var writer = new StreamWriter(dlg.FileName)) { using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); foreach (var line in result.Exports.Distinct()) { // Write the record. if(line is StockMovementTimberlineDirectCost dc) { csv.WriteRecord(dc); } else if(line is StockMovementTimberlineGL gl) { csv.WriteRecord(gl); } csv.NextRecord(); } } } else { throw new PostCancelledException(); } return result; } public void AfterPost(IDataModel model, IPostResult result) { Script?.Execute(methodname: "AfterPost", parameters: new object[] { model }); } } public class StockMovementTimberlinePosterEngine : TimberlinePosterEngine { }