Pārlūkot izejas kodu

Finishing off scripts

Kenric Nugteren 1 gadu atpakaļ
vecāks
revīzija
f481529ded

+ 54 - 2
prs.shared/Posters/MYOB/BillMYOBPoster.cs

@@ -2,6 +2,7 @@
 using InABox.Core;
 using InABox.Core.Postable;
 using InABox.Poster.MYOB;
+using InABox.Scripting;
 using MYOB.AccountRight.SDK.Contracts.Version2.Sale;
 using MYOB.AccountRight.SDK.Services.GeneralLedger;
 using MYOB.AccountRight.SDK.Services.Purchase;
@@ -10,7 +11,6 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
-using MYOBAccount = MYOB.AccountRight.SDK.Contracts.Version2.GeneralLedger.Account;
 using MYOBBill = MYOB.AccountRight.SDK.Contracts.Version2.Purchase.ServiceBill;
 using MYOBBillLine = MYOB.AccountRight.SDK.Contracts.Version2.Purchase.ServiceBillLine;
 using MYOBTaxCode = MYOB.AccountRight.SDK.Contracts.Version2.GeneralLedger.TaxCode;
@@ -19,10 +19,35 @@ namespace PRS.Shared.Posters.MYOB;
 
 public class BillMYOBPosterSettings : MYOBPosterSettings
 {
+    public override string DefaultScript(Type TPostable)
+    {
+        return @"using MYOBBill = MYOB.AccountRight.SDK.Contracts.Version2.Purchase.ServiceBill;
+using MYOBBillLine = MYOB.AccountRight.SDK.Contracts.Version2.Purchase.ServiceBillLine;
+
+public class Module
+{
+    public void BeforePost(IDataModel<Bill> model)
+    {
+        // Perform pre-processing
+    }
+
+    public void ProcessBill(IDataModel<Bill> model, Bill bill, MYOBBill myobBill)
+    {
+        // Do extra processing for a bill; throw an exception to fail this bill.
+    }
+
+    public void ProcessBillLine(IDataModel<Bill> model, BillLine billLine, MYOBBillLine myobBillLine)
+    {
+        // Do extra processing for a bill line; throw an exception to fail this bill line (and thus fail the entire bill)
+    }
+}";
+    }
 }
 
 public class BillMYOBPoster : IMYOBPoster<Bill, BillMYOBPosterSettings>
 {
+    public ScriptDocument? Script { get; set; }
+
     public MYOBConnectionData ConnectionData { get; set; }
 
     public BillMYOBPosterSettings Settings { get; set; }
@@ -43,10 +68,25 @@ public class BillMYOBPoster : IMYOBPoster<Bill, BillMYOBPosterSettings>
 
         model.SetIsDefault<Supplier>(true, alias: "Bill_Supplier");
         model.SetColumns<Supplier>(RequiredSupplierColumns(), alias: "Bill_Supplier");
+        
+        Script?.Execute(methodname: "BeforePost", parameters: new object[] { model });
 
         return true;
     }
 
+    #region Script Functions
+
+    private Result<Exception> ProcessBill(IDataModel<Bill> model, Bill bill, MYOBBill myobBill)
+    {
+        return this.WrapScript("ProcessBill", model, bill, myobBill);
+    }
+    private Result<Exception> ProcessBillLine(IDataModel<Bill> model, BillLine billLine, MYOBBillLine myobBillLine)
+    {
+        return this.WrapScript("ProcessBillLine", model, billLine, myobBillLine);
+    }
+
+    #endregion
+
     private static Columns<Bill> RequiredBillColumns()
     {
         return Columns.None<Bill>()
@@ -130,7 +170,7 @@ public class BillMYOBPoster : IMYOBPoster<Bill, BillMYOBPosterSettings>
                 }
                 myobBill.Supplier ??= new();
                 myobBill.Supplier.UID = supplierID;
-                results.AddFragment(supplier);
+                results.AddFragment(supplier, supplier.PostedStatus);
             }
 
             // myobBill.ShipToAddress = 
@@ -225,6 +265,12 @@ public class BillMYOBPoster : IMYOBPoster<Bill, BillMYOBPosterSettings>
                     line.TaxCode ??= new();
                     line.TaxCode.UID = taxCodeID;
 
+                    if(!ProcessBillLine(model, billLine, line).Get(out error))
+                    {
+                        failed = error.Message;
+                        break;
+                    }
+
                     newLines[i] = line;
                 }
 
@@ -240,6 +286,12 @@ public class BillMYOBPoster : IMYOBPoster<Bill, BillMYOBPosterSettings>
                 myobBill.Lines = [];
             }
 
+            if(!ProcessBill(model, bill, myobBill).Get(out error))
+            {
+                results.AddFailed(bill, error.Message);
+                continue;
+            }
+
             if(service.Save(ConnectionData, myobBill).Get(out var result, out error))
             {
                 bill.PostedReference = result.UID.ToString();

+ 42 - 1
prs.shared/Posters/MYOB/CustomerMYOBPoster.cs

@@ -18,11 +18,29 @@ using MYOB.AccountRight.SDK.Contracts.Version2.Sale;
 using InABox.Clients;
 using InABox.Database;
 using MYOB.AccountRight.SDK;
+using InABox.Scripting;
 
 namespace PRS.Shared.Posters.MYOB;
 
 public class CustomerMYOBPosterSettings : MYOBPosterSettings
 {
+    public override string DefaultScript(Type TPostable)
+    {
+        return @"using MYOBCustomer = MYOB.AccountRight.SDK.Contracts.Version2.Contact.Customer;
+
+public class Module
+{
+    public void BeforePost(IDataModel<Customer> model)
+    {
+        // Perform pre-processing
+    }
+
+    public void ProcessCustomer(IDataModel<Customer> model, Customer customer, MYOBCustomer myobCustomer)
+    {
+        // Do extra processing for a customer; throw an exception to fail this customer.
+    }
+}";
+    }
 }
 
 public static class ContactMYOBUtils
@@ -103,6 +121,8 @@ public class CustomerMYOBAutoRefresher : IAutoRefresher<Customer>
 
 public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettings>, IAutoRefreshPoster<Customer, CustomerMYOBAutoRefresher>
 {
+    public ScriptDocument? Script { get; set; }
+
     public CustomerMYOBPosterSettings Settings { get; set; }
     public MYOBGlobalPosterSettings GlobalSettings { get; set; }
 
@@ -117,6 +137,8 @@ public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettin
         model.SetIsDefault<Customer>(true);
         model.SetColumns<Customer>(RequiredColumns());
 
+        Script?.Execute(methodname: "BeforePost", parameters: new object[] { model });
+
         return true;
     }
 
@@ -125,6 +147,7 @@ public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettin
         return Columns.None<Customer>()
             .Add(x => x.ID)
             .Add(x => x.PostedReference)
+            .Add(x => x.PostedStatus)
             .Add(x => x.DefaultContact.Name)
             .Add(x => x.Name)
             .Add(x => x.Code)
@@ -146,6 +169,15 @@ public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettin
             .Add(x => x.ABN);
     }
 
