This commit is contained in:
2022-07-11 19:34:46 +00:00
parent cb5d9033af
commit 757372b6cc
2 changed files with 728 additions and 1 deletions

View File

@@ -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;

View File

@@ -6051,6 +6051,733 @@ namespace AyaNovaQBI
#endregion wo_mismatch_scan
#region Invoice
/// <summary>
/// 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
/// </summary>
/// <param name="selectedWorkOrderIdList"></param>
/// <returns></returns>
public static async Task Invoice(List<long> 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<WorkOrder>();
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);
}
}
/// <summary>
/// Add text to the invoice
/// chopping into chunks less than the max 4095 characters limit
/// as necessary
/// </summary>
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);
}
}
/// <summary>
/// Add charge line to the invoice
///
/// </summary>
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