소스 검색

Added PostedReference field and made BIllLine and PurchaseOrderItem IPostableFragment. Added links for GL codes on PurchaseOrderItem.
Renamed PO.Notes to PO.Description, obsoleting the old property; also new database update script to transfer.
Implemented POTimberline posting.

Kenric Nugteren 1 년 전
부모
커밋
b365ad417a

+ 3 - 0
prs.classes/Entities/Bill/Bill.cs

@@ -71,6 +71,9 @@ namespace Comal.Classes
         [NullEditor]
         public string PostedNote { get; set; }
 
+        [NullEditor]
+        public string PostedReference { get; set; }
+
         protected override void Init()
         {
             base.Init();

+ 4 - 1
prs.classes/Entities/Bill/BillLine.cs

@@ -10,7 +10,7 @@ namespace Comal.Classes
     }
 
     [UserTracking(typeof(Bill))]
-    public class BillLine : Entity, IPersistent, IRemotable, IOneToMany<Bill>, ITaxable, ILicense<AccountsPayableLicense>
+    public class BillLine : Entity, IPersistent, IRemotable, IOneToMany<Bill>, ITaxable, ILicense<AccountsPayableLicense>, IPostableFragment
     {
         [EntityRelationship(DeleteAction.Cascade)]
         [NullEditor]
@@ -37,6 +37,9 @@ namespace Comal.Classes
         [DoubleEditor(Summary = Summary.Sum)]
         public double IncTax { get; set; }
 
+        [NullEditor]
+        public string PostedReference { get; set; }
+
         protected override void Init()
         {
             base.Init();

+ 4 - 0
prs.classes/Entities/GLCode/GLCodeLink.cs

@@ -7,6 +7,10 @@ namespace Comal.Classes
 
     public class GLCodeLink : EntityLink<GLCode>
     {
+        public GLCodeLink(Func<BaseObject>? entity) : base(entity)
+        {
+        }
+
         [LookupEditor(typeof(GLCode))]
         public override Guid ID { get; set; }
 

+ 3 - 0
prs.classes/Entities/Invoice/Invoice.cs

@@ -147,6 +147,9 @@ namespace Comal.Classes
         [NullEditor]
         public string PostedNote { get; set; }
 
+        [NullEditor]
+        public string PostedReference { get; set; }
+
         public Expression<Func<Invoice, int>> AutoIncrementField()
         {
             return x => x.Number;

+ 2 - 2
prs.classes/Entities/Product/Product.cs

@@ -192,8 +192,8 @@ namespace Comal.Classes
             Units = new ProductUOMLink();
             Group = new ProductGroupLink();
             CostCentre = new CostCentreLink();
-            PurchaseGL = new GLCodeLink();
-            SellGL = new GLCodeLink();
+            PurchaseGL = new GLCodeLink(() => this);
+            SellGL = new GLCodeLink(() => this);
             CostSheetSection = new CostSheetSectionLink();
 
             PricingStrategy = ProductPricingStrategy.Standard;

+ 5 - 0
prs.classes/Entities/Product/ProductLink.cs

@@ -60,6 +60,10 @@ namespace Comal.Classes
         [RequiredColumn]
         public TaxCodeLink TaxCode { get; set; }
 
+        [NullEditor]
+        [RequiredColumn]
+        public GLCodeLink PurchaseGL { get; set; }
+
         [NullEditor]
         public bool NonStock { get; set; }
 
@@ -114,6 +118,7 @@ namespace Comal.Classes
             Units = new ProductUOMLink();
             DefaultLocation = new StockLocationLink(LinkedEntity);
             TaxCode = new TaxCodeLink(LinkedEntity);
+            PurchaseGL = new GLCodeLink(LinkedEntity);
             NonStock = true;
             Image = new ImageDocumentLink();
             DigitalForm = new DigitalFormLink();

+ 7 - 0
prs.classes/Entities/PurchaseOrder/PurchaseOrder.cs

@@ -90,6 +90,10 @@ namespace Comal.Classes
 
         [MemoEditor]
         [EditorSequence(2)]
+        public string Description { get; set; }
+
+        [NullEditor]
+        [Obsolete("Notes is dumb, because Description exists")]
         public string Notes { get; set; }
 
         [EditorSequence(3)]
@@ -161,6 +165,9 @@ namespace Comal.Classes
         [NullEditor]
         public string PostedNote { get; set; }
 
+        [NullEditor]
+        public string PostedReference { get; set; }
+
         public Expression<Func<PurchaseOrder, string>> AutoIncrementField() => x => x.PONumber;
         public Filter<PurchaseOrder> AutoIncrementFilter() => null;
         public string AutoIncrementPrefix() => PONumberPrefix;

+ 9 - 1
prs.classes/Entities/PurchaseOrder/PurchaseOrderItem.cs

@@ -39,7 +39,7 @@ namespace Comal.Classes
     [UserTracking(typeof(Bill))]
     [Caption("Purchase Order Items")]
     public class PurchaseOrderItem : StockEntity, IRemotable, IPersistent, IOneToMany<PurchaseOrder>, ITaxable, IOneToMany<Consignment>, IOneToMany<Job>,
-        ILicense<AccountsPayableLicense>, IJobMaterial
+        ILicense<AccountsPayableLicense>, IJobMaterial, IPostableFragment
     {
         [EntityRelationship(DeleteAction.SetNull)]
         [NullEditor]
@@ -100,6 +100,9 @@ namespace Comal.Classes
         [EditorSequence(10)]
         public TaxCodeLink TaxCode { get; set; }
 
+        [EditorSequence(11)]
+        public GLCodeLink PurchaseGL { get; set; }
+
         [EditorSequence(14)]
         [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Hidden, Summary = Summary.Sum)]
         public double Balance { get; set; }
@@ -168,6 +171,9 @@ namespace Comal.Classes
         [CurrencyEditor(Visible = Visible.Default, Summary = Summary.Sum)]
         public double IncTax { get; set; }
 
+        [NullEditor]
+        public string PostedReference { get; set; }
+
         protected override void Init()
         {
             base.Init();
@@ -178,6 +184,7 @@ namespace Comal.Classes
             Style = new ProductStyleLink();
 
             TaxCode = new TaxCodeLink(() => this);
+            PurchaseGL = new GLCodeLink(() => this);
 
             Packet = new ManufacturingPacketLink();
 
@@ -198,6 +205,7 @@ namespace Comal.Classes
             LinkedProperties.Register<PurchaseOrderItem, ProductLink, String>(x => x.Product, x => x.Name, x => x.Description);
 
             LinkedProperties.Register<PurchaseOrderItem, TaxCodeLink, Guid>(x => x.Product.TaxCode, x => x.ID, x => x.TaxCode.ID);
+            LinkedProperties.Register<PurchaseOrderItem, GLCodeLink, Guid>(x => x.Product.PurchaseGL, x => x.ID, x => x.PurchaseGL.ID);
             LinkedProperties.Register<PurchaseOrderItem, TaxCodeLink, double>(x => x.Product.TaxCode, x => x.Rate, x => x.TaxCode.Rate);
             LinkedProperties.Register<PurchaseOrderItem, ProductStyleLink, Guid>(x => x.Product.DefaultStyle, x => x.ID, x => x.Style.ID);
 

+ 1 - 1
prs.desktop/Panels/Factory/FactoryPanel.xaml.cs

@@ -641,7 +641,7 @@ namespace PRSDesktop
             //).Rows.FirstOrDefault()?.ToObject<Supplier>());
             order.SupplierLink.ID = window.SupplierID;
             order.SupplierLink.Name = window.SupplierName; //supplier != null ? supplier.Name : "Unknown Supplier";
-            order.Notes = string.Format("Materials Processing Request raised by {0} from Factory Floor", Employees[myID]);
+            order.Description = string.Format("Materials Processing Request raised by {0} from Factory Floor", Employees[myID]);
             order.RaisedBy.ID = myID;
             order.IssuedBy.ID = myID;
             order.IssuedDate = DateTime.Today;

+ 1 - 1
prs.desktop/Panels/Products/Reservation Management/JobRequisitionPurchasing.xaml.cs

@@ -46,7 +46,7 @@ namespace PRSDesktop
                             x => x.SupplierLink.ID,
                             x => x.SupplierLink.Code,
                             x => x.SupplierLink.Name,
-                            x => x.Notes,
+                            x => x.Description,
                             x => x.Category.ID,
                             x => x.Category.Code,
                             x => x.Category.Description,

+ 2 - 2
prs.desktop/Panels/Suppliers/SupplierPurchaseOrderPanel.xaml.cs

@@ -27,7 +27,7 @@ namespace PRSDesktop
             Orders.HiddenColumns.Add(x => x.PONumber);
             Orders.HiddenColumns.Add(x => x.SupplierLink.Code);
             Orders.HiddenColumns.Add(x => x.SupplierLink.Name);
-            Orders.HiddenColumns.Add(x => x.Notes);
+            Orders.HiddenColumns.Add(x => x.Description);
             Orders.HiddenColumns.Add(x => x.IssuedDate);
             Orders.HiddenColumns.Add(x => x.IssuedBy.Name);
             Orders.HiddenColumns.Add(x => x.DueDate);
@@ -126,7 +126,7 @@ namespace PRSDesktop
             SupplierCode.Text = row == null ? "" : row.Get<PurchaseOrder, string>(x => x.SupplierLink.Code);
             SupplierName.Text = row == null ? "" : row.Get<PurchaseOrder, string>(x => x.SupplierLink.Name);
 
-            Description.Text = row == null ? "" : row.Get<PurchaseOrder, string>(x => x.Notes);
+            Description.Text = row == null ? "" : row.Get<PurchaseOrder, string>(x => x.Description);
 
             Issued.Text = row == null ? "" : CheckDate(row.Get<PurchaseOrder, DateTime>(x => x.IssuedDate));
             IssuedBy.Text = row == null ? "" : row.Get<PurchaseOrder, string>(x => x.IssuedBy.Name);

+ 38 - 0
prs.desktop/Panels/Suppliers/SupplierPurchaseOrders.cs

@@ -4,18 +4,23 @@ using System.Linq;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Media.Imaging;
 using Comal.Classes;
 using InABox.Clients;
 using InABox.Core;
 using InABox.DynamicGrid;
 using InABox.WPF;
 using sun.misc;
+using Syncfusion.UI.Xaml.Diagram.Controls;
 
 namespace PRSDesktop
 {
     public class SupplierPurchaseOrders : DynamicDataGrid<PurchaseOrder>
     {
         private readonly Button close;
+        private static readonly BitmapImage? tick = PRSDesktop.Resources.tick.AsBitmapImage();
+        private static readonly BitmapImage? warning = PRSDesktop.Resources.warning.AsBitmapImage();
+        private static readonly BitmapImage? refresh = PRSDesktop.Resources.refresh.AsBitmapImage();
 
         public SupplierPurchaseOrders()
         {
@@ -29,10 +34,43 @@ namespace PRSDesktop
             
             HiddenColumns.Add(x => x.ClosedDate);
             HiddenColumns.Add(x => x.Balance);
+            HiddenColumns.Add(x => x.PostedStatus);
+            HiddenColumns.Add(x => x.PostedNote);
+
+            ActionColumns.Add(new DynamicImageColumn(Posted_Image, null)
+            {
+                ToolTip = Posted_ToolTip
+            });
 
             close = AddButton("Close Order", null, CloseOrder);
             close.IsEnabled = false;
         }
+        private FrameworkElement? Posted_ToolTip(DynamicActionColumn column, CoreRow? row)
+        {
+            if (row is null)
+            {
+                return column.TextToolTip("Purchase Order Processed Status");
+            }
+            return column.TextToolTip(row.Get<PurchaseOrder, PostedStatus>(x => x.PostedStatus) switch
+            {
+                PostedStatus.PostFailed => "Post failed: " + row.Get<PurchaseOrder, string>(x => x.PostedNote),
+                PostedStatus.RequiresRepost => "Repost required: " + row.Get<PurchaseOrder, string>(x => x.PostedNote),
+                PostedStatus.Posted => "Processed",
+                PostedStatus.NeverPosted or _ => "Not posted yet",
+            });
+        }
+
+        private BitmapImage? Posted_Image(CoreRow? row)
+        {
+            if (row is null) return tick;
+            return row.Get<PurchaseOrder, PostedStatus>(x => x.PostedStatus) switch
+            {
+                PostedStatus.PostFailed => warning,
+                PostedStatus.Posted => tick,
+                PostedStatus.RequiresRepost => refresh,
+                PostedStatus.NeverPosted or _ => null,
+            };
+        }
 
         private Dictionary<string, object> SupplierPurchaseOrders_OnEditorValueChanged(object sender, string name, object value)
         {

+ 1 - 0
prs.shared/Database Update Scripts/DatabaseUpdateScripts.cs

@@ -30,6 +30,7 @@ namespace PRS.Shared
             DataUpdater.RegisterUpdateScript<Update_7_14>();
             DataUpdater.RegisterUpdateScript<Update_7_19>();
             DataUpdater.RegisterUpdateScript<Update_7_21>();
+            DataUpdater.RegisterUpdateScript<Update_7_24a>();
         }
     }
 }

+ 27 - 0
prs.shared/Database Update Scripts/Update_7_24a.cs

@@ -0,0 +1,27 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Shared
+{
+    public class Update_7_24a : DatabaseUpdateScript
+    {
+        public override VersionNumber Version => new(7, 24, "a");
+
+        public override bool Update()
+        {
+            var pos = DbFactory.Provider.Query<PurchaseOrder>().ToObjects<PurchaseOrder>().ToList();
+            foreach(var po in pos)
+            {
+                po.Description = po.Description.NotWhiteSpaceOr(po.Notes);
+            }
+            DbFactory.Provider.Save(pos);
+            return true;
+        }
+    }
+}

+ 10 - 2
prs.shared/Posters/Timberline/BillTimberlinePoster.cs

@@ -230,13 +230,15 @@ public class Module
     {
         public ScriptDocument? Script { get; set; }
 
+        public event ITimberlinePoster<Bill, BillTimberlineSettings>.AddFragmentCallback? AddFragment;
+
         public bool BeforePost(IDataModel<Bill> model)
         {
             model.SetIsDefault<Document>(false, alias: "CompanyLogo");
             model.SetIsDefault<CoreTable>(false, alias: "CompanyInformation");
             model.SetIsDefault<Employee>(false);
 
-            model.SetColumns<Bill>(new Columns<Bill>(x => x.ID)
+            model.SetColumns(new Columns<Bill>(x => x.ID)
                 .Add(x => x.SupplierLink.Code)
                 .Add(x => x.Number)
                 .Add(x => x.IncTax)
@@ -347,13 +349,19 @@ public class Module
                     {
                         apdf.Commitment = poItem.PONumber;
                         apdf.Job = poItem.Job.JobNumber;
-                        // apdf.CommitmentLineItem : Leaving this blank
+                        if (int.TryParse(poItem.ReceivedReference, out var itemNumber))
+                        {
+                            apdf.CommitmentLineItem = itemNumber;
+                            billLine.PostedReference = poItem.ReceivedReference;
+                        }
                         apdf.Units = poItem.Qty;
                         apdf.UnitCost = poItem.Cost;
                     }
 
                     ProcessLine(model, billLine, apdf);
                     apif.Distributions.Add(apdf);
+
+                    AddFragment?.Invoke(billLine);
                 }
                 apifs.Add(apif);
             }

+ 192 - 116
prs.shared/Posters/Timberline/PurchaseOrderTimberlinePoster.cs

@@ -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();
                     }
                 }