+    #region Script Functions
+
+    private Result<Exception> ProcessCustomer(IDataModel<Customer> model, Customer customer, MYOBCustomer myobCustomer)
+    {
+        return this.WrapScript("ProcessCustomer", model, customer, myobCustomer);
+    }
+
+    #endregion
+
     public static Result<Exception> UpdateCustomer(MYOBConnectionData data, MYOBGlobalPosterSettings settings, Customer customer, MYOBCustomer myobCustomer, bool isNew)
     {
         // Documentation: https://developer.myob.com/api/myob-business-api/v2/contact/customer/
@@ -227,12 +259,20 @@ public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettin
                 }
                 var myobCustomer = new MYOBCustomer();
                 return UpdateCustomer(data, settings, customer, myobCustomer, true)
-                    .MapOk(() => service.Save(data, myobCustomer).MapOk(x => x.UID))
+                    .MapOk(() => service.Save(data, myobCustomer)
+                        .MapOk(x =>
+                        {
+                            // Marking as repost because a script may not have run.
+                            customer.PostedStatus = PostedStatus.RequiresRepost;
+                            customer.PostedReference = x.UID.ToString();
+                            return x.UID;
+                        }))
                     .Flatten();
             }
             else
             {
                 customer.PostedReference = customers.Items[0].UID.ToString();
+                customer.PostedStatus = PostedStatus.RequiresRepost;
                 return Result.Ok(customers.Items[0].UID);
             }
         }).Flatten();
@@ -295,6 +335,7 @@ public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettin
             }
 
             if(UpdateCustomer(ConnectionData, GlobalSettings, customer, myobCustomer, isNew)
+                .MapOk(() => ProcessCustomer(model, customer, myobCustomer)).Flatten()
                 .MapOk(() => service.Save(ConnectionData, myobCustomer)).Flatten()
                 .Get(out var result, out error))
             {

+ 54 - 1
prs.shared/Posters/MYOB/InvoiceMYOBPoster.cs

@@ -1,6 +1,7 @@
 using Comal.Classes;
 using InABox.Core;
 using InABox.Poster.MYOB;
+using InABox.Scripting;
 using MYOB.AccountRight.SDK.Contracts.Version2.Sale;
 using MYOB.AccountRight.SDK.Services.Contact;
 using MYOB.AccountRight.SDK.Services.GeneralLedger;
@@ -21,10 +22,35 @@ namespace PRS.Shared.Posters.MYOB;
 
 public class InvoiceMYOBPosterSettings : MYOBPosterSettings
 {
+    public override string DefaultScript(Type TPostable)
+    {
+        return @"using MYOBInvoice = MYOB.AccountRight.SDK.Contracts.Version2.Sale.ServiceInvoice;
+using MYOBInvoiceLine = MYOB.AccountRight.SDK.Contracts.Version2.Sale.ServiceInvoiceLine;
+
+public class Module
+{
+    public void BeforePost(IDataModel<Invoice> model)
+    {
+        // Perform pre-processing
+    }
+
+    public void ProcessInvoice(IDataModel<Invoice> model, Invoice invoice, MYOBInvoice myobInvoice)
+    {
+        // Do extra processing for an invoice; throw an exception to fail this invoice.
+    }
+
+    public void ProcessInvoiceLine(IDataModel<Invoice> model, InvoiceLine invoiceLine, MYOBInvoiceLine myobInvoiceLine)
+    {
+        // Do extra processing for an invoice line; throw an exception to fail this invoice line (and thus fail the entire invoice)
+    }
+}";
+    }
 }
 
 public class InvoiceMYOBPoster : IMYOBPoster<Invoice, InvoiceMYOBPosterSettings>
 {
+    public ScriptDocument? Script { get; set; }
+
     public MYOBConnectionData ConnectionData { get; set; }
     public InvoiceMYOBPosterSettings Settings { get; set; }
     public MYOBGlobalPosterSettings GlobalSettings { get; set; }
@@ -44,9 +70,24 @@ public class InvoiceMYOBPoster : IMYOBPoster<Invoice, InvoiceMYOBPosterSettings>
         model.SetIsDefault<Customer>(true, alias: "Invoice_Customer");
         model.SetColumns<Customer>(RequiredCustomerColumns(), alias: "Invoice_Customer");
 
+        Script?.Execute(methodname: "BeforePost", parameters: new object[] { model });
+
         return true;
     }
 
+    #region Script Functions
+
+    private Result<Exception> ProcessInvoice(IDataModel<Invoice> model, Invoice invoice, MYOBInvoice myobInvoice)
+    {
+        return this.WrapScript("ProcessInvoice", model, invoice, myobInvoice);
+    }
+    private Result<Exception> ProcessInvoiceLine(IDataModel<Invoice> model, InvoiceLine invoiceLine, MYOBInvoiceLine myobInvoiceLine)
+    {
+        return this.WrapScript("ProcessInvoiceLine", model, invoiceLine, myobInvoiceLine);
+    }
+
+    #endregion
+
     private static Columns<Invoice> RequiredInvoiceColumns()
     {
         return Columns.None<Invoice>()
@@ -124,7 +165,7 @@ public class InvoiceMYOBPoster : IMYOBPoster<Invoice, InvoiceMYOBPosterSettings>
                 }
                 myobInvoice.Customer ??= new();
                 myobInvoice.Customer.UID = customerID;
-                results.AddFragment(customer);
+                results.AddFragment(customer, customer.PostedStatus);
             }
             // myobInvoice.PromisedDate = 
             if(invoiceLines.TryGetValue(invoice.ID, out var lines))
@@ -193,6 +234,12 @@ public class InvoiceMYOBPoster : IMYOBPoster<Invoice, InvoiceMYOBPosterSettings>
                     line.TaxCode ??= new();
                     line.TaxCode.UID = taxCodeID;
 
+                    if(!ProcessInvoiceLine(model, item, line).Get(out error))
+                    {
+                        failed = error.Message;
+                        break;
+                    }
+
                     newLines[i] = line;
                 }
                 if(failed is not null)
@@ -224,6 +271,12 @@ public class InvoiceMYOBPoster : IMYOBPoster<Invoice, InvoiceMYOBPosterSettings>
             // Order
             // OnlinePaymentMethod
 
