From 7d68f1126cb110140f5ab121ff98dfe5b3165566 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Thu, 7 Jul 2022 23:50:30 +0000 Subject: [PATCH] --- AyaNovaQBI/AyaNovaQBI.csproj | 8 + AyaNovaQBI/MainForm.cs | 268 +++++++++++++-- AyaNovaQBI/WorkOrder.cs | 131 ++++++++ AyaNovaQBI/WorkOrderItem.cs | 60 ++++ AyaNovaQBI/WorkOrderItemExpense.cs | 41 +++ AyaNovaQBI/WorkOrderItemLabor.cs | 58 ++++ AyaNovaQBI/WorkOrderItemLoan.cs | 69 ++++ AyaNovaQBI/WorkOrderItemOutsideService.cs | 63 ++++ AyaNovaQBI/WorkOrderItemPart.cs | 65 ++++ AyaNovaQBI/WorkOrderItemTravel.cs | 61 ++++ AyaNovaQBI/util.cs | 308 ++++++++++++++++++ .../main-menu-invoice-fix-problems-item.png | Bin 0 -> 4160 bytes ...ain-menu-invoice-refresh-invoices-item.png | Bin 0 -> 3941 bytes ...in-menu-invoice-selected-multiple-item.png | Bin 0 -> 6000 bytes .../main-menu-invoice-selected-one-item.png | Bin 0 -> 6610 bytes .../main-menu-tools-invoice-template-item.png | Bin 0 -> 4303 bytes docs/docs/img/main-menu-tools-item.png | Bin 0 -> 4703 bytes .../main-menu-tools-link-and-sync-item.png | Bin 0 -> 4227 bytes .../img/main-menu-tools-preferences-item.png | Bin 0 -> 3937 bytes ...in-menu-tools-refresh-cached-data-item.png | Bin 0 -> 3983 bytes 20 files changed, 1107 insertions(+), 25 deletions(-) create mode 100644 AyaNovaQBI/WorkOrder.cs create mode 100644 AyaNovaQBI/WorkOrderItem.cs create mode 100644 AyaNovaQBI/WorkOrderItemExpense.cs create mode 100644 AyaNovaQBI/WorkOrderItemLabor.cs create mode 100644 AyaNovaQBI/WorkOrderItemLoan.cs create mode 100644 AyaNovaQBI/WorkOrderItemOutsideService.cs create mode 100644 AyaNovaQBI/WorkOrderItemPart.cs create mode 100644 AyaNovaQBI/WorkOrderItemTravel.cs create mode 100644 docs/docs/img/main-menu-invoice-fix-problems-item.png create mode 100644 docs/docs/img/main-menu-invoice-refresh-invoices-item.png create mode 100644 docs/docs/img/main-menu-invoice-selected-multiple-item.png create mode 100644 docs/docs/img/main-menu-invoice-selected-one-item.png create mode 100644 docs/docs/img/main-menu-tools-invoice-template-item.png create mode 100644 docs/docs/img/main-menu-tools-item.png create mode 100644 docs/docs/img/main-menu-tools-link-and-sync-item.png create mode 100644 docs/docs/img/main-menu-tools-preferences-item.png create mode 100644 docs/docs/img/main-menu-tools-refresh-cached-data-item.png diff --git a/AyaNovaQBI/AyaNovaQBI.csproj b/AyaNovaQBI/AyaNovaQBI.csproj index 50c1266..a4920ee 100644 --- a/AyaNovaQBI/AyaNovaQBI.csproj +++ b/AyaNovaQBI/AyaNovaQBI.csproj @@ -223,6 +223,14 @@ Waiting.cs + + + + + + + + Form diff --git a/AyaNovaQBI/MainForm.cs b/AyaNovaQBI/MainForm.cs index dde83fa..39c83bb 100644 --- a/AyaNovaQBI/MainForm.cs +++ b/AyaNovaQBI/MainForm.cs @@ -16,7 +16,7 @@ namespace AyaNovaQBI public MainForm() { InitializeComponent(); - this.Icon = AyaNovaQBI.Properties.Resources.logo; + Icon = AyaNovaQBI.Properties.Resources.logo; } async private void MainForm_Load(object sender, EventArgs e) @@ -32,28 +32,37 @@ namespace AyaNovaQBI await Task.Run(() => MessageBox.Show($"AyaNova QBI was unable to start:\r\n{initErrors.ToString()}")); } Close(); + return; } - else - { - //Confirm main settings and set any that are missing: - if (await util.ValidateSettings(false) == util.pfstat.Cancel) - { - await util.IntegrationLog("PFC: User settings not completed, user selected cancel"); - Close(); - } - //check if setup is required - //if (util.QBIntegration.Items.Count == 0) - //{ - // MessageBox.Show("STUB: mainform,no maps, no integration data set"); - //} + //Confirm main settings and set any that are missing: + if (await util.ValidateSettings(false) == util.pfstat.Cancel) + { + await util.IntegrationLog("PFC: User settings not completed, user selected cancel"); + Close(); + return; } + Text = "AyaNova QBI - " + util.QCompanyName; + + //See if there are *any* data mappings, if not then we will prompt the user to start that process + if (util.QBIntegration.Items.Count == 0) + { + //show message about mapping + MessageBox.Show( + "AyaNova QBI now needs you to map data between QuickBooks and AyaNova.", + "Setup mapping", MessageBoxButtons.OK, MessageBoxIcon.Information); + + Map m = new Map(); + if (m.ShowDialog() == DialogResult.Abort) + Close(); + + } + //Display billable workorders + InitInvoices(); + grid.Visible = true; menuStrip1.Enabled = true; - //MessageBox.Show("DONE / OK"); - - // grid.DataSource = util.GetInvoiceableItems(); } private void grid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) @@ -116,18 +125,17 @@ namespace AyaNovaQBI private async void preferencesToolStripMenuItem_Click(object sender, EventArgs e) { await util.ValidateSettings(true); - //TODO: CODE THIS InitInvoices(); } private void mapAndImportToolStripMenuItem_Click(object sender, EventArgs e) { Map m = new Map(); if (m.ShowDialog() == DialogResult.Abort) - this.Close(); + Close(); else { m.Dispose(); - //todo: this.InitInvoices(); + InitInvoices(); } } @@ -142,7 +150,7 @@ namespace AyaNovaQBI MessageBox.Show(sVersion, "About"); } - + private void onlineManualToolStripMenuItem_Click(object sender, EventArgs e) { util.OpenWebURL("https://ayanova.com/qbi/docs"); @@ -154,14 +162,224 @@ namespace AyaNovaQBI await util.PopulateAyaListCache(); } - private async void invoiceDescriptiveTextTemplateToolStripMenuItem_Click(object sender, EventArgs e) + private async void invoiceDescriptiveTextTemplateToolStripMenuItem_Click(object sender, EventArgs e) { InvoiceTemplateBuilder b = new InvoiceTemplateBuilder(); b.ShowDialog(); - if (util.QDat.IsDirty) + if (util.QDat.IsDirty) await util.SaveIntegrationObject(); b.Dispose(); } - } -} + + + + + + #region Main workorder grid + + /// + /// Adjusts main form display to either show a list of billable workorders + /// or a status indicating there are none and why + /// + private void SetState() + { + fixProblemsToolStripMenuItem.Enabled = _MisMatches.Count > 0; + + if (grid.Rows.Count > 0) + { + + grid.Visible = true; + this.lblStatus.Visible = false; + } + else + { + StringBuilder sb = new StringBuilder(); + sb.Append("No invoiceable work orders found in AyaNova\r\n\r\n"); + sb.Append("A work order is invoiceable and will be listed here if it has:\r\n"); + sb.Append(" - \"Invoice number\" field empty\r\n"); + if (util.QDat.PreWOStatus != Guid.Empty) + { + sb.Append(" - \"Status\" field set to: "); + sb.Append(NameFetcher.GetItem("aWorkorderStatus", "aName", util.QDat.PreWOStatus)); + sb.Append("\r\n"); + sb.Append(" (You can change this status under Tools->Preferences in the menu)"); + + } + this.lblStatus.Text = sb.ToString(); + + grid.Visible = false; + this.lblStatus.Visible = true; + } + } + + private WorkorderServiceBillableList _wolist = null; + private ArrayList _MisMatches = new ArrayList(); + private ArrayList _PartPriceOverrides = new ArrayList(); + /// + /// Initialize invoices dataset + /// from scratch. If a previous + /// initialize was done, wipe it and + /// repopulate from scratch + /// + private void InitInvoices() + { + Waiting w = new Waiting(); + w.Show(); + w.Ops = "Validating invoices..."; + + + try + { + + _MisMatches.Clear(); + grid.BeginUpdate(); + dsInvoices.Clear(); + _wolist = WorkorderServiceBillableList.GetList(Util.QDat.PreWOStatus, true); + DataTable dtInvoice = dsInvoices.Tables["Invoices"]; + DataTable dtWorkorder = dsInvoices.Tables["Workorders"]; + foreach (WorkorderServiceBillableList.WorkorderServiceBillableListInfo i in _wolist) + { + bool bLinked = Util.ScanLinksOK(i.ID, _MisMatches, _PartPriceOverrides); + + DataRow dri = InvoiceRowForClientID(i.ClientID); + w.Step = "WO: " + i.ServiceNumber; + if (dri == null) + { + dri = dtInvoice.NewRow(); + dri["Client"] = i.Client; + dri["ClientID"] = i.ClientID; + dtInvoice.Rows.Add(dri); + } + + //If any one single workorder is linked + //then the invoice is flagged as linked because + //you can invoice out anything under it that is linked and the + //not linked items simply won't invoice + if (bLinked) + dri["Linked"] = true; + + DataRow drw = dtWorkorder.NewRow(); + drw["InvoiceWorkingID"] = (int)dri["WorkingID"]; + drw["WorkorderID"] = i.ID; + drw["Status"] = i.Status; + drw["ServiceNumber"] = i.ServiceNumber; + drw["ServiceDate"] = i.ServiceDate; + drw["Project"] = i.Project; + drw["StatusARGB"] = i.StatusARGB; + + drw["Linked"] = bLinked; + dtWorkorder.Rows.Add(drw); + + + + } + + grid.DisplayLayout.Rows.CollapseAll(false); + foreach (UltraGridRow r in grid.Rows) + { + foreach (UltraGridRow rr in r.ChildBands[0].Rows) + { + if ((bool)rr.Cells["Linked"].Value == false) + r.Expanded = true; + } + + } + grid.EndUpdate(); + } + finally + { + w.Close(); + } + + SetState(); + } + + + /// + /// Helper for grouping workorders by client + /// + /// + /// null if not found else datarow containing invoice for client + private DataRow InvoiceRowForClientID(Guid ClientID) + { + foreach (DataRow r in dsInvoices.Tables["Invoices"].Rows) + { + if ((Guid)r["ClientID"] == ClientID) + { + return r; + + } + } + return null; + } + + + + + private void InitializeGrid() + { + grid.DataSource = dsInvoices; + string currentAssemblyDirectoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + //Load the grid layout from file + if (System.IO.File.Exists(currentAssemblyDirectoryName + "\\MainGrid.lyt")) + grid.DisplayLayout.Load(currentAssemblyDirectoryName + "\\MainGrid.lyt"); + grid.DisplayLayout.Bands[0].Columns["WorkingID"].Hidden = true; + grid.DisplayLayout.Bands[0].Columns["ClientID"].Hidden = true; + grid.DisplayLayout.Bands[0].Columns["Linked"].Hidden = true; + grid.DisplayLayout.Bands[0].Columns["Client"].Header.Caption = "Invoice"; + + grid.DisplayLayout.Bands[1].Columns["InvoiceWorkingID"].Hidden = true; + grid.DisplayLayout.Bands[1].Columns["WorkorderID"].Hidden = true; + grid.DisplayLayout.Bands[1].Columns["StatusARGB"].Hidden = true; + grid.DisplayLayout.Bands[1].Columns["Linked"].Hidden = true; + } + + private void grid_InitializeRow(object sender, Infragistics.Win.UltraWinGrid.InitializeRowEventArgs e) + { + if (e.Row.Band.Index == 0) + { + //Prepare invoice row + if ((bool)e.Row.Cells["Linked"].Value == true) + { + e.Row.Cells["Client"].Appearance.Image = Util.AyaImage("OK16");//Util.Image("OK16.png"); + } + else + { + e.Row.Cells["Client"].Appearance.Image = Util.AyaImage("Cancel16");//Util.Image("Cancel16.png"); + } + } + else + { + //prepare workorder row + + //if backcolor==0 that means no color was set + int nColor = (int)e.Row.Cells["StatusARGB"].Value; + if (nColor != 0) + { + e.Row.Cells["Status"].Appearance.BackColor = Color.FromArgb(nColor); + e.Row.Cells["Status"].Appearance.ForeColor = Util.InvertColor(Color.FromArgb(nColor)); + + } + + //flag whether billable (linked) or not + if ((bool)e.Row.Cells["Linked"].Value == true) + { + e.Row.Cells["ServiceNumber"].Appearance.Image = Util.AyaImage("OK16"); + } + else + { + e.Row.Cells["ServiceNumber"].Appearance.Image = Util.AyaImage("Cancel16"); + } + } + + } + #endregion + + + + + + }//eoc +}//eons diff --git a/AyaNovaQBI/WorkOrder.cs b/AyaNovaQBI/WorkOrder.cs new file mode 100644 index 0000000..97fcd4c --- /dev/null +++ b/AyaNovaQBI/WorkOrder.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AyaNovaQBI +{ + internal class WorkOrder + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + + public long Serial { get; set; } + + public string Notes { get; set; }//WAS "SUMMARY" + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } = new List(); + + + public long CustomerId { get; set; } + + public string CustomerViz { get; set; } + + + public string CustomerTechNotesViz { get; set; } + + public string CustomerPhone1Viz { get; set; } + + public string CustomerPhone2Viz { get; set; } + + public string CustomerPhone3Viz { get; set; } + + public string CustomerPhone4Viz { get; set; } + + public string CustomerPhone5Viz { get; set; } + + public string CustomerEmailAddressViz { get; set; } + + public long? ProjectId { get; set; } + + public string ProjectViz { get; set; } + public string InternalReferenceNumber { get; set; } + public string CustomerReferenceNumber { get; set; } + public string CustomerContactName { get; set; } + public long? FromQuoteId { get; set; } + public long? FromPMId { get; set; } + + public DateTime CreatedDate { get; set; } = DateTime.UtcNow; + public DateTime? ServiceDate { get; set; } + public DateTime? CompleteByDate { get; set; } + public TimeSpan DurationToCompleted { get; set; } = TimeSpan.Zero; + public string InvoiceNumber { get; set; } + public string CustomerSignature { get; set; } + public string CustomerSignatureName { get; set; } + public DateTime? CustomerSignatureCaptured { get; set; } + public string TechSignature { get; set; } + public string TechSignatureName { get; set; } + public DateTime? TechSignatureCaptured { get; set; } + public bool Onsite { get; set; } + public long? ContractId { get; set; } + + public string ContractViz { get; set; } + + //redundant field to speed up list queries + //(added after status system already coded) + public long? LastStatusId { get; set; } + + + //POSTAL ADDRESS / "BILLING ADDRESS" + public string PostAddress { get; set; } + public string PostCity { get; set; } + public string PostRegion { get; set; } + public string PostCountry { get; set; } + public string PostCode { get; set; } + + //PHYSICAL ADDRESS / "SERVICE ADDRESS" + public string Address { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string Country { get; set; } + public decimal? Latitude { get; set; } + public decimal? Longitude { get; set; } + + public List Items { get; set; } = new List(); + public List States { get; set; } = new List(); + + + //UTILITY FIELDS + + public bool IsLockedAtServer { get; set; } = false;//signal to client that it came from the server in a locked state + + public string AlertViz { get; set; } = null; + + public string FromQuoteViz { get; set; } + + public string FromPMViz { get; set; } + + + public string LastStateUserViz { get; set; } + + public string LastStateNameViz { get; set; } + + public string LastStateColorViz { get; set; } + + public bool LastStateCompletedViz { get; set; } + + public bool LastStateLockedViz { get; set; } + + + + public bool IsCompleteRecord { get; set; } = true;//indicates if some items were removed due to user role / type restrictions (i.e. woitems they are not scheduled on) + + + public bool UserIsRestrictedType { get; set; } + + public bool UserIsTechRestricted { get; set; } + + public bool UserIsSubContractorFull { get; set; } + + public bool UserIsSubContractorRestricted { get; set; } + + public bool UserCanViewPartCosts { get; set; } + + public bool UserCanViewLaborOrTravelRateCosts { get; set; } + + public bool UserCanViewLoanerCosts { get; set; } + } +} diff --git a/AyaNovaQBI/WorkOrderItem.cs b/AyaNovaQBI/WorkOrderItem.cs new file mode 100644 index 0000000..23fb782 --- /dev/null +++ b/AyaNovaQBI/WorkOrderItem.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AyaNovaQBI +{ + internal class WorkOrderItem + { + public long Id { get; set; } + public uint Concurrency { get; set; } + public string Notes { get; set; }//"Summary" field + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } = new List(); + + + public long WorkOrderId { get; set; } + public string TechNotes { get; set; } + public long? WorkOrderItemStatusId { get; set; } + + public string WorkOrderItemStatusNameViz { get; set; } + + public string WorkOrderItemStatusColorViz { get; set; } + + public long? WorkOrderItemPriorityId { get; set; } + + public string WorkOrderItemPriorityNameViz { get; set; } + + public string WorkOrderItemPriorityColorViz { get; set; } + + public DateTime? RequestDate { get; set; } + public bool WarrantyService { get; set; } = false; + public int Sequence { get; set; } + + public long? FromCSRId { get; set; } + + public string FromCSRViz { get; set; } + + //workaround for notification + + public string Name { get; set; } + + //Principle + + public WorkOrder WorkOrder { get; set; } + //dependents + public List Expenses { get; set; } = new List(); + public List Labors { get; set; } = new List(); + public List Loans { get; set; } = new List(); + public List Parts { get; set; } = new List(); + public List PartRequests { get; set; } = new List(); + public List ScheduledUsers { get; set; } = new List(); + public List Tasks { get; set; } = new List(); + public List Travels { get; set; } = new List(); + public List Units { get; set; } = new List(); + public List OutsideServices { get; set; } = new List(); + } +} diff --git a/AyaNovaQBI/WorkOrderItemExpense.cs b/AyaNovaQBI/WorkOrderItemExpense.cs new file mode 100644 index 0000000..fc9767e --- /dev/null +++ b/AyaNovaQBI/WorkOrderItemExpense.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AyaNovaQBI +{ + internal class WorkOrderItemExpense + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + public string Description { get; set; } + public string Name { get; set; } + + public decimal TotalCost { get; set; } + + public decimal ChargeAmount { get; set; } + + public decimal TaxPaid { get; set; } + public long? ChargeTaxCodeId { get; set; } + + public string TaxCodeViz { get; set; } + + public bool ReimburseUser { get; set; } = false; + public long? UserId { get; set; } + + public string UserViz { get; set; } + public bool ChargeToCustomer { get; set; } = false; + + + public decimal TaxAViz { get; set; } + + public decimal TaxBViz { get; set; } + + public decimal LineTotalViz { get; set; } + + public long WorkOrderItemId { get; set; } + } +} diff --git a/AyaNovaQBI/WorkOrderItemLabor.cs b/AyaNovaQBI/WorkOrderItemLabor.cs new file mode 100644 index 0000000..ae0f3fe --- /dev/null +++ b/AyaNovaQBI/WorkOrderItemLabor.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AyaNovaQBI +{ + internal class WorkOrderItemLabor + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + public long? UserId { get; set; } + + public string UserViz { get; set; } + public DateTime? ServiceStartDate { get; set; } + public DateTime? ServiceStopDate { get; set; } + public long? ServiceRateId { get; set; } + + public string ServiceRateViz { get; set; } + public string ServiceDetails { get; set; } + + public decimal ServiceRateQuantity { get; set; } + + public decimal NoChargeQuantity { get; set; } + //public long? ServiceBankId { get; set; } + public long? TaxCodeSaleId { get; set; } + + public string TaxCodeViz { get; set; } + + + //Standard pricing fields (mostly to support printed reports though some show in UI) + //some not to be sent with record depending on role (i.e. cost and charge in some cases) + public decimal? PriceOverride { get; set; }//user entered manually overridden price, if null then ignored in calcs otherwise this *is* the price even if zero + + public decimal CostViz { get; set; }//cost from source record (e.g. serviceRate) or zero if no cost entered + + public decimal ListPriceViz { get; set; }//List price from source record (e.g. serviceRate) or zero if no cost entered + + public string UnitOfMeasureViz { get; set; }//"each", "hour" etc + + public decimal PriceViz { get; set; }//per unit price used in calcs after discounts or manual price if non-null or just ListPrice if no discount or manual override + + public decimal NetViz { get; set; }//quantity * price (before taxes line total essentially) + + public decimal TaxAViz { get; set; }//total amount of taxA + + public decimal TaxBViz { get; set; }//total amount of taxB + + public decimal LineTotalViz { get; set; }//line total netViz + taxes + + + public long WorkOrderItemId { get; set; } + + + } +} diff --git a/AyaNovaQBI/WorkOrderItemLoan.cs b/AyaNovaQBI/WorkOrderItemLoan.cs new file mode 100644 index 0000000..ade1410 --- /dev/null +++ b/AyaNovaQBI/WorkOrderItemLoan.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AyaNovaQBI +{ + internal enum LoanUnitRateUnit : int + { + None = 0, + Hours = 1, + HalfDays = 2, + Days = 3, + Weeks = 4, + Months = 5, + Years = 6 + } + internal class WorkOrderItemLoan + { + public long Id { get; set; } + public uint Concurrency { get; set; } + public string Notes { get; set; } + public DateTime? OutDate { get; set; } + public DateTime? DueDate { get; set; } + public DateTime? ReturnDate { get; set; } + // + // public decimal Charges { get; set; }//removed in favor of ListPRice snapshot which normalizes fields to other objects + public long? TaxCodeId { get; set; } + + public string TaxCodeViz { get; set; } + + public long LoanUnitId { get; set; } + + public string LoanUnitViz { get; set; } + + public decimal Quantity { get; set; } + + public LoanUnitRateUnit Rate { get; set; } + + public decimal Cost { get; set; }//cost from source record (e.g. serviceRate) or zero if no cost entered + public decimal ListPrice { get; set; }//List price from source record (e.g. serviceRate) or zero if no cost entered + + //Standard pricing fields (mostly to support printed reports though some show in UI) + //some not to be sent with record depending on role (i.e. cost and charge in some cases) + public decimal? PriceOverride { get; set; }//user entered manually overridden price, if null then ignored in calcs otherwise this *is* the price even if zero + + + public string UnitOfMeasureViz { get; set; }//"each", "hour" etc + + public decimal PriceViz { get; set; }//per unit price used in calcs after discounts or manual price if non-null or just ListPrice if no discount or manual override + + public decimal NetViz { get; set; }//quantity * price (before taxes line total essentially) + + public decimal TaxAViz { get; set; }//total amount of taxA + + public decimal TaxBViz { get; set; }//total amount of taxB + + public decimal LineTotalViz { get; set; }//line total netViz + taxes + + //workaround for notification + + public List Tags { get; set; } = new List(); + + public string Name { get; set; } + + public long WorkOrderItemId { get; set; } + } +} diff --git a/AyaNovaQBI/WorkOrderItemOutsideService.cs b/AyaNovaQBI/WorkOrderItemOutsideService.cs new file mode 100644 index 0000000..d02050f --- /dev/null +++ b/AyaNovaQBI/WorkOrderItemOutsideService.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AyaNovaQBI +{ + internal class WorkOrderItemOutsideService + { + public long Id { get; set; } + public uint Concurrency { get; set; } + public string Notes { get; set; } + + + public long UnitId { get; set; } + + public string UnitViz { get; set; } + public long? VendorSentToId { get; set; } + + public string VendorSentToViz { get; set; } + public long? VendorSentViaId { get; set; } + + public string VendorSentViaViz { get; set; } + public string RMANumber { get; set; } + public string TrackingNumber { get; set; } + + public decimal RepairCost { get; set; } + + public decimal RepairPrice { get; set; } + + public decimal ShippingCost { get; set; } + + public decimal ShippingPrice { get; set; } + public DateTime? SentDate { get; set; } + public DateTime? ETADate { get; set; } + public DateTime? ReturnDate { get; set; } + public long? TaxCodeId { get; set; } + + public string TaxCodeViz { get; set; } + + public decimal CostViz { get; set; }//Total cost shipping + repairs + + public decimal PriceViz { get; set; }//Total price shipping + repairs + + public decimal NetViz { get; set; }//=priceViz for standardization not because it's necessary (before taxes line total essentially) + + public decimal TaxAViz { get; set; }//total amount of taxA + + public decimal TaxBViz { get; set; }//total amount of taxB + + public decimal LineTotalViz { get; set; }//line total netViz + taxes + + //workaround for notification + + public List Tags { get; set; } = new List(); + + public string Name { get; set; } + + + public long WorkOrderItemId { get; set; } + } +} diff --git a/AyaNovaQBI/WorkOrderItemPart.cs b/AyaNovaQBI/WorkOrderItemPart.cs new file mode 100644 index 0000000..64a2b4d --- /dev/null +++ b/AyaNovaQBI/WorkOrderItemPart.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AyaNovaQBI +{ + internal class WorkOrderItemPart + { + public long Id { get; set; } + public uint Concurrency { get; set; } + public string Description { get; set; } + public string Serials { get; set; } + + public long PartId { get; set; } + + public string PartDescriptionViz { get; set; } + + public string PartNameViz { get; set; } + + public string UpcViz { get; set; } + + public long PartWarehouseId { get; set; } + + public string PartWarehouseViz { get; set; } + + public decimal Quantity { get; set; } + public decimal SuggestedQuantity { get; set; } + public long? TaxPartSaleId { get; set; } + + public string TaxCodeViz { get; set; } + + //NOTE: part prices are volatile and expected to be frequently edited so snapshotted when newly added unlike other things like rates etc that are protected from change + public decimal Cost { get; set; }//cost from source record (e.g. serviceRate) or zero if no cost entered + public decimal ListPrice { get; set; }//List price from source record (e.g. serviceRate) or zero if no cost entered + + //Standard pricing fields (mostly to support printed reports though some show in UI) + //some not to be sent with record depending on role (i.e. cost and charge in some cases) + public decimal? PriceOverride { get; set; }//user entered manually overridden price, if null then ignored in calcs otherwise this *is* the price even if zero + + + + public string UnitOfMeasureViz { get; set; }//"each", "hour" etc + + public decimal PriceViz { get; set; }//per unit price used in calcs after discounts or manual price if non-null or just ListPrice if no discount or manual override + + public decimal NetViz { get; set; }//quantity * price (before taxes line total essentially) + + public decimal TaxAViz { get; set; }//total amount of taxA + + public decimal TaxBViz { get; set; }//total amount of taxB + + public decimal LineTotalViz { get; set; }//line total netViz + taxes + + //workaround for notification + + public List Tags { get; set; } = new List(); + + public string Name { get; set; } + + + public long WorkOrderItemId { get; set; } + } +} diff --git a/AyaNovaQBI/WorkOrderItemTravel.cs b/AyaNovaQBI/WorkOrderItemTravel.cs new file mode 100644 index 0000000..f313968 --- /dev/null +++ b/AyaNovaQBI/WorkOrderItemTravel.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AyaNovaQBI +{ + internal class WorkOrderItemTravel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + public long? UserId { get; set; } + + public string UserViz { get; set; } + public DateTime? TravelStartDate { get; set; } + public DateTime? TravelStopDate { get; set; } + public long? TravelRateId { get; set; } + + public string TravelRateViz { get; set; } + public string TravelDetails { get; set; } + public decimal TravelRateQuantity { get; set; } + public decimal NoChargeQuantity { get; set; } + //public long? ServiceBankId { get; set; } + public long? TaxCodeSaleId { get; set; } + + public string TaxCodeViz { get; set; } + public decimal Distance { get; set; } + + + //Standard pricing fields (mostly to support printed reports though some show in UI) + //some not to be sent with record depending on role (i.e. cost and charge in some cases) + public decimal? PriceOverride { get; set; }//user entered manually overridden price, if null then ignored in calcs otherwise this *is* the price even if zero + + public decimal CostViz { get; set; }//cost from source record (e.g. serviceRate) or zero if no cost entered + + public decimal ListPriceViz { get; set; }//List price from source record (e.g. serviceRate) or zero if no cost entered + + public string UnitOfMeasureViz { get; set; }//"each", "hour" etc + + public decimal PriceViz { get; set; }//per unit price used in calcs after discounts or manual price if non-null or just ListPrice if no discount or manual override + + public decimal NetViz { get; set; }//quantity * price (before taxes line total essentially) + + public decimal TaxAViz { get; set; }//total amount of taxA + + public decimal TaxBViz { get; set; }//total amount of taxB + + public decimal LineTotalViz { get; set; }//line total netViz + taxes + + //workaround for notification + + public List Tags { get; set; } = new List(); + + public string Name { get; set; } + + + public long WorkOrderItemId { get; set; } + } +} diff --git a/AyaNovaQBI/util.cs b/AyaNovaQBI/util.cs index 03b5a53..3d905cd 100644 --- a/AyaNovaQBI/util.cs +++ b/AyaNovaQBI/util.cs @@ -5654,6 +5654,314 @@ namespace AyaNovaQBI #endregion export to quickbooks + + + #region Workorder mismatch scanning + + public enum MisMatchReason + { + NotLinkedToQB = 0, + PriceDifferent = 1, + NothingToInvoice = 2 + + } + /// + /// Mismatch properties + /// A structure for storing mismatches identified + ///so user can resolve them. + /// + public class MisMatch + { + //public Guid WorkorderID; + public long ObjectId { get; set; } + public AyaType ObjectType { get; set; } + public string Name { get; set; } + public MisMatchReason mReason { get; set; } + public decimal AyaPrice { get; set; } + public decimal QBPrice { get; set; } + public long WorkorderItemPartId { get; set; } + public string QBListID { get; set; } + + } + + + /// + /// Given a workorder ID + /// scans the objects in the workorder + /// that need to be linked to QB for invoicing + /// and on any error found adds them to the + /// mismatched object array list + /// + /// Id of workorder being scanned + /// An list of mismatch objects + /// An list of id values of workorderitemparts that have been set by + /// user to forcibly use the price set on the workorderitem part even though + /// it differs from the quickbooks price + /// True if all links ok, false if there are any mismatches at all + public static bool ScanLinksOK(long WorkorderID, List MisMatches, List PriceOverrides) + { + bool bReturn = true; + bool bSomethingToInvoice = false; + + + Workorder w = Workorder.GetItem(WorkorderID); + + //Client ok? + if (!QBIntegration.Items.Any(z=>z.AType==AyaType.Customer && z.ObjectId==w.CustomerId)) + { + bReturn = false; + AddMisMatch(AyaClientList[w.ClientID].Name, w.ClientID, AyaType.Customer, MisMatchReason.NotLinkedToQB, MisMatches); + } + + //Service rates: + foreach (WorkorderItem wi in w.WorkorderItems) + { + #region Labor + foreach (WorkorderItemLabor wl in wi.Labors) + { + + //If there's *any* labor then there is something to invoice + bSomethingToInvoice = true; + + //Check that rate isn't actually guid.empty + //it's possible that some users have not selected a rate on the workorder + if (wl.ServiceRateID == Guid.Empty) + throw new System.ApplicationException("ERROR: Workorder " + w.WorkorderService.ServiceNumber.ToString() + " has a labor item with no rate selected\r\n" + + "This is a serious problem for QBI and needs to be rectified before QBI can be used.\r\n"); + + if (!QBI.Maps.Contains(wl.ServiceRateID)) + { + bReturn = false; + AddMisMatch(AyaRateList[wl.ServiceRateID].Name, wl.ServiceRateID, RootObjectTypes.Rate, MisMatchReason.NotLinkedToQB, MisMatches); + + } + + } + #endregion + + #region Travel + foreach (WorkorderItemTravel wt in wi.Travels) + { + //If there's *any* travel then there is something to invoice + bSomethingToInvoice = true; + + //Check that rate isn't actually guid.empty + //it's possible that some users have not selected a rate on the workorder + if (wt.TravelRateID == Guid.Empty) + throw new System.ApplicationException("ERROR: Workorder " + w.WorkorderService.ServiceNumber.ToString() + " has a travel item with no rate selected\r\n" + + "This is a serious problem for QBI and needs to be rectified before QBI can be used.\r\n"); + + if (!QBI.Maps.Contains(wt.TravelRateID)) + { + bReturn = false; + AddMisMatch(AyaRateList[wt.TravelRateID].Name, wt.TravelRateID, RootObjectTypes.Rate, MisMatchReason.NotLinkedToQB, MisMatches); + + } + + } + #endregion + + #region Parts + foreach (WorkorderItemPart wp in wi.Parts) + { + //If there's *any* parts then there is something to invoice + bSomethingToInvoice = true; + + //Changed: 14-Nov-2006 to check that linked item id exists in qb + if (!QBI.Maps.Contains(wp.PartID) || QBItems.Rows.Find(QBI.Maps[wp.PartID].ForeignID) == null) + { + bReturn = false; + //Changed: 21-June-2006 to use display formatted name + AddMisMatch(AyaPartList[wp.PartID].DisplayName(Util.GlobalSettings.DefaultPartDisplayFormat), wp.PartID, RootObjectTypes.Part, MisMatchReason.NotLinkedToQB, MisMatches); + + } + else + { + //check the price + if (!PriceOverrides.Contains(wp.ID)) + { + + decimal qbPrice = (decimal)QBItems.Rows.Find(QBI.Maps[wp.PartID].ForeignID)["Price"]; + + //------------DISCOUNT----------------- + string disco = ""; + //Added:20-July-2006 to incorporate discounts on parts into qb invoice + decimal charge; + + //Changed: 18-Nov-2006 CASE 158 + //this is all wrong, it was multiplying price by quantity to calculate charge when it shouldn't + //removed quanty * price in next line to just price + charge = decimal.Round(wp.Price, 2, MidpointRounding.AwayFromZero); + charge = charge - (decimal.Round(charge * wp.Discount, 2, MidpointRounding.AwayFromZero)); + if (wp.Discount != 0) + { + disco = " (Price " + wp.Price.ToString("c") + " discounted on workorder " + wp.Discount.ToString("p") + ") \r\n"; + } + + //----------------------------- + + //It's a match, let's see if the price matches as well + if (charge != qbPrice) + { + bReturn = false; + AddMisMatch("WO: " + w.WorkorderService.ServiceNumber.ToString() + disco + " Part: " + AyaPartList[wp.PartID].DisplayName(Util.GlobalSettings.DefaultPartDisplayFormat),//Changed: 21-June-2006 to use display formatted name + wp.PartID, RootObjectTypes.Part, MisMatchReason.PriceDifferent, MisMatches, qbPrice, charge, wp.ID, + QBI.Maps[wp.PartID].ForeignID); + + } + } + } + + } + #endregion + + #region Outside service charges + + if (wi.HasOutsideService) + { + if (wi.OutsideService.RepairPrice != 0 || wi.OutsideService.ShippingPrice != 0) + { + bSomethingToInvoice = true; + //there is something billable, just need to make sure + //that there is a QB charge defined for outside service + if (QDat.OutsideServiceChargeAs == null || + QDat.OutsideServiceChargeAs == "" || + !QBItems.Rows.Contains(QDat.OutsideServiceChargeAs)) + { + bReturn = false; + AddMisMatch("Outside service", Guid.Empty, RootObjectTypes.WorkorderItemOutsideService, MisMatchReason.NotLinkedToQB, MisMatches); + + } + } + } + #endregion + + #region Workorder item loan charges + + if (wi.HasLoans) + { + foreach (WorkorderItemLoan wil in wi.Loans) + { + + if (wil.Charges != 0) + { + //case 772 + bSomethingToInvoice = true; + + //there is something billable, just need to make sure + //that there is a QB charge defined for loaned item charges + if (QDat.WorkorderItemLoanChargeAs == null || + QDat.WorkorderItemLoanChargeAs == "" || + !QBItems.Rows.Contains(QDat.WorkorderItemLoanChargeAs)) + { + bReturn = false; + AddMisMatch("Workorder item loan", Guid.Empty, RootObjectTypes.WorkorderItemLoan, MisMatchReason.NotLinkedToQB, MisMatches); + break; + + } + } + } + } + #endregion + + #region Workorder item misc expenses + + if (wi.HasExpenses) + { + foreach (WorkorderItemMiscExpense wie in wi.Expenses) + { + + + if (wie.ChargeToClient) + { + bSomethingToInvoice = true; + //there is something billable, just need to make sure + //that there is a QB charge defined for misc expense item charges + if (QDat.MiscExpenseChargeAs == null || + QDat.MiscExpenseChargeAs == "" || + !QBItems.Rows.Contains(QDat.MiscExpenseChargeAs)) + { + bReturn = false; + AddMisMatch("Workorder item expense", Guid.Empty, RootObjectTypes.WorkorderItemMiscExpense, MisMatchReason.NotLinkedToQB, MisMatches); + break; + + } + } + } + } + #endregion + + + }//workorder items loop + + //If there are no mismatches so far, + //maybe it's because it's got nothing to invoice? + if (bReturn && !bSomethingToInvoice) + { + bReturn = false; + AddMisMatch("WO: " + w.WorkorderService.ServiceNumber.ToString() + " - Nothing chargeable on it, will not be invoiced", + Guid.Empty, RootObjectTypes.Nothing, MisMatchReason.NothingToInvoice, MisMatches); + + } + + return bReturn; + + + } + + + + private static void AddMisMatch(string Name, Guid RootObjectID, RootObjectTypes RootObjectType, MisMatchReason Reason, ArrayList Mismatches) + { + AddMisMatch(Name, RootObjectID, RootObjectType, Reason, Mismatches, 0m, 0m, Guid.Empty, ""); + } + + private static void AddMisMatch(string Name, Guid RootObjectID, RootObjectTypes RootObjectType, + MisMatchReason Reason, ArrayList Mismatches, decimal QBPrice, decimal AyaPrice, Guid WorkorderItemPartID, string QBListID) + { + bool bDuplicate = false; + //scan through list of existing mismatches, + //only add a not linked item if it's + //not there already + //other types of mismatches need to be added because + //they need to be resolved on a case by case basis or are unresolvable + if (Reason == MisMatchReason.NotLinkedToQB) + { + + foreach (object o in Mismatches) + { + MisMatch m = (MisMatch)o; + //Have to check ID and type here because for outside service + //and loans and misc expenses the id is always empty so type is + //the only way to differentiate + if (m.RootObjectID == RootObjectID && m.ObjectType == RootObjectType) + { + bDuplicate = true; + break; + } + } + } + + if (!bDuplicate) + { + MisMatch m = new MisMatch(); + m.mName = Name; + m.mRootObjectID = RootObjectID; + m.mObjectType = RootObjectType; + m.mReason = Reason; + m.mAyaPrice = AyaPrice; + m.mQBPrice = QBPrice; + m.mWorkorderItemPartID = WorkorderItemPartID; + m.mQBListID = QBListID; + Mismatches.Add(m); + + } + } + + + #endregion wo_mismatch_scan + + #endregion qbi stuff (anything not api) #region general utils diff --git a/docs/docs/img/main-menu-invoice-fix-problems-item.png b/docs/docs/img/main-menu-invoice-fix-problems-item.png new file mode 100644 index 0000000000000000000000000000000000000000..9a931b21c8caa2cdebe7b50d3beae55482116a36 GIT binary patch literal 4160 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 literal 0 HcmV?d00001 diff --git a/docs/docs/img/main-menu-invoice-refresh-invoices-item.png b/docs/docs/img/main-menu-invoice-refresh-invoices-item.png new file mode 100644 index 0000000000000000000000000000000000000000..e1a14a8d5473c4613fe3110b3648d781fbc03623 GIT binary patch literal 3941 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 literal 0 HcmV?d00001 diff --git a/docs/docs/img/main-menu-invoice-selected-multiple-item.png b/docs/docs/img/main-menu-invoice-selected-multiple-item.png new file mode 100644 index 0000000000000000000000000000000000000000..13c2b67c4a14469f8fd965f28624619d37f5cdd7 GIT binary patch literal 6000 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 MsOTtHDcVN-52k8U`~Uy| literal 0 HcmV?d00001 diff --git a/docs/docs/img/main-menu-invoice-selected-one-item.png b/docs/docs/img/main-menu-invoice-selected-one-item.png new file mode 100644 index 0000000000000000000000000000000000000000..802380486f2be6f88ac2396d0f278a13c9d5f85d GIT binary patch literal 6610 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 E7YHtnZU6uP literal 0 HcmV?d00001 diff --git a/docs/docs/img/main-menu-tools-invoice-template-item.png b/docs/docs/img/main-menu-tools-invoice-template-item.png new file mode 100644 index 0000000000000000000000000000000000000000..571225c6144afaf0294a6eb56f4fa398e2038ad2 GIT binary patch literal 4303 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 bCp5N>-Hn>P_gGIqApkvXBQ5k@`>1~b%NlQX literal 0 HcmV?d00001 diff --git a/docs/docs/img/main-menu-tools-item.png b/docs/docs/img/main-menu-tools-item.png new file mode 100644 index 0000000000000000000000000000000000000000..ab73f9bd9330733f0111995224b52ea7da67c9a3 GIT binary patch literal 4703 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 literal 0 HcmV?d00001 diff --git a/docs/docs/img/main-menu-tools-link-and-sync-item.png b/docs/docs/img/main-menu-tools-link-and-sync-item.png new file mode 100644 index 0000000000000000000000000000000000000000..b9e34c35562fa20ba0790ab98d0541cfdba9a2b2 GIT binary patch literal 4227 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 literal 0 HcmV?d00001 diff --git a/docs/docs/img/main-menu-tools-preferences-item.png b/docs/docs/img/main-menu-tools-preferences-item.png new file mode 100644 index 0000000000000000000000000000000000000000..3b11efde553ecbb0cc830059eb75bf312485cae0 GIT binary patch literal 3937 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 I(EX?X189S^f&c&j literal 0 HcmV?d00001 diff --git a/docs/docs/img/main-menu-tools-refresh-cached-data-item.png b/docs/docs/img/main-menu-tools-refresh-cached-data-item.png new file mode 100644 index 0000000000000000000000000000000000000000..58d3190d94b1c549c4203ca883ffcec9dda6ea3d GIT binary patch literal 3983 zcmaKvc{J2-`^P`DC_C9Qc2Twwk)g5gvTtJ@vSg627-N?tikK`jWE&}4gkC>&Uya0?(1CVT<3l6KVH}Sy6*&YQ++0SUU~okm<$bcEKl>^ zX&^70KRq2h3c;s|25G6U1ym05QBI4qUYaJF08ss$;qcD6)B56l1A8O@Fn9h5njZgB z4*+1fVyL5O9SUE~Blv>6czg08AZ_pA<1@E^yVZR=o1QzRdJpY+rpTP_vOn)TLQ!j> zd7{z1u3)8CB~I+&6-?f&w&p?{@5w*kZA<8euDrh(_l`CEnu*(0bRL9A4kPD%Gsq56 zu)N~AwSN$!lpXe+d?c41HbV|`2@~KC-CDFrX1_n%P7`%@=#k9H)tjo0-YpqyM;C#2 znD#W6rXWL4Mo@nAaibbBx;?GrxdQxlY+Am0G~AP!!6o`g5i>>ma^dLQnhKC{MvSgK zkQdWpTh3%d3*bvcZ^PA)Pnr3BOnSJTWxieXR6#C6P&T#hk_wx@&vGhlc|Vj;H)hX* zi$;Ag@qaR8tjn!i4KGaSgm$B%m^GfFG0>1zWz}dfg_rcXpfSI7GvA9}h9mO?cq#O(b7S?6BZhGi9WUecv9*7>)RklBPW%G{?rDo}p%eC&}{ zCdgkJ^f{A~-@|6aNCqCA}?wlfgmVCM_t@_F5&!I0&PV=P%WyB&5gav7KQc)T$= zaey9bQSf$MM3#+Hl=H=;1iQ?$0Uumk@c|yr@*LJJv39`j*|3?Igo^KJKayfm6}sJ@ z(|_n6|G{Cp@CykdD7eB6pH6EAPbLpzZdDNS?6Xx@)1<`3E2WLQg~|JuRlbcoH*_mj zW@gw4D}X@Iu@tv2)(|P5JN>KD6vE%9X2D{Oy~C3euU(!e{$x;|go@q}bN&4w^;&s>%g)=im>N zJ3BB_aI1#7qK5AJeY60n+LjR<{(`B@8b5IU)*#4!AK@xKiX6`Lkm;ba3y8h4)t;7L z?AtO1SHDsAc4IMNIcR@SanKLO^~f1zGf4U57Jt{J9~OA8Ym_%at14DCvy~Iac(b^b znjqr2|MRBOny@iP7;W~8g6&A$LBFM1o9S>zw<8bui`zWYhgqX4*E4QSDqLwh?XY5Ra1d=o zz^Ph!M?~b{)S}9U(yy8!e0^_=0qwJwPlqhTfE{R8n~B^s;oVznUjL`ksn>sRzjwo+ zg>d#^a?8o_fx^&q^91nl&TL04-7by~$Y~5&hi7A3jIHQchB}0jhqYjucS>6J=NYzm z;jZUqYpIj|ZAH^*x2#LUU4Gaw=xkc$NlKb=Sb6qg8d&_yYklYz2wQIz*%s5W ze;)9Y9)pCHPE{)hFY@3)D^?5Z&hbGe;L1aYBPAfXt7JDRfExk5zbaJyb1!$2S`OASxAOCcc-gvZKDtr5$@bo$u~BA|ectu= zkK#R8-rEO_f8?^gB$bI*#VOD`h$5ksr-dAio(=&>Yx8u&3s#PUa)EtTCO80S^+Md7 zfii&f(K7Y2{k;g@2${^sPwR4(%cubf&~d>vl|}C0NV7EvlpmuFu8o2B#{~o`P;^GT zhcj-#ihMfm#D3Rw0fHX#8-X({qk6z;BqME}V#E?rvJVJZ>` z{}dzpy@;sP7OBGx&V9K_CMX>y_zt|&M&`im6D z*`|sYYqsUqZ5S4)XTKghEE~L#tcv!Q)fZE|;62VU%2jxd0W-y_YE$!(rRJJ}vt0It zkJl9)g{`b@kAvtJxZemyZ7_op61yc=F-L})ubRgu<#xQD(w5Jvh^ZRlog$E16MXp* zCGW?;3r=Np_~f=Hclxa4?<~E{E_22SiM%F0!n^KeI|)E~4rdd}-z2rzy^`b%Z|vZ> zoQ%cRwi|UM$_6Yrk2S%ACqhDpvgf3Ha<5O6l^>8DH{-QZXr*C+b$7+;<_-~hm3h;O zb$#~&Myu_Nv7qNVE~p94@w+ZZi?Ja*hDZh5Co*Cc_m4Qs9ElKP3)fqk`bkW-^w7pH znc5kuY%AGPq5@?jCf1JE?^{+b2*XmW3SWr}^(3k_%Fpcg7b^#6de2NkvK`XBZqm$z zk9>cZBNE^*dR^C@6PAUo8vsYYuPXi{+wqKNc|Rw~hkqK3juM?Kj!;)6)eG+ou_ka8 zCZ3U!l9I$M5sAF7YMC!&3E7Hwf)K!#K*HGma#b5~t+IqQ01LV~1uV*%Q)3F+mbqXP z-`VI~0gYa|__r0U&XI>%*Lbe{4?3AKd_aFp+8I~nKTTJ=jKKcTZHe3zdV3CZ(vXzL zz!3sAyxMRO+}dfJe^O=n=@`Gz$s4Eet06~V7U#TPE~)2qq#&8cf9e0wHNZF#N@dt| z=S5!1_nC}#FhWL2*wfGKs>R)ORO^dyiEZh= z=W#%-?fuf99A!ACl#lYc4f%+yaKX_>o%4xHvFR>t-YxJ%kTyhpsu-qcT40NnpWjx@ z>zS!~`pZXIwhKRLu1_VM^qeSsRw^_lGCS8hne7U^*(@fDe^aGeLE7w#DNw3F(bWV~ z!oa{t{AXt+9S-b#>4j3sG=$S*%VY^&pEQF)5%qeyeG(Mo9c>yh|1i`@zHD=qq(4V660Q(sh#Xt>_?9$co`wh>L$ z8-eAxez9^IDAzQ1$pU*9#CgUTnfJ5g>TaQ|1j$OOD9aU+4Ag*p7FBBCc zN)1I)6O$wYeRKAiES#FrpO>iH6cr-KfIUX>R6^mrJ3pKtS;U5%AvM+J{L)FA6%b1% zPS(gVaoq|Y*EQVznaLk88X4Mc!cj27!8BS<_+XzRyJjyc-FYJ{Xu($ds9grilQ>88 z)3JT9I2IJK#BF)FoK9!ojJ06unyLyOpZxnT>%4PmE^h-E-Ut0^iGL$x^w(5F8lFEu z#!qhgHEZ|jy?KXSHSuEwy1~(8iXG>V!hx0bh{uQuVCB*uKW4^6B7STOi?r3z>=-=! zH~IgVH$F1%cgg07%{k!V56^X%Xr>QZ&#b9}caPtmr2#s7=x(3*ng=#4|Izr5seu=` z{k$H^UV_s{cq54~7 z6Q*dATuiA0C&UZRAUXFB#4_5lN<>w-gU*Q4I`RmF99d@^n}UMcR<$z=+L9>RKs&3% zUIqQMfV=Jv=k8yrY8RctsyE!i7La+B4?q`kb0P!KYu=}?6}I{ zz0OulwNC$=dntkO?j{d5rBp;RoXA7Y!IQeehZi4K^hFIm6W6|aQouQ5?JwQAYNl`J zvv!h(vor3GYgSs+w@s|+C}jWm{p`JWEppW2W8?XZ!|JNVx40|l$ZWC-8$SIRuNQ|Q zNu!L1IV4KjY>#jgzfEQHop&cGPb$DTUxMfqcn2&PDr{1jY31ds;6qR_d`DB@i2|}n$ zxao(JyXD`_(4K7|FBc}c<@VD20KTViv zm1$jtyEV`?H8n;6aQ#q6QW