This commit is contained in:
2022-07-07 23:50:30 +00:00
parent 159835116c
commit 7d68f1126c
20 changed files with 1107 additions and 25 deletions

View File

@@ -223,6 +223,14 @@
<Compile Include="Waiting.Designer.cs">
<DependentUpon>Waiting.cs</DependentUpon>
</Compile>
<Compile Include="WorkOrder.cs" />
<Compile Include="WorkOrderItem.cs" />
<Compile Include="WorkOrderItemExpense.cs" />
<Compile Include="WorkOrderItemLabor.cs" />
<Compile Include="WorkOrderItemLoan.cs" />
<Compile Include="WorkOrderItemOutsideService.cs" />
<Compile Include="WorkOrderItemPart.cs" />
<Compile Include="WorkOrderItemTravel.cs" />
<Compile Include="WorkOrderStatus.cs" />
<Compile Include="SetQBImportInventoryAccounts.cs">
<SubType>Form</SubType>

View File

@@ -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();
}
}
@@ -154,7 +162,7 @@ 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();
@@ -163,5 +171,215 @@ namespace AyaNovaQBI
b.Dispose();
}
}
}
#region Main workorder grid
/// <summary>
/// Adjusts main form display to either show a list of billable workorders
/// or a status indicating there are none and why
/// </summary>
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();
/// <summary>
/// Initialize invoices dataset
/// from scratch. If a previous
/// initialize was done, wipe it and
/// repopulate from scratch
/// </summary>
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();
}
/// <summary>
/// Helper for grouping workorders by client
/// </summary>
/// <param name="ClientID"></param>
/// <returns>null if not found else datarow containing invoice for client</returns>
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

131
AyaNovaQBI/WorkOrder.cs Normal file
View File

@@ -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<string> Tags { get; set; } = new List<string>();
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<WorkOrderItem> Items { get; set; } = new List<WorkOrderItem>();
public List<WorkOrderState> States { get; set; } = new List<WorkOrderState>();
//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; }
}
}

View File

@@ -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<string> Tags { get; set; } = new List<string>();
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<WorkOrderItemExpense> Expenses { get; set; } = new List<WorkOrderItemExpense>();
public List<WorkOrderItemLabor> Labors { get; set; } = new List<WorkOrderItemLabor>();
public List<WorkOrderItemLoan> Loans { get; set; } = new List<WorkOrderItemLoan>();
public List<WorkOrderItemPart> Parts { get; set; } = new List<WorkOrderItemPart>();
public List<WorkOrderItemPartRequest> PartRequests { get; set; } = new List<WorkOrderItemPartRequest>();
public List<WorkOrderItemScheduledUser> ScheduledUsers { get; set; } = new List<WorkOrderItemScheduledUser>();
public List<WorkOrderItemTask> Tasks { get; set; } = new List<WorkOrderItemTask>();
public List<WorkOrderItemTravel> Travels { get; set; } = new List<WorkOrderItemTravel>();
public List<WorkOrderItemUnit> Units { get; set; } = new List<WorkOrderItemUnit>();
public List<WorkOrderItemOutsideService> OutsideServices { get; set; } = new List<WorkOrderItemOutsideService>();
}
}

View File

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

View File

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

View File

@@ -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<string> Tags { get; set; } = new List<string>();
public string Name { get; set; }
public long WorkOrderItemId { get; set; }
}
}

View File

@@ -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<string> Tags { get; set; } = new List<string>();
public string Name { get; set; }
public long WorkOrderItemId { get; set; }
}
}

View File

@@ -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<string> Tags { get; set; } = new List<string>();
public string Name { get; set; }
public long WorkOrderItemId { get; set; }
}
}

View File

@@ -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<string> Tags { get; set; } = new List<string>();
public string Name { get; set; }
public long WorkOrderItemId { get; set; }
}
}

View File

@@ -5654,6 +5654,314 @@ namespace AyaNovaQBI
#endregion export to quickbooks
#region Workorder mismatch scanning
public enum MisMatchReason
{
NotLinkedToQB = 0,
PriceDifferent = 1,
NothingToInvoice = 2
}
/// <summary>
/// Mismatch properties
/// A structure for storing mismatches identified
///so user can resolve them.
/// </summary>
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; }
}
/// <summary>
/// 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
/// </summary>
/// <param name="WorkorderID">Id of workorder being scanned</param>
/// <param name="MisMatches">An list of mismatch objects</param>
/// <param name="PriceOverrides">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</param>
/// <returns>True if all links ok, false if there are any mismatches at all</returns>
public static bool ScanLinksOK(long WorkorderID, List<MisMatch> MisMatches, List<long> 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB