diff --git a/AyaNovaQBI/MainForm.cs b/AyaNovaQBI/MainForm.cs index e4ba785..28fb6b7 100644 --- a/AyaNovaQBI/MainForm.cs +++ b/AyaNovaQBI/MainForm.cs @@ -328,7 +328,7 @@ namespace AyaNovaQBI al.Add((Guid)r.Cells["WorkorderID"].Value); w.Step = "Invoicing single workorder "; - Util.Invoice(al, alErrors); + util.Invoice(al, alErrors); continue; diff --git a/AyaNovaQBI/util.cs b/AyaNovaQBI/util.cs index f0c53ed..cb7f4bd 100644 --- a/AyaNovaQBI/util.cs +++ b/AyaNovaQBI/util.cs @@ -6051,6 +6051,733 @@ namespace AyaNovaQBI #endregion wo_mismatch_scan + #region Invoice + + /// + /// Invoice out the workorders contained in the array list + /// as one single invoice + /// + /// Put in descriptive text as necessary and when sucessfully invoiced + /// set workorders in list to status selected for post invoicing and + /// set invoice number and then close them + /// + /// + /// + public static async Task Invoice(List selectedWorkOrderIdList, ArrayList alErrors) + { + + if (selectedWorkOrderIdList.Count == 0) return; + + //Connect to QB + // IY: Create the session manager object using QBFC + QBSessionManager sessionManager = new QBSessionManager(); + + // IY: We want to know if we begun a session so we can end it if an + // error happens + bool booSessionBegun = false; + + + try + { + // IY: Get the RequestMsgSet based on the correct QB Version + IMsgSetRequest requestSet = getLatestMsgSetRequest(sessionManager); + + // IY: Initialize the message set request object + requestSet.Attributes.OnError = ENRqOnError.roeStop; + + // IY: Add the request to the message set request object + //Create invoice + IInvoiceAdd i = requestSet.AppendInvoiceAddRq(); + + //a string to hold the memo field + StringBuilder sbMemo = new StringBuilder(); + if (QDat.SetMemoField) + { + sbMemo.Append("Imported by: " + AyaNovaUserName); + if (selectedWorkOrderIdList.Count > 1) + { + sbMemo.Append(" Workorders: "); + } + else + { + sbMemo.Append(" Workorder: "); + } + } + + + //Keep track if first time through so + //we can set invoice header stuff based on first workorder's client etc + bool bFirstLoop = true; + + //Loop through alworkorders + foreach (long SelectedWorkOrderId in selectedWorkOrderIdList) + { + var woReponse = await GetAsync($"workorder/{SelectedWorkOrderId}"); + WorkOrder w = woReponse.ObjectResponse["data"].ToObject(); + if (w == null) + throw new Exception($"util.Invoice: WorkOrder with record id {SelectedWorkOrderId} was not found in AyaNova and may have just been deleted.\r\nUnable to invoice this wo."); + + + if (bFirstLoop) + { + bFirstLoop = false; + //Set client + i.CustomerRef.ListID.SetValue(QBI.Maps[w.ClientID].ForeignID); + + //Set QB Invoice template + if (!(QVersion < 3))//templates are qbxml 3 or greater + { + if (QDat.QBInvoiceTemplate != null && QDat.QBInvoiceTemplate != "") + i.TemplateRef.ListID.SetValue(QDat.QBInvoiceTemplate); + } + + //Set Class + if (QDat.TransactionClass != null && QDat.TransactionClass != "" && + QDat.TransactionClass != TRANSACTION_CLASS_NO_CLASS_SELECTED)//case 3268 + i.ClassRef.ListID.SetValue(QDat.TransactionClass); + + //Set ToBePrinted + if (QDat.ToBePrinted) + i.IsToBePrinted.SetValue(true); + + } + else + { + //put a comma after the last workorder number that was + //added to the memo string + if (QDat.SetMemoField) + sbMemo.Append(", "); + } + + //if set memo true then build memo string from workorder service numbers etc + if (QDat.SetMemoField) + { + sbMemo.Append(w.WorkorderService.ServiceNumber.ToString()); + + } + + + #region Invoice header text + if (QDat.HasInvoiceHeaderTemplate) + { + string s = QDat.InvoiceHeaderTemplate; + + if (s.IndexOf("~WO#~") != -1) + { + s = s.Replace("~WO#~", w.WorkorderService.ServiceNumber.ToString()); + } + + if (s.IndexOf("~CONTACT~") != -1) + { + s = s.Replace("~CONTACT~", w.CustomerContactName); + } + + if (s.IndexOf("~CREF#~") != -1) + { + s = s.Replace("~CREF#~", w.CustomerReferenceNumber); + } + + if (s.IndexOf("~OURREF#~") != -1) + { + s = s.Replace("~OURREF#~", w.InternalReferenceNumber); + } + + if (s.IndexOf("~PROJ~") != -1) + { + if (w.ProjectID == Guid.Empty) + s = s.Replace("~PROJ~", ""); + else + s = s.Replace("~PROJ~", NameFetcher.GetItem("aProject", "aName", w.ProjectID).RecordName); + } + + if (s.IndexOf("~CLIENT~") != -1) + { + s = s.Replace("~CLIENT~", NameFetcher.GetItem("aClient", "aName", w.ClientID).RecordName); + } + + if (s.IndexOf("~SERVDATE~") != -1) + { + s = s.Replace("~SERVDATE~", w.WorkorderService.ServiceDate.ToString()); + } + + if (s.IndexOf("~STAT~") != -1) + { + if (w.WorkorderService.WorkorderStatusID == Guid.Empty) + s = s.Replace("~STAT~", ""); + else + s = s.Replace("~STAT~", NameFetcher.GetItem("aWorkorderStatus", "aName", w.WorkorderService.WorkorderStatusID).RecordName); + } + + if (s.IndexOf("~DESC~") != -1) + { + s = s.Replace("~DESC~", w.Summary); + } + + InvoiceAddText(i, s); + } + #endregion header text + + #region Part charges + foreach (WorkorderItem it in w.WorkorderItems) + { + foreach (WorkorderItemPart p in it.Parts) + { + //------------DISCOUNT----------------- + //Added:20-July-2006 to incorporate discounts on parts into qb invoice + decimal charge; + + //Case 269 this is incorrect: + //charge = decimal.Round(p.Quantity * p.Price, 2, MidpointRounding.AwayFromZero); + charge = decimal.Round(1 * p.Price, 2, MidpointRounding.AwayFromZero); + + + charge = charge - (decimal.Round(charge * p.Discount, 2, MidpointRounding.AwayFromZero)); + //----------------------------- + + InvoiceAddCharge(i, QBI.Maps[p.PartID].ForeignID, p.Quantity, charge); + string sn = ""; + if (p.PartSerialID != Guid.Empty) + { + sn = PartSerial.GetSerialNumberFromPartSerialID(p.PartSerialID); + if (sn != "") + InvoiceAddText(i, "SN: " + sn); + + } + + //Added:18-Nov-2006 case 125 + //checks for nonempty description, also checks to see if description is + //same as serial number because it's copied there in some cases and no sense + //in showing it twice + if (p.Description != "" && sn != p.Description) + InvoiceAddText(i, p.Description); + + } + } + #endregion part charges + + #region Service charges + foreach (WorkorderItem it in w.WorkorderItems) + { + foreach (WorkorderItemLabor l in it.Labors) + { + //Added 20-July-2006 to not charge for banked hours + if (l.ServiceBankID != Guid.Empty) + { + InvoiceAddCharge(i, QBI.Maps[l.ServiceRateID].ForeignID, l.ServiceRateQuantity, 0); + + } + else + InvoiceAddCharge(i, QBI.Maps[l.ServiceRateID].ForeignID, l.ServiceRateQuantity, + AyaRateList[l.ServiceRateID].Charge); + + } + } + #endregion Service charges + + #region Travel charges + foreach (WorkorderItem it in w.WorkorderItems) + { + foreach (WorkorderItemTravel l in it.Travels) + { + InvoiceAddCharge(i, QBI.Maps[l.TravelRateID].ForeignID, l.TravelRateQuantity, AyaRateList[l.TravelRateID].Charge); + + + } + } + #endregion Travel charges + + #region MiscExpense charges + foreach (WorkorderItem it in w.WorkorderItems) + { + foreach (WorkorderItemMiscExpense l in it.Expenses) + { + if (l.ChargeToClient) + { + InvoiceAddCharge(i, QDat.MiscExpenseChargeAs, 1, l.ChargeAmount); + } + + } + } + #endregion MiscExpense charges + + #region Loaner charges + foreach (WorkorderItem it in w.WorkorderItems) + { + foreach (WorkorderItemLoan l in it.Loans) + { + InvoiceAddCharge(i, QDat.WorkorderItemLoanChargeAs, 1, l.Charges); + + } + } + #endregion Loaner charges + + #region OutsideService charges + foreach (WorkorderItem it in w.WorkorderItems) + { + if (it.HasOutsideService && (it.OutsideService.ShippingPrice != 0 || it.OutsideService.RepairPrice != 0)) + { + InvoiceAddCharge(i, QDat.OutsideServiceChargeAs, 1, it.OutsideService.ShippingPrice + it.OutsideService.RepairPrice); + + } + + } + #endregion OutsideService charges + + //Descriptive footer text + + //Loop through workorder items + //inserting descriptive text as required + if (QDat.HasAnyInvoiceFooterTemplateFields) + { + foreach (WorkorderItem it in w.WorkorderItems) + { + #region Item (footer) fields + if (QDat.InvoiceFooterTemplate != "") + { + string s = QDat.InvoiceFooterTemplate; + + if (s.IndexOf("~ITEM_SUMMARY~") != -1) + { + s = s.Replace("~ITEM_SUMMARY~", it.Summary); + } + + if (s.IndexOf("~ITEM_SERVICE_NOTES~") != -1) + { + s = s.Replace("~ITEM_SERVICE_NOTES~", it.TechNotes); + } + + if (s.IndexOf("~ITEM_TYPE~") != -1) + { + if (it.TypeID == Guid.Empty) + s = s.Replace("~ITEM_TYPE~", ""); + else + s = s.Replace("~ITEM_TYPE~", NameFetcher.GetItem("aWorkorderItemType", "aName", it.TypeID).RecordName); + } + + if (s.IndexOf("~ITEM_REQUEST_DATE~") != -1) + { + s = s.Replace("~ITEM_REQUEST_DATE~", it.RequestDate.ToString()); + } + + if (s.IndexOf("~ITEM_STATUS~") != -1) + { + if (it.WorkorderStatusID == Guid.Empty) + s = s.Replace("~ITEM_STATUS~", ""); + else + s = s.Replace("~ITEM_STATUS~", NameFetcher.GetItem("aWorkorderStatus", "aName", it.WorkorderStatusID).RecordName); + } + + InvoiceAddText(i, s); + } + #endregion item + + #region Unit fields + if (QDat.InvoiceUnitTemplate != "" && it.UnitID != Guid.Empty) + { + string s = QDat.InvoiceUnitTemplate; + + UnitPickList up = UnitPickList.GetListOfOneSpecificUnit(it.UnitID); + + + if (s.IndexOf("~AYAFORMAT~") != -1) + { + s = s.Replace("~AYAFORMAT~", up[0].UnitName()); + } + + + if (s.IndexOf("~UNIT_SN~") != -1) + { + s = s.Replace("~UNIT_SN~", up[0].Serial); + } + + if (s.IndexOf("~UNIT_METER~") != -1) + { + if (!up[0].Metered) + s = s.Replace("~UNIT_METER~", ""); + else + s = s.Replace("~UNIT_METER~", Unit.LastMeterReading(up[0].ID).ToString()); + } + + if (s.IndexOf("~UNIT_MAKE~") != -1) + { + s = s.Replace("~UNIT_MAKE~", up[0].VendorName); + } + + if (s.IndexOf("~UNIT_MODEL_NAME~") != -1) + { + s = s.Replace("~UNIT_MODEL_NAME~", up[0].ModelName); + } + + if (s.IndexOf("~UNIT_MODEL_NUMBER~") != -1) + { + s = s.Replace("~UNIT_MODEL_NUMBER~", up[0].ModelNumber); + } + + InvoiceAddText(i, s); + } + #endregion unit + + #region Labor fields + if (QDat.InvoiceServiceTemplate != "" && it.HasLabor) + { + foreach (WorkorderItemLabor wl in it.Labors) + { + string s = QDat.InvoiceServiceTemplate; + + if (s.IndexOf("~SERVICE_START~") != -1) + { + s = s.Replace("~SERVICE_START~", wl.ServiceStartDate.ToString()); + } + + if (s.IndexOf("~SERVICE_STOP~") != -1) + { + s = s.Replace("~SERVICE_STOP~", wl.ServiceStopDate.ToString()); + } + + if (s.IndexOf("~SERVICE_QUANTITY~") != -1) + { + s = s.Replace("~SERVICE_QUANTITY~", wl.ServiceRateQuantity.ToString()); + } + + + if (s.IndexOf("~NO_CHARGE_QUANTITY~") != -1) + { + s = s.Replace("~NO_CHARGE_QUANTITY~", wl.NoChargeQuantity.ToString()); + } + + if (s.IndexOf("~RATE_NAME~") != -1) + { + s = s.Replace("~RATE_NAME~", AyaRateList[wl.ServiceRateID].Name); + } + + if (s.IndexOf("~SERVICE_TECH~") != -1) + { + s = s.Replace("~SERVICE_TECH~", UserPickList.GetListOfOneSpecificUser(wl.UserID)[0].Name); + } + + if (s.IndexOf("~DETAILS~") != -1) + { + s = s.Replace("~DETAILS~", wl.ServiceDetails); + } + + + + + InvoiceAddText(i, s); + } + } + #endregion service + + #region Travel fields + if (QDat.InvoiceTravelTemplate != "" && it.HasTravel) + { + foreach (WorkorderItemTravel wt in it.Travels) + { + string s = QDat.InvoiceTravelTemplate; + + if (s.IndexOf("~TRAVEL_START~") != -1) + { + s = s.Replace("~TRAVEL_START~", wt.TravelStartDate.ToString()); + } + + if (s.IndexOf("~TRAVEL_STOP~") != -1) + { + s = s.Replace("~TRAVEL_STOP~", wt.TravelStopDate.ToString()); + } + + if (s.IndexOf("~TRAVEL_QUANTITY~") != -1) + { + s = s.Replace("~TRAVEL_QUANTITY~", wt.TravelRateQuantity.ToString()); + } + + if (s.IndexOf("~TRAVEL_NO_CHARGE_QUANTITY~") != -1) + { + s = s.Replace("~TRAVEL_NO_CHARGE_QUANTITY~", wt.NoChargeQuantity.ToString()); + } + + if (s.IndexOf("~TRAVEL_RATE_NAME~") != -1) + { + s = s.Replace("~TRAVEL_RATE_NAME~", AyaRateList[wt.TravelRateID].Name); + } + + if (s.IndexOf("~TRAVEL_TECH~") != -1) + { + s = s.Replace("~TRAVEL_TECH~", UserPickList.GetListOfOneSpecificUser(wt.UserID)[0].Name); + } + + if (s.IndexOf("~TRAVEL_DETAILS~") != -1) + { + s = s.Replace("~TRAVEL_DETAILS~", wt.TravelDetails); + } + + if (s.IndexOf("~TRAVEL_DISTANCE~") != -1) + { + s = s.Replace("~TRAVEL_DISTANCE~", wt.Distance.ToString()); + } + + + InvoiceAddText(i, s); + } + } + #endregion travel fields + + #region Outside fields + if (QDat.OutsideServiceChargeAs != "" && it.HasOutsideService) + { + string s = QDat.InvoiceOutsideServiceTemplate; + + if (s.IndexOf("~REPAIR_PRICE~") != -1) + { + s = s.Replace("~REPAIR_PRICE~", it.OutsideService.RepairPrice.ToString("c")); + } + + if (s.IndexOf("~SHIP_CHARGE~") != -1) + { + s = s.Replace("~SHIP_CHARGE~", it.OutsideService.ShippingPrice.ToString("c")); + } + + if (s.IndexOf("~SENT~") != -1) + { + s = s.Replace("~SENT~", it.OutsideService.DateSent.ToString()); + } + + if (s.IndexOf("~RETURNED~") != -1) + { + s = s.Replace("~RETURNED~", it.OutsideService.DateReturned.ToString()); + } + + if (s.IndexOf("~NOTES~") != -1) + { + s = s.Replace("~NOTES~", it.OutsideService.Notes); + } + + + + InvoiceAddText(i, s); + } + #endregion outside service + + #region Misc expense fields + if (QDat.InvoiceMiscExpenseTemplate != "" && it.HasExpenses) + { + foreach (WorkorderItemMiscExpense e in it.Expenses) + { + string s = QDat.InvoiceMiscExpenseTemplate; + + if (s.IndexOf("~CHARGES~") != -1) + { + s = s.Replace("~CHARGES~", e.ChargeAmount.ToString("c")); + } + + if (s.IndexOf("~SUMMARY~") != -1) + { + s = s.Replace("~SUMMARY~", e.Name); + } + + if (s.IndexOf("~DESCRIPTION~") != -1) + { + s = s.Replace("~DESCRIPTION~", e.Description); + } + + if (s.IndexOf("~TECH~") != -1) + { + s = s.Replace("~TECH~", UserPickList.GetListOfOneSpecificUser(e.UserID)[0].Name); + } + + InvoiceAddText(i, s); + } + } + #endregion misc expense + + #region Loan item fields + if (QDat.InvoiceLoanItemTemplate != "" && it.HasLoans) + { + foreach (WorkorderItemLoan l in it.Loans) + { + string s = QDat.InvoiceLoanItemTemplate; + + if (s.IndexOf("~CHARGE~") != -1) + { + s = s.Replace("~CHARGE~", l.Charges.ToString("c")); + } + + if (s.IndexOf("~ITEM~") != -1) + { + s = s.Replace("~ITEM~", NameFetcher.GetItem("aLoanItem", "aName", l.LoanItemID).RecordName); + } + + if (s.IndexOf("~LOANED~") != -1) + { + s = s.Replace("~LOANED~", l.OutDate.ToString()); + } + + if (s.IndexOf("~LOAN_RETURNED~") != -1) + { + s = s.Replace("~LOAN_RETURNED~", l.ReturnDate.ToString()); + } + + if (s.IndexOf("~LOAN_NOTES~") != -1) + { + s = s.Replace("~LOAN_NOTES~", l.Notes); + } + + InvoiceAddText(i, s); + } + } + #endregion loan item expense + + + + } + + } + + + + } + //Set memo field + if (QDat.SetMemoField) + { + i.Memo.SetValue(T(4094, sbMemo.ToString())); + + } + + + + + //This is intended to be called after already sucessfully connected + //to get version info so no special safety checks here + sessionManager.OpenConnection2("", "AyaNova QBI", ENConnectionType.ctLocalQBDLaunchUI); + sessionManager.BeginSession("", ENOpenMode.omDontCare); + booSessionBegun = true; + + + IMsgSetResponse responseSet = sessionManager.DoRequests(requestSet); + + + IResponse response = responseSet.ResponseList.GetAt(0); + //nonzero indicates an error this is unrecoverable + //so throw an exception + + if (response.StatusCode != 0) + { + throw new ApplicationException(response.StatusMessage + " Code: " + response.StatusCode); + + } + + string InvoiceNumber = ""; + IInvoiceRet ir = (IInvoiceRet)response.Detail; + if (ir.RefNumber != null) + InvoiceNumber = ir.RefNumber.GetValue(); + + // Close the session and connection with QuickBooks + requestSet.ClearRequests(); + sessionManager.EndSession(); + booSessionBegun = false; + sessionManager.CloseConnection(); + + //If Successful post: + //Loop through all workorders again and set their invoice number, status and close them + + //Loop through alworkorders + foreach (object o in selectedWorkOrderIdList) + { + Workorder w = Workorder.GetItem((Guid)o); + if (QDat.PostWOStatus != Guid.Empty) + { + + w.WorkorderService.WorkorderStatusID = QDat.PostWOStatus; + } + + w.WorkorderService.InvoiceNumber = InvoiceNumber; + + //Case 7 + if (QDat.AutoClose) + w.Closed = true; + + w.Save(); + + } + + }//end try block + catch (Exception ex) + { + if (booSessionBegun) + { + sessionManager.EndSession(); + sessionManager.CloseConnection(); + } + + + //crack the exception in case it's a generic dataportal one + //and it is if it's got an inner exception of any kind + if (ex.InnerException != null) ex = ex.InnerException; + alErrors.Add("Invoice: Invoicing failed due to the following error:\r\n" + ex.Message); + + } + + + } + + + + + /// + /// Add text to the invoice + /// chopping into chunks less than the max 4095 characters limit + /// as necessary + /// + private static void InvoiceAddText(IInvoiceAdd i, string Text) + { + if (Text == null || Text == "") return; + + ArrayList lines = new ArrayList(); + + while (Text.Length > 4094) + { + lines.Add(Text.Substring(0, 4094)); + Text = Text.Remove(0, 4094); + } + //Get the last bit + if (Text.Length > 0) + lines.Add(Text); + + + //Loop through lines and add them one at a time to the invoice + foreach (object o in lines) + { + i.ORInvoiceLineAddList.Append().InvoiceLineAdd.Desc.SetValue((string)o); + + } + + + + } + + /// + /// Add charge line to the invoice + /// + /// + private static void InvoiceAddCharge(IInvoiceAdd i, string QBListID, decimal Quantity, decimal rate) + { + + IInvoiceLineAdd line = i.ORInvoiceLineAddList.Append().InvoiceLineAdd;//.InvoiceLineAdd.Desc.SetValue((string)o); + line.ItemRef.ListID.SetValue(QBListID); + line.Quantity.SetValue((double)Quantity); + + //QBFC7 - unified, now seems to use US style for all editions + //if(QCountry!="US") + // line.ORRate.Rate.SetValue(Convert.ToDouble(rate)); + //else + line.ORRatePriceLevel.Rate.SetValue(Convert.ToDouble(rate)); + // + + } + + #endregion + + + #endregion qbi stuff (anything not api) #region general utils