|
@@ -13,180 +13,146 @@ using System.Linq;
|
|
|
using System.Text;
|
|
|
using System.Threading.Tasks;
|
|
|
using Microsoft.Win32;
|
|
|
+using CsvHelper.TypeConversion;
|
|
|
+using CsvHelper.Configuration;
|
|
|
+using System.Reflection;
|
|
|
+using System.Windows;
|
|
|
|
|
|
namespace PRS.Shared
|
|
|
{
|
|
|
+ public enum PurchaseOrderTimberlineCommitmentType
|
|
|
+ {
|
|
|
+ Subcontract = 1,
|
|
|
+ PO = 2
|
|
|
+ }
|
|
|
+
|
|
|
+ public class POTimberlineCommitmentTypeConverter : DefaultTypeConverter
|
|
|
+ {
|
|
|
+ public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData)
|
|
|
+ {
|
|
|
+ if(Enum.TryParse<PurchaseOrderTimberlineCommitmentType>(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 PurchaseOrderTimberlineCommitmentType type)
|
|
|
+ {
|
|
|
+ return ((int)type).ToString();
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public class PurchaseOrderTimberlineHeader
|
|
|
{
|
|
|
[Ignore]
|
|
|
public List<PurchaseOrderTimberlineLine> Lines { get; set; } = new();
|
|
|
|
|
|
+ [Index(0)]
|
|
|
+ public string RecordID { get; set; } = "C";
|
|
|
+
|
|
|
[Index(1)]
|
|
|
- public string RecordID { get; set; } = "APIF";
|
|
|
+ public string CommitmentID { get; set; }
|
|
|
|
|
|
[Index(2)]
|
|
|
- public string Vendor { get; set; }
|
|
|
+ [TypeConverter(typeof(POTimberlineCommitmentTypeConverter))]
|
|
|
+ public PurchaseOrderTimberlineCommitmentType CommitmentType { get; set; }
|
|
|
|
|
|
[Index(3)]
|
|
|
- public string Invoice { get; set; }
|
|
|
+ public string Description { get; set; }
|
|
|
|
|
|
[Index(4)]
|
|
|
- public string Description { get; set; }
|
|
|
+ public string VendorID { get; set; }
|
|
|
|
|
|
[Index(5)]
|
|
|
- public double Amount { get; set; }
|
|
|
+ public DateTime Date { get; set; }
|
|
|
|
|
|
[Index(6)]
|
|
|
- public double Tax { get; set; }
|
|
|
+ public double RetainagePercent { get; set; }
|
|
|
|
|
|
[Index(7)]
|
|
|
- public double DiscountOffered { get; set; }
|
|
|
+ public bool CommittedToJC { get; set; }
|
|
|
|
|
|
[Index(8)]
|
|
|
- public double MiscDeduction { get; set; }
|
|
|
+ public bool Closed { get; set; }
|
|
|
|
|
|
[Index(9)]
|
|
|
- public DateTime InvoiceDate { get; set; }
|
|
|
-
|
|
|
- [Index(10)]
|
|
|
- public DateTime DateReceived { get; set; }
|
|
|
-
|
|
|
- [Index(11)]
|
|
|
- public DateTime DiscountDate { get; set; }
|
|
|
-
|
|
|
- [Index(12)]
|
|
|
- public DateTime PaymentDate { get; set; }
|
|
|
-
|
|
|
- [Index(13)]
|
|
|
- public DateTime AccountingDate { get; set; }
|
|
|
-
|
|
|
- [Index(14)]
|
|
|
- public string InvoiceCode1 { get; set; }
|
|
|
-
|
|
|
- [Index(15)]
|
|
|
- public string InvoiceCode2 { get; set; }
|
|
|
-
|
|
|
- [Index(16)]
|
|
|
- public string SmryPayeeName { get; set; }
|
|
|
+ public bool Printed { get; set; }
|
|
|
|
|
|
- [Index(17)]
|
|
|
- public string SmryPayeeAddress1 { get; set; }
|
|
|
-
|
|
|
- [Index(18)]
|
|
|
- public string SmryPayeeAddress2 { get; set; }
|
|
|
-
|
|
|
- [Index(19)]
|
|
|
- public string SmryPayeeCity { get; set; }
|
|
|
-
|
|
|
- [Index(20)]
|
|
|
- public string SmryPayeeState { get; set; }
|
|
|
-
|
|
|
- [Index(21)]
|
|
|
- public string SmryPayeeZip { get; set; }
|
|
|
+ /// <summary>
|
|
|
+ /// Dictionary of extra fields to write; the key is the 0-based index of the column in which we are writing.
|
|
|
+ /// If the index is that of any of the explicit fields of this class, nothing happens.
|
|
|
+ /// </summary>
|
|
|
+ [Ignore]
|
|
|
+ public Dictionary<int, string> AdditionalFields { get; set; } = new();
|
|
|
}
|
|
|
|
|
|
public class PurchaseOrderTimberlineLine
|
|
|
{
|
|
|
+ [Index(0)]
|
|
|
+ public string RecordID { get; set; } = "CI";
|
|
|
+
|
|
|
[Index(1)]
|
|
|
- public string RecordID { get; set; } = "APDF";
|
|
|
+ public string CommitmentID { get; set; }
|
|
|
|
|
|
[Index(2)]
|
|
|
- public string Commitment { get; set; }
|
|
|
+ public int ItemNumber { get; set; }
|
|
|
|
|
|
[Index(3)]
|
|
|
- public int CommitmentLineItem { get; set; }
|
|
|
+ public string Description { get; set; }
|
|
|
|
|
|
[Index(4)]
|
|
|
- public string Equipment { get; set; }
|
|
|
+ public double RetainagePercent { get; set; }
|
|
|
|
|
|
[Index(5)]
|
|
|
- public string EQCostCode { get; set; }
|
|
|
+ public DateTime DeliveryDate { get; set; }
|
|
|
|
|
|
[Index(6)]
|
|
|
- public string Job { get; set; }
|
|
|
+ public string ScopeOfWork { get; set; }
|
|
|
|
|
|
[Index(7)]
|
|
|
- public string Extra { get; set; }
|
|
|
+ public string Job { get; set; }
|
|
|
|
|
|
[Index(8)]
|
|
|
- public string CostCode { get; set; }
|
|
|
+ public string Extra { get; set; }
|
|
|
|
|
|
[Index(9)]
|
|
|
- public string Category { get; set; }
|
|
|
+ public string CostCode { get; set; }
|
|
|
|
|
|
[Index(10)]
|
|
|
- public string BLStdItem { get; set; }
|
|
|
+ public string Category { get; set; }
|
|
|
|
|
|
[Index(11)]
|
|
|
- public string Reserved { get; set; }
|
|
|
+ public string TaxGroup { get; set; }
|
|
|
|
|
|
[Index(12)]
|
|
|
- public string ExpenseAccount { get; set; }
|
|
|
+ public string Tax { get; set; }
|
|
|
|
|
|
[Index(13)]
|
|
|
- public string APAccount { get; set; }
|
|
|
+ public double Units { get; set; }
|
|
|
|
|
|
[Index(14)]
|
|
|
- public double TaxablePayments { get; set; }
|
|
|
+ public double UnitCost { get; set; }
|
|
|
|
|
|
[Index(15)]
|
|
|
- public string TaxGroup { get; set; }
|
|
|
+ public string UnitDescription { get; set; }
|
|
|
|
|
|
[Index(16)]
|
|
|
- public double Units { get; set; }
|
|
|
-
|
|
|
- [Index(17)]
|
|
|
- public double UnitCost { get; set; }
|
|
|
-
|
|
|
- [Index(18)]
|
|
|
public double Amount { get; set; }
|
|
|
|
|
|
- [Index(19)]
|
|
|
- public double Tax { get; set; }
|
|
|
-
|
|
|
- [Index(20)]
|
|
|
- public double TaxLiability { get; set; }
|
|
|
-
|
|
|
- [Index(21)]
|
|
|
- public double DiscountOffered { get; set; }
|
|
|
-
|
|
|
- [Index(22)]
|
|
|
- public double Retainage { get; set; }
|
|
|
-
|
|
|
- [Index(23)]
|
|
|
- public double MiscDeduction { get; set; }
|
|
|
-
|
|
|
- [Index(24)]
|
|
|
- [BooleanTrueValues("t", "T", "1", "Y", "y")]
|
|
|
- [BooleanFalseValues("f", "F", "0", "N", "n")]
|
|
|
- public bool TaxablePaymentsExempt { get; set; }
|
|
|
-
|
|
|
- [Index(25)]
|
|
|
- public string DistCode { get; set; }
|
|
|
-
|
|
|
- [Index(26)]
|
|
|
- public string MiscEntry1 { get; set; }
|
|
|
-
|
|
|
- [Index(27)]
|
|
|
- public double MiscEntryUnits1 { get; set; }
|
|
|
-
|
|
|
- [Index(28)]
|
|
|
- public string MiscEntry2 { get; set; }
|
|
|
-
|
|
|
- [Index(29)]
|
|
|
- public double MiscEntryUnits2 { get; set; }
|
|
|
-
|
|
|
- [Index(30)]
|
|
|
- public double MeterOdometer { get; set; }
|
|
|
-
|
|
|
- [Index(31)]
|
|
|
- public string Description { get; set; }
|
|
|
-
|
|
|
- [Index(32)]
|
|
|
- public string Authorization { get; set; }
|
|
|
+ [Index(17)]
|
|
|
+ public bool BoughtOut { get; set; }
|
|
|
|
|
|
- [Index(33)]
|
|
|
- public string JointPayee { get; set; }
|
|
|
+ /// <summary>
|
|
|
+ /// Dictionary of extra fields to write; the key is the 0-based index of the column in which we are writing.
|
|
|
+ /// If the index is that of any of the explicit fields of this class, nothing happens.
|
|
|
+ /// </summary>
|
|
|
+ [Ignore]
|
|
|
+ public Dictionary<int, string> AdditionalFields { get; set; } = new();
|
|
|
}
|
|
|
|
|
|
public class PurchaseOrderTimberlineSettings : TimberlinePosterSettings<PurchaseOrder>
|
|
@@ -227,12 +193,35 @@ public class Module
|
|
|
{
|
|
|
public ScriptDocument? Script { get; set; }
|
|
|
|
|
|
+ public event ITimberlinePoster<PurchaseOrder, PurchaseOrderTimberlineSettings>.AddFragmentCallback? AddFragment;
|
|
|
+
|
|
|
public bool BeforePost(IDataModel<PurchaseOrder> model)
|
|
|
{
|
|
|
model.SetIsDefault<Document>(false, alias: "CompanyLogo");
|
|
|
model.SetIsDefault<CoreTable>(false, alias: "CompanyInformation");
|
|
|
model.SetIsDefault<Employee>(false);
|
|
|
|
|
|
+ model.SetIsDefault<PurchaseOrderItem>(true, alias: "PurchaseOrder_PurchaseOrderItem");
|
|
|
+
|
|
|
+ model.SetColumns(new Columns<PurchaseOrder>(x => x.ID)
|
|
|
+ .Add(x => x.PONumber)
|
|
|
+ .Add(x => x.Description)
|
|
|
+ .Add(x => x.SupplierLink.Code)
|
|
|
+ .Add(x => x.IssuedDate)
|
|
|
+ .Add(x => x.ClosedDate));
|
|
|
+ model.SetColumns(new Columns<PurchaseOrderItem>(x => x.ID)
|
|
|
+ .Add(x => x.PurchaseOrderLink.ID)
|
|
|
+ .Add(x => x.PostedReference)
|
|
|
+ .Add(x => x.Description)
|
|
|
+ .Add(x => x.ReceivedDate)
|
|
|
+ .Add(x => x.Job.JobNumber)
|
|
|
+ .Add(x => x.TaxCode.Code)
|
|
|
+ .Add(x => x.Qty)
|
|
|
+ .Add(x => x.Cost)
|
|
|
+ .Add(x => x.Dimensions.UnitSize)
|
|
|
+ .Add(x => x.IncTax),
|
|
|
+ alias: "PurchaseOrder_PurchaseOrderItem");
|
|
|
+
|
|
|
Script?.Execute(methodname: "BeforePost", parameters: new object[] { model });
|
|
|
return true;
|
|
|
}
|
|
@@ -248,6 +237,8 @@ public class Module
|
|
|
|
|
|
private List<PurchaseOrderTimberlineHeader> DoProcess(IDataModel<PurchaseOrder> model)
|
|
|
{
|
|
|
+ List<IPostableFragment> Fragments = new List<IPostableFragment>();
|
|
|
+
|
|
|
var cs = new List<PurchaseOrderTimberlineHeader>();
|
|
|
|
|
|
var lines = model.GetTable<PurchaseOrderItem>("PurchaseOrder_PurchaseOrderItem").ToObjects<PurchaseOrderItem>()
|
|
@@ -256,17 +247,75 @@ public class Module
|
|
|
{
|
|
|
var c = new PurchaseOrderTimberlineHeader
|
|
|
{
|
|
|
+ CommitmentID = purchaseOrder.PONumber,
|
|
|
+ CommitmentType = PurchaseOrderTimberlineCommitmentType.PO,
|
|
|
+ Description = purchaseOrder.Description,
|
|
|
+ VendorID = purchaseOrder.SupplierLink.Code,
|
|
|
+ Date = purchaseOrder.IssuedDate,
|
|
|
+ // RetainagePercent
|
|
|
+ // Committed to JC
|
|
|
+ Closed = purchaseOrder.ClosedDate != DateTime.MinValue,
|
|
|
+ // Printed
|
|
|
};
|
|
|
ProcessHeader(model, purchaseOrder, c);
|
|
|
|
|
|
- foreach (var purchaseOrderItem in lines.GetValueOrDefault(purchaseOrder.ID) ?? Enumerable.Empty<PurchaseOrderItem>())
|
|
|
+ // Dictionary from line number to POItem.
|
|
|
+ var items = new Dictionary<int, PurchaseOrderItem>();
|
|
|
+ var POItems = lines.GetValueOrDefault(purchaseOrder.ID)?.ToList() ?? new List<PurchaseOrderItem>();
|
|
|
+ foreach (var purchaseOrderItem in POItems)
|
|
|
{
|
|
|
+ if(int.TryParse(purchaseOrderItem.PostedReference, out var itemNumber))
|
|
|
+ {
|
|
|
+ if (items.TryGetValue(itemNumber, out var oldItem))
|
|
|
+ {
|
|
|
+ MessageBox.Show($"Warning: Multiple PurchaseOrder Items have the same line number for export; the line number for '{purchaseOrderItem.Description}' will be changed in the export.");
|
|
|
+ Logger.Send(LogType.Error, "", $"Purchase Order Post: Multiple POItems with the same Line Number; changing line number of POItem {purchaseOrderItem.ID}");
|
|
|
+ purchaseOrderItem.PostedReference = "";
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ items[itemNumber] = purchaseOrderItem;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var purchaseOrderItem in POItems)
|
|
|
+ {
|
|
|
+ if (!int.TryParse(purchaseOrderItem.PostedReference, out var itemNumber))
|
|
|
+ {
|
|
|
+ itemNumber = 1;
|
|
|
+ while(items.ContainsKey(itemNumber))
|
|
|
+ {
|
|
|
+ ++itemNumber;
|
|
|
+ }
|
|
|
+
|
|
|
+ items[itemNumber] = purchaseOrderItem;
|
|
|
+ purchaseOrderItem.PostedReference = itemNumber.ToString();
|
|
|
+ }
|
|
|
var ci = new PurchaseOrderTimberlineLine
|
|
|
{
|
|
|
+ CommitmentID = purchaseOrder.PONumber,
|
|
|
+ ItemNumber = itemNumber,
|
|
|
+ Description = purchaseOrderItem.Description,
|
|
|
+ // RetainagePercent = ,
|
|
|
+ DeliveryDate = purchaseOrderItem.ReceivedDate,
|
|
|
+ //ScopeOfWork
|
|
|
+ Job = purchaseOrderItem.Job.JobNumber,
|
|
|
+ //Extra = purchaseOrderItem.Job
|
|
|
+ //CostCode = purchaseOrderItem.c
|
|
|
+ //Category = purchaseOrderItem.cat
|
|
|
+ TaxGroup = purchaseOrderItem.TaxCode.Code,
|
|
|
+ Units = purchaseOrderItem.Qty,
|
|
|
+ UnitCost = purchaseOrderItem.Cost,
|
|
|
+ UnitDescription = purchaseOrderItem.Dimensions.UnitSize,
|
|
|
+ Amount = purchaseOrderItem.IncTax,
|
|
|
+ // BoughtOut
|
|
|
};
|
|
|
|
|
|
ProcessLine(model, purchaseOrderItem, ci);
|
|
|
c.Lines.Add(ci);
|
|
|
+
|
|
|
+ AddFragment?.Invoke(purchaseOrderItem);
|
|
|
}
|
|
|
cs.Add(c);
|
|
|
}
|
|
@@ -275,7 +324,7 @@ public class Module
|
|
|
|
|
|
public bool Process(IDataModel<PurchaseOrder> model)
|
|
|
{
|
|
|
- var cs = DoProcess(model);
|
|
|
+ var POs = DoProcess(model);
|
|
|
|
|
|
var dlg = new SaveFileDialog()
|
|
|
{
|
|
@@ -286,13 +335,40 @@ public class Module
|
|
|
{
|
|
|
using var writer = new StreamWriter(dlg.FileName);
|
|
|
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
|
|
|
- foreach (var c in cs)
|
|
|
+ foreach (var po in POs)
|
|
|
{
|
|
|
- csv.WriteRecord(c);
|
|
|
+ // Write the record.
|
|
|
+ csv.WriteRecord(po);
|
|
|
+
|
|
|
+ // Current 0-based index that the writer is at.
|
|
|
+ int i = csv.Index;
|
|
|
+ foreach(var (index, field) in po.AdditionalFields.OrderBy(x => x.Key))
|
|
|
+ {
|
|
|
+ while(i < index)
|
|
|
+ {
|
|
|
+ csv.WriteField("");
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+ csv.WriteField(field);
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
csv.NextRecord();
|
|
|
- foreach (var ci in c.Lines)
|
|
|
+ foreach (var poi in po.Lines)
|
|
|
{
|
|
|
- csv.WriteRecord(ci);
|
|
|
+ csv.WriteRecord(poi);
|
|
|
+
|
|
|
+ // Current 0-based index that the writer is at.
|
|
|
+ i = csv.Index;
|
|
|
+ foreach (var (index, field) in poi.AdditionalFields.OrderBy(x => x.Key))
|
|
|
+ {
|
|
|
+ while (i < index)
|
|
|
+ {
|
|
|
+ csv.WriteField("");
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+ csv.WriteField(field);
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
csv.NextRecord();
|
|
|
}
|
|
|
}
|