+            if(!ProcessInvoice(model, invoice, myobInvoice).Get(out error))
+            {
+                results.AddFailed(invoice, error.Message);
+                continue;
+            }
+
             if(service.Save(ConnectionData, myobInvoice).Get(out var result, out error))
             {
                 invoice.PostedReference = result.UID.ToString();

+ 56 - 2
prs.shared/Posters/MYOB/ReceiptMYOBPoster.cs

@@ -2,6 +2,7 @@
 using InABox.Core;
 using InABox.Core.Postable;
 using InABox.Poster.MYOB;
+using InABox.Scripting;
 using MYOB.AccountRight.SDK.Contracts.Version2.Sale;
 using MYOB.AccountRight.SDK.Services.Sale;
 using System;
@@ -12,6 +13,7 @@ using System.Threading.Tasks;
 using System.Windows.Markup.Localizer;
 using Invoice = Comal.Classes.Invoice;
 using MYOBReceipt = MYOB.AccountRight.SDK.Contracts.Version2.Sale.CustomerPayment;
+using MYOBInvoiceReceipt = MYOB.AccountRight.SDK.Contracts.Version2.Sale.CustomerPaymentLine;
 
 namespace PRS.Shared.Posters.MYOB;
 
@@ -20,10 +22,35 @@ public class ReceiptMYOBPosterSettings : MYOBPosterSettings
     [EditorSequence(1)]
     [TextBoxEditor(ToolTip = "The MYOB account code for receipts")]
     public string Account { get; set; }
+
+    public override string DefaultScript(Type TPostable)
+    {
+        return @"using MYOBReceipt = MYOB.AccountRight.SDK.Contracts.Version2.Sale.CustomerPayment;
+using MYOBInvoiceReceipt = MYOB.AccountRight.SDK.Contracts.Version2.Sale.CustomerPaymentLine;
+
+public class Module
+{
+    public void BeforePost(IDataModel<Receipt> model)
+    {
+        // Perform pre-processing
+    }
+
+    public void ProcessReceipt(IDataModel<Receipt> model, Receipt receipt, MYOBReceipt myobReceipt)
+    {
+        // Do extra processing for a receipt; throw an exception to fail this receipt.
+    }
+
+    public void ProcessInvoiceReceipt(IDataModel<Receipt> model, InvoiceReceipt invoiceReceipt, MYOBInvoiceReceipt myobInvoiceReceipt)
+    {
+        // Do extra processing for an invoice receipt; throw an exception to fail this invoice receipt (and thus fail the entire receipt)
+    }
+}";
+    }
 }
 
 public class ReceiptMYOBPoster : IMYOBPoster<Receipt, ReceiptMYOBPosterSettings>
 {
+    public ScriptDocument? Script { get; set; }
     public MYOBConnectionData ConnectionData { get; set; }
     public ReceiptMYOBPosterSettings Settings { get; set; }
     public MYOBGlobalPosterSettings GlobalSettings { get; set; }
@@ -43,9 +70,24 @@ public class ReceiptMYOBPoster : IMYOBPoster<Receipt, ReceiptMYOBPosterSettings>
         model.SetIsDefault<Customer>(true, alias: "Receipt_Customer");
         model.SetColumns<Customer>(RequiredCustomerColumns(), alias: "Receipt_Customer");
 
+        Script?.Execute(methodname: "BeforePost", parameters: [model]);
+
         return true;
     }
 
+    #region Script Functions
+
+    private Result<Exception> ProcessReceipt(IDataModel<Receipt> model, Receipt receipt, MYOBReceipt myobReceipt)
+    {
+        return this.WrapScript("ProcessReceipt", model, receipt, myobReceipt);
+    }
+    private Result<Exception> ProcessInvoiceReceipt(IDataModel<Receipt> model, InvoiceReceipt invoiceReceipt, MYOBInvoiceReceipt myobInvoiceReceipt)
+    {
+        return this.WrapScript("ProcessInvoiceReceipt", model, invoiceReceipt, myobInvoiceReceipt);
+    }
+
+    #endregion
+
     private static Columns<Receipt> RequiredReceiptColumns()
     {
         return Columns.None<Receipt>()
@@ -129,7 +171,7 @@ public class ReceiptMYOBPoster : IMYOBPoster<Receipt, ReceiptMYOBPosterSettings>
                 }
                 myobReceipt.Customer ??= new();
                 myobReceipt.Customer.UID = customerID;
-                results.AddFragment(customer);
+                results.AddFragment(customer, customer.PostedStatus);
             }
 
             // myobReceipt.ReceiptNumber = 
@@ -163,6 +205,12 @@ public class ReceiptMYOBPoster : IMYOBPoster<Receipt, ReceiptMYOBPosterSettings>
                         break;
                     }
 
+                    if(!ProcessInvoiceReceipt(model, invoice, line).Get(out var error))
+                    {
+                        failed = error.Message;
+                        break;
+                    }
+
                     myobInvoices[i] = line;
                 }
 
@@ -178,7 +226,13 @@ public class ReceiptMYOBPoster : IMYOBPoster<Receipt, ReceiptMYOBPosterSettings>
                 myobReceipt.Invoices = [];
             }
 
-            if(service.Save(ConnectionData, myobReceipt).Get(out var result, out var e))
+            if(!ProcessReceipt(model, receipt, myobReceipt).Get(out var e))
+            {
+                results.AddFailed(receipt, e.Message);
+                continue;
+            }
+
+            if(service.Save(ConnectionData, myobReceipt).Get(out var result, out e))
             {
                 receipt.PostedReference = result.UID.ToString();
                 results.AddSuccess(receipt);

+ 42 - 1
prs.shared/Posters/MYOB/SupplierMYOBPoster.cs

@@ -4,6 +4,7 @@ using InABox.Core;
 using InABox.Core.Postable;
 using InABox.Database;
 using InABox.Poster.MYOB;
+using InABox.Scripting;
 using MYOB.AccountRight.SDK.Services.Contact;
 using System;
 using System.Collections.Generic;
@@ -16,6 +17,23 @@ namespace PRS.Shared.Posters.MYOB;
 
 public class SupplierMYOBPosterSettings : MYOBPosterSettings
 {
+    public override string DefaultScript(Type TPostable)
+    {
+        return @"using MYOBSupplier = MYOB.AccountRight.SDK.Contracts.Version2.Contact.Supplier;
+
+public class Module
+{
+    public void BeforePost(IDataModel<Supplier> model)
+    {
+        // Perform pre-processing
+    }
+
+    public void ProcessSupplier(IDataModel<Supplier> model, Supplier supplier, MYOBSupplier myobSupplier)
+    {
+        // Do extra processing for a supplier; throw an exception to fail this supplier.
+    }
+}";
+    }
 }
 
 public class SupplierMYOBAutoRefresher : IAutoRefresher<Supplier>
@@ -60,6 +78,7 @@ public class SupplierMYOBAutoRefresher : IAutoRefresher<Supplier>
 }
 public class SupplierMYOBPoster : IMYOBPoster<Supplier, SupplierMYOBPosterSettings>, IAutoRefreshPoster<Supplier, SupplierMYOBAutoRefresher>
 {
+    public ScriptDocument? Script { get; set; }
     public MYOBConnectionData ConnectionData { get; set; }
     public SupplierMYOBPosterSettings Settings { get; set; }
     public MYOBGlobalPosterSettings GlobalSettings { get; set; }
@@ -73,13 +92,26 @@ public class SupplierMYOBPoster : IMYOBPoster<Supplier, SupplierMYOBPosterSettin
         model.SetIsDefault<Supplier>(true);
         model.SetColumns<Supplier>(RequiredColumns());
 
+        Script?.Execute(methodname: "BeforePost", parameters: [model]);
+
         return true;
     }
+
+    #region Script Functions
+
+    private Result<Exception> ProcessSupplier(IDataModel<Supplier> model, Supplier supplier, MYOBSupplier myobSupplier)
+    {
+        return this.WrapScript("ProcessSupplier", model, supplier, myobSupplier);
+    }
+
+    #endregion
+
     public static Columns<Supplier> RequiredColumns()
     {
         return Columns.None<Supplier>()
             .Add(x => x.ID)
             .Add(x => x.PostedReference)
+            .Add(x => x.PostedStatus)
             .Add(x => x.Name)
             .Add(x => x.Code)
             .Add(x => x.SupplierStatus.ID)
@@ -195,11 +227,19 @@ public class SupplierMYOBPoster : IMYOBPoster<Supplier, SupplierMYOBPosterSettin
                 }
                 var myobSupplier = new MYOBSupplier();
                 return UpdateSupplier(data, settings, supplier, myobSupplier, true)
-                    .MapOk(() => service.Save(data, myobSupplier).MapOk(x => x.UID)).Flatten();
+                    .MapOk(() => service.Save(data, myobSupplier)
+                        .MapOk(x =>
+                        {
+                            supplier.PostedReference = x.UID.ToString();
+                            // Marking as repost because a script may not have run.
+                            supplier.PostedStatus = PostedStatus.RequiresRepost;
+                            return x.UID;
+                        })).Flatten();
             }
             else
             {
                 supplier.PostedReference = suppliers.Items[0].UID.ToString();
+                supplier.PostedStatus = PostedStatus.RequiresRepost;
                 return Result.Ok(suppliers.Items[0].UID);
             }
         }).Flatten();
@@ -262,6 +302,7 @@ public class SupplierMYOBPoster : IMYOBPoster<Supplier, SupplierMYOBPosterSettin
             }
 
             if(UpdateSupplier(ConnectionData, GlobalSettings, supplier, myobSupplier, isNew)
+                .MapOk(() => ProcessSupplier(model, supplier, myobSupplier)).Flatten()
                 .MapOk(() => service.Save(ConnectionData, myobSupplier)).Flatten()
                 .Get(out var result, out error))
             {

+ 401 - 402
prs.shared/Posters/Timberline/BillTimberlinePoster.cs

@@ -19,224 +19,224 @@ using System.Windows;
 using static System.Windows.Forms.VisualStyles.VisualStyleElement;
 using Columns = InABox.Core.Columns;
 
-namespace PRS.Shared
+namespace PRS.Shared;
+
+public class BillTimberlineHeader
 {
-    public class BillTimberlineHeader
-    {
-        [Ignore]
-        public List<BillTimberlineDistribution> Distributions { get; set; } = new();
+    [Ignore]
+    public List<BillTimberlineDistribution> Distributions { get; set; } = new();
 
-        [Index(0)]
-        public string RecordID { get; set; } = "APIF";
+    [Index(0)]
+    public string RecordID { get; set; } = "APIF";
 
-        [Index(1)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string Vendor { get; set; } = "";
+    [Index(1)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string Vendor { get; set; } = "";
 
-        [Index(2)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 15)]
-        public string Invoice { get; set; } = "";
+    [Index(2)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 15)]
+    public string Invoice { get; set; } = "";
 
-        [Index(3)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 30)]
-        public string Description { get; set; } = "";
+    [Index(3)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 30)]
+    public string Description { get; set; } = "";
 
-        [Index(4)]
-        public double Amount { get; set; }
+    [Index(4)]
+    public double Amount { get; set; }
 
-        [Index(5)]
-        public double Tax { get; set; }
+    [Index(5)]
+    public double Tax { get; set; }
 
-        [Index(6)]
-        public double DiscountOffered { get; set; }
+    [Index(6)]
+    public double DiscountOffered { get; set; }
 
-        [Index(7)]
-        public double MiscDeduction { get; set; }
+    [Index(7)]
+    public double MiscDeduction { get; set; }
 
-        [Index(8)]
-        [TypeConverter(typeof(TimberlinePosterDateConverter))]
-        public DateTime InvoiceDate { get; set; }
+    [Index(8)]
+    [TypeConverter(typeof(TimberlinePosterDateConverter))]
+    public DateTime InvoiceDate { get; set; }
 
-        [Index(9)]
-        [TypeConverter(typeof(TimberlinePosterDateConverter))]
-        public DateTime DateReceived { get; set; }
+    [Index(9)]
+    [TypeConverter(typeof(TimberlinePosterDateConverter))]
+    public DateTime DateReceived { get; set; }
 
-        [Index(10)]
-        [TypeConverter(typeof(TimberlinePosterDateConverter))]
-        public DateTime DiscountDate { get; set; }
+    [Index(10)]
+    [TypeConverter(typeof(TimberlinePosterDateConverter))]
+    public DateTime DiscountDate { get; set; }
 
-        [Index(11)]
-        [TypeConverter(typeof(TimberlinePosterDateConverter))]
-        public DateTime PaymentDate { get; set; }
+    [Index(11)]
+    [TypeConverter(typeof(TimberlinePosterDateConverter))]
+    public DateTime PaymentDate { get; set; }
 
-        [Index(12)]
-        [TypeConverter(typeof(TimberlinePosterDateConverter))]
-        public DateTime AccountingDate { get; set; }
+    [Index(12)]
+    [TypeConverter(typeof(TimberlinePosterDateConverter))]
+    public DateTime AccountingDate { get; set; }
 
-        [Index(13)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string InvoiceCode1 { get; set; } = "";
+    [Index(13)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string InvoiceCode1 { get; set; } = "";
 
-        [Index(14)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string InvoiceCode2 { get; set; } = "";
+    [Index(14)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string InvoiceCode2 { get; set; } = "";
 
-        [Index(15)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 30)]
-        public string SmryPayeeName { get; set; } = "";
+    [Index(15)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 30)]
+    public string SmryPayeeName { get; set; } = "";
 
-        [Index(16)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 33)]
-        public string SmryPayeeAddress1 { get; set; } = "";
+    [Index(16)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 33)]
+    public string SmryPayeeAddress1 { get; set; } = "";
 
-        [Index(17)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 33)]
-        public string SmryPayeeAddress2 { get; set; } = "";
+    [Index(17)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 33)]
+    public string SmryPayeeAddress2 { get; set; } = "";
 
-        [Index(18)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 15)]
-        public string SmryPayeeCity { get; set; } = "";
+    [Index(18)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 15)]
+    public string SmryPayeeCity { get; set; } = "";
 
