using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace AyaNovaQBI { public partial class MainForm : Form { public MainForm() { InitializeComponent(); //InitDataSet(); Icon = AyaNovaQBI.Properties.Resources.logo; } private async void MainForm_Load(object sender, EventArgs e) { //Initialize StringBuilder initErrors = new StringBuilder(); if (await util.InitializeQBI(initErrors) == false) { if (initErrors.Length > 0) { if (util.LOG_AVAILABLE) await util.IntegrationLog(initErrors.ToString()); await Task.Run(() => MessageBox.Show($"AyaNova QBI was unable to start:\r\n{initErrors.ToString()}")); } Close(); return; } //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 await InitInvoices(); menuStrip1.Enabled = true; } private void grid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { if (e.ColumnIndex == grid.Columns["Status"].Index && e.Value != null) { var isLinked = (bool)grid.Rows[e.RowIndex].Cells["linked"].Value; if (!isLinked) { grid.Rows[e.RowIndex].ErrorText = "Not invoiceable: use \"Work orders\" -> \"Fix problems\" to resolve"; } else { grid.Rows[e.RowIndex].ErrorText = null; } } } private void exitToolStripMenuItem_Click(object sender, EventArgs e) { Close(); } private async void refreshInvoicesToolStripMenuItem_Click(object sender, EventArgs e) { await InitInvoices(); } private async void preferencesToolStripMenuItem_Click(object sender, EventArgs e) { await util.ValidateSettings(true); } private async void mapAndImportToolStripMenuItem_Click(object sender, EventArgs e) { Map m = new Map(); if (m.ShowDialog() == DialogResult.Abort) Close(); else { m.Dispose(); await InitInvoices(); } } private void aboutToolStripMenuItem_Click(object sender, EventArgs e) { Assembly a = Assembly.GetExecutingAssembly(); string sVersion = "AyaNova QBI version " + util.DisplayVersion(a.GetName().Version) + "\r\n"; System.Diagnostics.FileVersionInfo fileVersion = System.Diagnostics.FileVersionInfo.GetVersionInfo(a.Location); if (fileVersion.FileBuildPart > 0) sVersion += " (Patch " + fileVersion.FileBuildPart.ToString() + ")\r\n"; sVersion += "Copyright 2000-2022 Ground Zero Tech-Works Inc.\r\n"; MessageBox.Show(sVersion, "About"); } private void onlineManualToolStripMenuItem_Click(object sender, EventArgs e) { util.OpenWebURL("https://ayanova.com/qbi/docs"); } private async void refreshCachedDataToolStripMenuItem_Click(object sender, EventArgs e) { await util.PopulateQBListCache(); await util.PopulateAyaListCache(); } private async void invoiceDescriptiveTextTemplateToolStripMenuItem_Click(object sender, EventArgs e) { InvoiceTemplateBuilder b = new InvoiceTemplateBuilder(); b.ShowDialog(); if (util.QDat.IsDirty) await util.SaveIntegrationObject(); b.Dispose(); } #region WORKORDERS / INVOICING private async void multipleWorkordersPerInvoiceToolStripMenuItem_Click(object sender, EventArgs e) => await InvoiceSelected(false); private async void oneWorkOrderPerInvoiceToolStripMenuItem_Click(object sender, EventArgs e) => await InvoiceSelected(true); private async void fixProblemsToolStripMenuItem_Click(object sender, EventArgs e) { FixInvoiceProblems d = new FixInvoiceProblems(); d.MisMatches = _MisMatches; d.PartPriceOverrides = _PartPriceOverrides; d.ShowDialog(); if (d.ChangesMade) await InitInvoices(); d.Dispose(); } /// /// Adjusts main form display to either show a list of billable workorders /// or a status indicating there are none and why /// private async Task SetState() { fixProblemsToolStripMenuItem.Enabled = _MisMatches.Count > 0; if (grid.Rows.Count > 0) { grid.Visible = true; 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 != 0) { sb.Append(" - \"Work order status\" field set to: "); var r = await util.GetAsync($"work-order-status/{util.QDat.PreWOStatus}"); var status = r.ObjectResponse["data"].ToObject(); if (status != null) sb.Append(status.Name); else sb.Append("UNKNOWN / MISSING STATUS - recently deleted?"); sb.Append("\r\n"); sb.Append(" (You can change this status under Tools->Preferences in the menu)"); } else sb.Append(" - any \"Locked\" type of Work order status set\r\n"); lblStatus.Text = sb.ToString(); grid.Visible = false; lblStatus.Visible = true; } } private DataTable _GridTable = null; private List _MisMatches = new List(); private List _PartPriceOverrides = new List(); private List _GridTableSourceList = new List(); /// /// Initialize invoices dataset /// from scratch. If a previous /// initialize was done, wipe it and /// repopulate from scratch /// private async Task InitInvoices() { Waiting w = new Waiting(); w.Show(); w.Ops = "Validating invoices..."; try { if (_GridTable == null) { //datatable is required to support sorting in dgv without writing a lot of extra code _GridTable = new DataTable(); _GridTable.Columns.Add(new DataColumn("CustomerName")); _GridTable.Columns.Add(new DataColumn("Serial", typeof(long))); _GridTable.Columns.Add(new DataColumn("ServiceDate", typeof(DateTime))); _GridTable.Columns.Add(new DataColumn("WorkorderStatusName")); _GridTable.Columns.Add(new DataColumn("ProjectName")); _GridTable.Columns.Add(new DataColumn("Color")); _GridTable.Columns.Add(new DataColumn("CustomerId", typeof(long))); _GridTable.Columns.Add(new DataColumn("Id", typeof(long))); _GridTable.Columns.Add(new DataColumn("Linked", typeof(bool))); } else { _GridTable.Clear(); } _MisMatches.Clear(); _GridTableSourceList.Clear(); //[HttpGet("accounting-list-billable/{workOrderStatusId}")] var r = await util.GetAsync($"workorder/accounting-list-billable/{util.QDat.PreWOStatus}"); _GridTableSourceList = r.ObjectResponse["data"].ToObject>(); foreach (WorkOrderAccountingListItem z in _GridTableSourceList) { w.Step = "WO: " + z.Serial; DataRow row = _GridTable.NewRow(); row["CustomerName"] = z.CustomerName; row["Serial"] = z.Serial; if (z.ServiceDate != null) row["ServiceDate"] = ((DateTime)z.ServiceDate).ToLocalTime(); row["WorkorderStatusName"] = z.WorkorderStatusName; row["ProjectName"] = z.ProjectName; row["Color"] = z.Color; row["CustomerId"] = z.CustomerId; row["Id"] = z.Id; row["Linked"] = await util.ScanLinksOK(z.Id, _MisMatches, _PartPriceOverrides); _GridTable.Rows.Add(row); } //var bindingList = new BindingList(_GridTable); //var source = new BindingSource(bindingList, null); //grid.DataSource = source; grid.DataSource = _GridTable; grid.ClearSelection(); } finally { w.Close(); } await SetState(); } /// /// Invoice selected items /// private async Task InvoiceSelected(bool bOneWoPerInvoice) { if (grid.SelectedRows.Count < 1) { MessageBox.Show("There are no Work order rows selected\r\nSelect one or more rows to invoice"); return; } //build a list of selected work orders ordered by customer and that have linked=true to process here so that they are in customer order and only good ones that can be billed List InvoiceableWorkOrdersList = new List(); foreach (DataGridViewRow r in grid.SelectedRows) { if ((bool)r.Cells["Linked"].Value == true) { InvoiceableWorkOrdersList.Add(_GridTableSourceList.First(z => z.Id == (long)r.Cells["id"].Value)); } } if (InvoiceableWorkOrdersList.Count < 1) { MessageBox.Show("There are no invoiceable Work order rows selected\r\nSelect one or more rows to invoice"); return; } if (MessageBox.Show("Invoice the selected AyaNova Work orders into QuickBooks.\r\n\r\nAre you sure?", "Invoice AyaNova Work orders", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) return; //We now have a list of work orders from grid selections ready to be invoiced and ordered by customer for grouping purposes //An array list to hold the guid's of workorders being invoiced List al = new List(); Cursor.Current = Cursors.WaitCursor; ArrayList alErrors = new ArrayList(); Waiting w = new Waiting(); w.Show(); w.Ops = "Invoicing to QuickBooks..."; if (bOneWoPerInvoice) { foreach (var invwo in InvoiceableWorkOrdersList) { al.Add(invwo.Id); w.Step = "Invoicing single workorder "; await util.Invoice(al, alErrors); al.Clear(); } } else { //Invoice grouped by customer var grouped = InvoiceableWorkOrdersList.GroupBy(z => z.CustomerId, z => z.Id, (Key, g) => new { CustomerId = Key, WorkOrders = g.ToList() }); foreach (var CustomerWoGroup in grouped) { foreach (var woid in CustomerWoGroup.WorkOrders) al.Add(woid); w.Step = $"Batch invoicing {CustomerWoGroup.WorkOrders.Count.ToString()}{((al.Count > 1) ? " Work Orders" : " Work Order")}"; await util.Invoice(al, alErrors); al.Clear(); } } w.Close(); //display errors if any if (alErrors.Count != 0) { StringBuilder sb = new StringBuilder(); sb.Append("Invoicing completed with some errors:\r\n\r\n"); foreach (object o in alErrors) { sb.Append((string)o); sb.Append("\r\n************\r\n"); } CopyableMessageBox cb = new CopyableMessageBox(sb.ToString()); cb.ShowDialog(); } //refresh display await InitInvoices(); } #endregion workorders / invoicing }//eoc }//eons