-        [Index(19)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 4)]
-        public string SmryPayeeState { get; set; } = "";
+    [Index(19)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 4)]
+    public string SmryPayeeState { get; set; } = "";
 
-        [Index(20)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string SmryPayeeZip { get; set; } = "";
-    }
+    [Index(20)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string SmryPayeeZip { get; set; } = "";
+}
 
-    public class BillTimberlineDistribution
-    {
-        [Index(0)]
-        public string RecordID { get; set; } = "APDF";
+public class BillTimberlineDistribution
+{
+    [Index(0)]
+    public string RecordID { get; set; } = "APDF";
 
-        [Index(1)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 12)]
-        public string Commitment { get; set; } = "";
+    [Index(1)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 12)]
+    public string Commitment { get; set; } = "";
 
-        [Index(2)]
-        public int CommitmentLineItem { get; set; }
+    [Index(2)]
+    public int CommitmentLineItem { get; set; }
 
-        [Index(3)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 7)]
-        public string Equipment { get; set; } = "";
+    [Index(3)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 7)]
+    public string Equipment { get; set; } = "";
 
-        [Index(4)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 6)]
-        public string EQCostCode { get; set; } = "";
+    [Index(4)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 6)]
+    public string EQCostCode { get; set; } = "";
 
-        [Index(5)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 6)]
-        public string Job { get; set; } = "";
+    [Index(5)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 6)]
+    public string Job { get; set; } = "";
 
-        [Index(6)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string Extra { get; set; } = "";
+    [Index(6)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string Extra { get; set; } = "";
 
-        [Index(7)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 7)]
-        public string CostCode { get; set; } = "";
+    [Index(7)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 7)]
+    public string CostCode { get; set; } = "";
 
-        [Index(8)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 3)]
-        public string Category { get; set; } = "";
+    [Index(8)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 3)]
+    public string Category { get; set; } = "";
 
-        [Index(9)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string BLStdItem { get; set; } = "";
+    [Index(9)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string BLStdItem { get; set; } = "";
 
-        [Index(10)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 8)]
-        public string Reserved { get; set; } = "";
+    [Index(10)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 8)]
+    public string Reserved { get; set; } = "";
 
-        [Index(11)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), int.MaxValue)] // int.MaxValue because it was chopping the accounts in PRS
-        public string ExpenseAccount { get; set; } = "";
+    [Index(11)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), int.MaxValue)] // int.MaxValue because it was chopping the accounts in PRS
+    public string ExpenseAccount { get; set; } = "";
 
-        [Index(12)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), int.MaxValue)] // int.MaxValue because it was chopping the accounts in PRS
-        public string APAccount { get; set; } = "";
+    [Index(12)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), int.MaxValue)] // int.MaxValue because it was chopping the accounts in PRS
+    public string APAccount { get; set; } = "";
 
-        [Index(13)]
-        public double TaxablePayments { get; set; }
+    [Index(13)]
+    public double TaxablePayments { get; set; }
 
-        [Index(14)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 6)]
-        public string TaxGroup { get; set; } = "";
+    [Index(14)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 6)]
+    public string TaxGroup { get; set; } = "";
 
-        [Index(15)]
-        public double Units { get; set; }
+    [Index(15)]
+    public double Units { get; set; }
 
-        [Index(16)]
-        public double UnitCost { get; set; }
+    [Index(16)]
+    public double UnitCost { get; set; }
 
-        [Index(17)]
-        public double Amount { get; set; }
+    [Index(17)]
+    public double Amount { get; set; }
 
-        [Index(18)]
-        public double Tax { get; set; }
+    [Index(18)]
+    public double Tax { get; set; }
 
-        [Index(19)]
-        public double TaxLiability { get; set; }
+    [Index(19)]
+    public double TaxLiability { get; set; }
 
-        [Index(20)]
-        public double DiscountOffered { get; set; }
+    [Index(20)]
+    public double DiscountOffered { get; set; }
 
-        [Index(21)]
-        public double Retainage { get; set; }
+    [Index(21)]
+    public double Retainage { get; set; }
 
-        [Index(22)]
-        public double MiscDeduction { get; set; }
+    [Index(22)]
+    public double MiscDeduction { get; set; }
 
-        [Index(23)]
-        [BooleanTrueValues("t", "T", "1", "Y", "y")]
-        [BooleanFalseValues("f", "F", "0", "N", "n")]
-        public bool TaxablePaymentsExempt { get; set; }
+    [Index(23)]
+    [BooleanTrueValues("t", "T", "1", "Y", "y")]
+    [BooleanFalseValues("f", "F", "0", "N", "n")]
+    public bool TaxablePaymentsExempt { get; set; }
 
-        [Index(24)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string DistCode { get; set; } = "";
+    [Index(24)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string DistCode { get; set; } = "";
 
-        [Index(25)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string Draw { get; set; } = "";
+    [Index(25)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string Draw { get; set; } = "";
 
-        [Index(26)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string MiscEntry1 { get; set; } = "";
+    [Index(26)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string MiscEntry1 { get; set; } = "";
 
-        [Index(27)]
-        public double MiscEntryUnits1 { get; set; }
+    [Index(27)]
+    public double MiscEntryUnits1 { get; set; }
 
-        [Index(28)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string MiscEntry2 { get; set; } = "";
+    [Index(28)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string MiscEntry2 { get; set; } = "";
 
-        [Index(29)]
-        public double MiscEntryUnits2 { get; set; }
+    [Index(29)]
+    public double MiscEntryUnits2 { get; set; }
 
-        [Index(30)]
-        public double MeterOdometer { get; set; }
+    [Index(30)]
+    public double MeterOdometer { get; set; }
 
-        [Index(31)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 30)]
-        public string Description { get; set; } = "";
+    [Index(31)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 30)]
+    public string Description { get; set; } = "";
 
-        [Index(32)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
-        public string Authorization { get; set; } = "";
+    [Index(32)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 10)]
+    public string Authorization { get; set; } = "";
 
-        [Index(33)]
-        [TypeConverter(typeof(TimberlinePosterStringConverter), 30)]
-        public string JointPayee { get; set; } = "";
-    }
+    [Index(33)]
+    [TypeConverter(typeof(TimberlinePosterStringConverter), 30)]
+    public string JointPayee { get; set; } = "";
+}
 
-    public class BillTimberlineSettings : TimberlinePosterSettings<Bill>
+public class BillTimberlineSettings : TimberlinePosterSettings<Bill>
+{
+    protected override string DefaultScript()
     {
-        protected override string DefaultScript()
-        {
-            return @"
+        return @"
 using PRS.Shared;
 using InABox.Core;
 using System.Collections.Generic;
@@ -265,311 +265,310 @@ public class Module
         // Perform post-processing;
     }
 }";
+    }
+}
+
+public class BillTimberlineResult : TimberlinePostResult<BillTimberlineHeader, Bill>
+{
+}
+
+public class BillTimberlinePoster : ITimberlinePoster<Bill, BillTimberlineSettings>
+{
+    public ScriptDocument? Script { get; set; }
+    public BillTimberlineSettings Settings { get; set; }
+
+    public bool BeforePost(IDataModel<Bill> model)
+    {
+        foreach(var (name, table) in model.ModelTables)
+        {
+            table.IsDefault = false;
         }
+        model.SetIsDefault<Bill>(true);
+        model.SetIsDefault<BillLine>(true, "Bill_BillLine");
+
+        model.SetColumns(Columns.None<Bill>().Add(x => x.ID)
+            .Add(x => x.SupplierLink.Code)
+            .Add(x => x.Description)
+            .Add(x => x.Number)
+            .Add(x => x.IncTax)
+            .Add(x => x.Tax)
+            .Add(x => x.BillDate)
+            .Add(x => x.AccountingDate)
+            .Add(x => x.PaymentDate)
+            .Add(x => x.Approved));
+
+        model.SetColumns(Columns.None<BillLine>().Add(x => x.ID)
+            .Add(x => x.BillLink.ID)
+            .Add(x => x.TaxCode.Code)
+            .Add(x => x.IncTax)
+            .Add(x => x.Tax)
+            .Add(x => x.Description)
+            .Add(x => x.PurchaseGL.Code)
+            .Add(x => x.OrderItem.ID)
+            .Add(x => x.Job.JobNumber),
+            alias: "Bill_BillLine");
+
+        model.AddChildTable<BillLine, PurchaseOrderItem>(x => x.OrderItem.ID, x => x.ID, isdefault: true,
+            parentalias: "Bill_BillLine", childalias: "POItem",
+            columns: Columns.None<PurchaseOrderItem>().Add(x => x.ID)
+                .Add(x => x.PurchaseOrderLink.PONumber)
+                .Add(x => x.Job.JobNumber)
+                .Add(x => x.Qty)
+                .Add(x => x.Description)
+                .Add(x => x.Cost)
+                .Add(x => x.PostedReference)
+                );
+
+        Script?.Execute(methodname: "BeforePost", parameters: new object[] { model });
+        return true;
     }
 
-    public class BillTimberlineResult : TimberlinePostResult<BillTimberlineHeader, Bill>
+    private bool ProcessHeader(IDataModel<Bill> model, Bill bill, BillTimberlineHeader header)
+    {
+        return Script?.Execute(methodname: "ProcessHeader", parameters: new object[] { model, bill, header }) != false;
+    }
+    private bool ProcessLine(IDataModel<Bill> model, BillLine bill, BillTimberlineDistribution distribution)
     {
+        return Script?.Execute(methodname: "ProcessLine", parameters: new object[] { model, bill, distribution }) != false;
     }
 
-    public class BillTimberlinePoster : ITimberlinePoster<Bill, BillTimberlineSettings>
+    private BillTimberlineResult DoProcess(IDataModel<Bill> model)
     {
-        public ScriptDocument? Script { get; set; }
-        public BillTimberlineSettings Settings { get; set; }
+        var result = new BillTimberlineResult();
 
-        public bool BeforePost(IDataModel<Bill> model)
-        {
-            foreach(var (name, table) in model.ModelTables)
-            {
-                table.IsDefault = false;
-            }
-            model.SetIsDefault<Bill>(true);
-            model.SetIsDefault<BillLine>(true, "Bill_BillLine");
+        var lines = model.GetTable<BillLine>("Bill_BillLine").ToObjects<BillLine>()
+            .GroupBy(x => x.BillLink.ID).ToDictionary(x => x.Key, x => x.ToList());
+        var purchaseOrderItems = model.GetTable<PurchaseOrderItem>("POItem").ToObjects<PurchaseOrderItem>()
+            .ToDictionary(x => x.ID, x => x);
 
-            model.SetColumns(Columns.None<Bill>().Add(x => x.ID)
-                .Add(x => x.SupplierLink.Code)
-                .Add(x => x.Description)
-                .Add(x => x.Number)
-                .Add(x => x.IncTax)
-                .Add(x => x.Tax)
-                .Add(x => x.BillDate)
-                .Add(x => x.AccountingDate)
-                .Add(x => x.PaymentDate)
-                .Add(x => x.Approved));
-
-            model.SetColumns(Columns.None<BillLine>().Add(x => x.ID)
-                .Add(x => x.BillLink.ID)
-                .Add(x => x.TaxCode.Code)
-                .Add(x => x.IncTax)
-                .Add(x => x.Tax)
-                .Add(x => x.Description)
-                .Add(x => x.PurchaseGL.Code)
-                .Add(x => x.OrderItem.ID)
-                .Add(x => x.Job.JobNumber),
-                alias: "Bill_BillLine");
-
-            model.AddChildTable<BillLine, PurchaseOrderItem>(x => x.OrderItem.ID, x => x.ID, isdefault: true,
-                parentalias: "Bill_BillLine", childalias: "POItem",
-                columns: Columns.None<PurchaseOrderItem>().Add(x => x.ID)
-                    .Add(x => x.PurchaseOrderLink.PONumber)
-                    .Add(x => x.Job.JobNumber)
-                    .Add(x => x.Qty)
-                    .Add(x => x.Description)
-                    .Add(x => x.Cost)
-                    .Add(x => x.PostedReference)
-                    );
-
-            Script?.Execute(methodname: "BeforePost", parameters: new object[] { model });
-            return true;
-        }
-
-        private bool ProcessHeader(IDataModel<Bill> model, Bill bill, BillTimberlineHeader header)
+        var bills = model.GetTable<Bill>().ToObjects<Bill>();
+        if(bills.Any(x => x.Approved.IsEmpty()))
         {
-            return Script?.Execute(methodname: "ProcessHeader", parameters: new object[] { model, bill, header }) != false;
-        }
-        private bool ProcessLine(IDataModel<Bill> model, BillLine bill, BillTimberlineDistribution distribution)
-        {
-            return Script?.Execute(methodname: "ProcessLine", parameters: new object[] { model, bill, distribution }) != false;
+            throw new PostFailedMessageException("We can't process unapproved bills; please approve all bills before processing.");
         }
 
-        private BillTimberlineResult DoProcess(IDataModel<Bill> model)
+        foreach (var bill in bills)
         {
-            var result = new BillTimberlineResult();
-
-            var lines = model.GetTable<BillLine>("Bill_BillLine").ToObjects<BillLine>()
-                .GroupBy(x => x.BillLink.ID).ToDictionary(x => x.Key, x => x.ToList());
-            var purchaseOrderItems = model.GetTable<PurchaseOrderItem>("POItem").ToObjects<PurchaseOrderItem>()
-                .ToDictionary(x => x.ID, x => x);
-
-            var bills = model.GetTable<Bill>().ToObjects<Bill>();
-            if(bills.Any(x => x.Approved.IsEmpty()))
+            var apif = new BillTimberlineHeader
             {
-                throw new PostFailedMessageException("We can't process unapproved bills; please approve all bills before processing.");
+                Vendor = bill.SupplierLink.Code,
+                Invoice = bill.Number,
+                Description = bill.Description,
+                Amount = bill.IncTax,
+                Tax = bill.Tax,
+                // DiscountOffered
+                // Misc. Deduction
+                InvoiceDate = bill.BillDate,
+                // DateReceived doesn't exist
+                DiscountDate = bill.BillDate,
+                PaymentDate = bill.PaymentDate,
+                AccountingDate = bill.AccountingDate,
+                // InvoiceCode1
+                // InvoiceCode2
+                // SmryPayeeName
+                // SmryPayeeAddress1
+                // SmryPayeeAddress2
+                // SmryPayeeCity
+                // SmryPayeeState
+                // SmryPayeeZip
+            };
+            if (!ProcessHeader(model, bill, apif))
+            {
+                result.AddFailed(bill, "Failed by script.");
             }
-
-            foreach (var bill in bills)
+            else
             {
-                var apif = new BillTimberlineHeader
-                {
-                    Vendor = bill.SupplierLink.Code,
-                    Invoice = bill.Number,
-                    Description = bill.Description,
-                    Amount = bill.IncTax,
-                    Tax = bill.Tax,
-                    // DiscountOffered
-                    // Misc. Deduction
-                    InvoiceDate = bill.BillDate,
-                    // DateReceived doesn't exist
-                    DiscountDate = bill.BillDate,
-                    PaymentDate = bill.PaymentDate,
-                    AccountingDate = bill.AccountingDate,
-                    // InvoiceCode1
-                    // InvoiceCode2
-                    // SmryPayeeName
-                    // SmryPayeeAddress1
-                    // SmryPayeeAddress2
-                    // SmryPayeeCity
-                    // SmryPayeeState
-                    // SmryPayeeZip
-                };
-                if (!ProcessHeader(model, bill, apif))
-                {
-                    result.AddFailed(bill, "Failed by script.");
-                }
-                else
+                var success = true;
+                var billLines = lines.GetValueOrDefault(bill.ID) ?? new List<BillLine>();
+                foreach (var billLine in billLines)
                 {
-                    var success = true;
-                    var billLines = lines.GetValueOrDefault(bill.ID) ?? new List<BillLine>();
-                    foreach (var billLine in billLines)
+                    var apdf = new BillTimberlineDistribution
                     {
-                        var apdf = new BillTimberlineDistribution
-                        {
-                            // Equipment
-                            // EQ Cost Code
-                            // Extra
-                            // Cost Code
-                            // Category
-                            /// BL STd Item
-                            // Reserved
-                            ExpenseAccount = billLine.PurchaseGL.Code,
-                            // AP Account
-                            // Taxable Payments
-                            TaxGroup = billLine.TaxCode.Code,
-                            Amount = billLine.IncTax,
-                            Tax = billLine.Tax,
-                            // Tax Liability
-                            // Discount OFfered
-                            // Retainage
-                            // MIsc Deduction
-                            // Tax Payments Exempt
-                            // Dist Code
-                            // Misc Entry 1
-                            // Misc Units 1
-                            // Misc Entry 2
-                            // Misc Units 2
-                            // Meter
-                            Description = billLine.Description,
-                            // Authorization
-                            // Joint Payee
-                        };
-                        if (purchaseOrderItems.TryGetValue(billLine.OrderItem.ID, out var poItem))
-                        {
-                            apdf.Commitment = poItem.PurchaseOrderLink.PONumber;
-                            apdf.Job = poItem.Job.JobNumber;
-                            if (int.TryParse(poItem.PostedReference, out var itemNumber))
-                            {
-                                apdf.CommitmentLineItem = itemNumber;
-                                billLine.PostedReference = poItem.PostedReference;
-                            }
-                            apdf.Units = poItem.Qty;
-                            apdf.UnitCost = poItem.Cost;
-                            apdf.Description = poItem.Description.NotWhiteSpaceOr(apdf.Description);
-                        }
-                        else
-                        {
-                            apdf.Job = billLine.Job.JobNumber;
-                        }
-
-                        if (!ProcessLine(model, billLine, apdf))
-                        {
-                            success = false;
-                            break;
-                        }
-                        apif.Distributions.Add(apdf);
-                    }
-                    if (success)
+                        // Equipment
+                        // EQ Cost Code
+                        // Extra
+                        // Cost Code
+                        // Category
+                        /// BL STd Item
+                        // Reserved
+                        ExpenseAccount = billLine.PurchaseGL.Code,
+                        // AP Account
+                        // Taxable Payments
+                        TaxGroup = billLine.TaxCode.Code,
+                        Amount = billLine.IncTax,
+                        Tax = billLine.Tax,
+                        // Tax Liability
+                        // Discount OFfered
+                        // Retainage
+                        // MIsc Deduction
+                        // Tax Payments Exempt
+                        // Dist Code
+                        // Misc Entry 1
+                        // Misc Units 1
+                        // Misc Entry 2
+                        // Misc Units 2
+                        // Meter
+                        Description = billLine.Description,
+                        // Authorization
+                        // Joint Payee
+                    };
+                    if (purchaseOrderItems.TryGetValue(billLine.OrderItem.ID, out var poItem))
                     {
-                        foreach(var billLine in billLines)
+                        apdf.Commitment = poItem.PurchaseOrderLink.PONumber;
+                        apdf.Job = poItem.Job.JobNumber;
+                        if (int.TryParse(poItem.PostedReference, out var itemNumber))
                         {
-                            result.AddFragment(billLine);
+                            apdf.CommitmentLineItem = itemNumber;
+                            billLine.PostedReference = poItem.PostedReference;
                         }
-                        result.AddSuccess(bill, apif);
+                        apdf.Units = poItem.Qty;
+                        apdf.UnitCost = poItem.Cost;
+                        apdf.Description = poItem.Description.NotWhiteSpaceOr(apdf.Description);
                     }
                     else
                     {
-                        result.AddFailed(bill, "Failed by script.");
+                        apdf.Job = billLine.Job.JobNumber;
+                    }
+
+                    if (!ProcessLine(model, billLine, apdf))
+                    {
+                        success = false;
+                        break;
+                    }
+                    apif.Distributions.Add(apdf);
+                }
+                if (success)
+                {
+                    foreach(var billLine in billLines)
+                    {
+                        result.AddFragment(billLine);
                     }
+                    result.AddSuccess(bill, apif);
+                }
+                else
+                {
+                    result.AddFailed(bill, "Failed by script.");
                 }
             }
-            return result;
         }
+        return result;
+    }
 
-        public IPostResult<Bill> Process(IDataModel<Bill> model)
-        {
-            var result = DoProcess(model);
+    public IPostResult<Bill> Process(IDataModel<Bill> model)
+    {
+        var result = DoProcess(model);
 
-            var dlg = new SaveFileDialog()
-            {
-                Filter = "CSV Files (*.csv)|*.csv"
-            };
+        var dlg = new SaveFileDialog()
+        {
+            Filter = "CSV Files (*.csv)|*.csv"
+        };
 
-            if (dlg.ShowDialog() == true)
+        if (dlg.ShowDialog() == true)
+        {
+            using (var writer = new StreamWriter(dlg.FileName))
             {
-                using (var writer = new StreamWriter(dlg.FileName))
+                using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
+                foreach (var apif in result.Exports)
                 {
-                    using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
-                    foreach (var apif in result.Exports)
+                    csv.WriteRecord(apif);
+                    csv.NextRecord();
+                    foreach (var apdf in apif.Distributions)
                     {
-                        csv.WriteRecord(apif);
+                        csv.WriteRecord(apdf);
                         csv.NextRecord();
-                        foreach (var apdf in apif.Distributions)
-                        {
-                            csv.WriteRecord(apdf);
-                            csv.NextRecord();
-                        }
                     }
                 }
+            }
 
-                while (true)
+            while (true)
+            {
+                var logDlg = new OpenFileDialog
                 {
-                    var logDlg = new OpenFileDialog
-                    {
-                        InitialDirectory = Path.GetDirectoryName(dlg.FileName),
-                        FileName = "APREJECT.txt",
-                        Filter = "All Files (*.*) | *.*",
-                        Title = "Please select APREJECT.txt"
-                    };
-                    if (logDlg.ShowDialog() == true)
+                    InitialDirectory = Path.GetDirectoryName(dlg.FileName),
+                    FileName = "APREJECT.txt",
+                    Filter = "All Files (*.*) | *.*",
+                    Title = "Please select APREJECT.txt"
+                };
+                if (logDlg.ShowDialog() == true)
+                {
+                    var rejectedHeaders = new List<BillTimberlineHeader?>();
+                    using (var reader = new StreamReader(logDlg.FileName))
                     {
-                        var rejectedHeaders = new List<BillTimberlineHeader?>();
-                        using (var reader = new StreamReader(logDlg.FileName))
+                        using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture)
                         {
-                            using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture)
-                            {
-                                HasHeaderRecord = false
-                            });
+                            HasHeaderRecord = false
+                        });
 
-                            var i = 1;
-                            while (csv.Read())
+                        var i = 1;
+                        while (csv.Read())
+                        {
+                            var id = csv.GetField(0);
+                            if (id == "APIF")
                             {
-                                var id = csv.GetField(0);
-                                if (id == "APIF")
+                                var header = csv.GetRecord<BillTimberlineHeader>();
+                                if (header is not null)
                                 {
-                                    var header = csv.GetRecord<BillTimberlineHeader>();
-                                    if (header is not null)
-                                    {
-                                        var entry = result.Items.FirstOrDefault(x => x.Item2?.Invoice.Equals(header.Invoice) == true);
-                                        if (entry is not null)
-                                        {
-                                            (entry.Item1 as IPostable).FailPost("");
-                                        }
-                                    }
-                                    else
+                                    var entry = result.Items.FirstOrDefault(x => x.Item2?.Invoice.Equals(header.Invoice) == true);
+                                    if (entry is not null)
                                     {
-                                        Logger.Send(LogType.Error, "", "Bill Timberline export: Unable to parse header from CSV line in rejection file.");
-                                        MessageBox.Show($"Invalid line {i} in file; skipping.");
+                                        (entry.Item1 as IPostable).FailPost("");
                                     }
                                 }
-                                else if (id == "APDF")
+                                else
                                 {
-                                    // Ignoring these because the reject file contains the header as well, and we don't need to fail BillLines, because
-                                    // they aren't postable.
+                                    Logger.Send(LogType.Error, "", "Bill Timberline export: Unable to parse header from CSV line in rejection file.");
+                                    MessageBox.Show($"Invalid line {i} in file; skipping.");
+                                }
+                            }
+                            else if (id == "APDF")
+                            {
+                                // Ignoring these because the reject file contains the header as well, and we don't need to fail BillLines, because
+                                // they aren't postable.
 
-                                    /*var line = csv.GetRecord<BillTimberlineDistribution>();
-                                    if (line is not null)
+                                /*var line = csv.GetRecord<BillTimberlineDistribution>();
+                                if (line is not null)
+                                {
+                                    var entry = result.Items.FirstOrDefault(x => x.Item2?.Invoice.Equals(line.Invoice) == true);
+                                    if (entry is not null)
                                     {
-                                        var entry = result.Items.FirstOrDefault(x => x.Item2?.Invoice.Equals(line.Invoice) == true);
-                                        if (entry is not null)
-                                        {
-                                            (entry.Item1 as IPostable).FailPost("");
-                                        }
+                                        (entry.Item1 as IPostable).FailPost("");
                                     }
-                                    else
-                                    {
-                                        Logger.Send(LogType.Error, "", "Bill Timberline export: Unable to parse line from CSV line in rejection file.");
-                                        MessageBox.Show("Invalid line in file; skipping.");
-                                    }*/
                                 }
-                                ++i;
+                                else
+                                {
+                                    Logger.Send(LogType.Error, "", "Bill Timberline export: Unable to parse line from CSV line in rejection file.");
+                                    MessageBox.Show("Invalid line in file; skipping.");
+                                }*/
                             }
+                            ++i;
                         }
-                        return result;
                     }
-                    else
+                    return result;
+                }
+                else
+                {
+                    if (MessageBox.Show("Do you wish to cancel the export?", "Cancel Export?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
                     {
-                        if (MessageBox.Show("Do you wish to cancel the export?", "Cancel Export?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
-                        {
-                            throw new PostCancelledException();
-                        }
-                        else if (MessageBox.Show("Did everything post successfully?", "Successful?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
-                        {
-                            return result;
-                        }
+                        throw new PostCancelledException();
+                    }
+                    else if (MessageBox.Show("Did everything post successfully?", "Successful?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+                    {
+                        return result;
                     }
                 }
             }
-            else
-            {
-                throw new PostCancelledException();
-            }
         }
-        public void AfterPost(IDataModel<Bill> model, IPostResult<Bill> result)
+        else
         {
-            Script?.Execute(methodname: "AfterPost", parameters: new object[] { model });
+            throw new PostCancelledException();
         }
     }
-
-    public class BillTimberlinePosterEngine<T> : TimberlinePosterEngine<Bill, BillTimberlineSettings>
+    public void AfterPost(IDataModel<Bill> model, IPostResult<Bill> result)
     {
+        Script?.Execute(methodname: "AfterPost", parameters: new object[] { model });
     }
 }
+
+public class BillTimberlinePosterEngine<T> : TimberlinePosterEngine<Bill, BillTimberlineSettings>
+{
+}