diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index 7228c954..8c2f01c2 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -2962,7 +2962,7 @@ namespace AyaNova.Biz await PartValidateAsync(putObject, dbObject); if (HasErrors) return null; - // await PartBizActionsAsync(AyaEvent.Modified, putObject, dbObject, null); + // await PartBizActionsAsync(AyaEvent.Modified, putObject, dbObject, null); ct.Replace(dbObject, putObject); try { @@ -3044,7 +3044,7 @@ namespace AyaNova.Biz // private async Task PartPopulateVizFields(WorkOrderItemPart o) { - if (o.PartWarehouseId != 0) + if (o.PartWarehouseId != 0) o.PartWarehouseViz = await ct.PartWarehouse.AsNoTracking().Where(x => x.Id == o.PartWarehouseId).Select(x => x.Name).FirstOrDefaultAsync(); Part part = null; if (o.PartId != 0) @@ -4117,7 +4117,7 @@ namespace AyaNova.Biz return null; else { - await TravelBizActionsAsync(AyaEvent.Created, newObject, null, null); + //await TravelBizActionsAsync(AyaEvent.Created, newObject, null, null); //newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); //newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemTravel.AddAsync(newObject); @@ -4163,7 +4163,7 @@ namespace AyaNova.Biz // dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); await TravelValidateAsync(putObject, dbObject); if (HasErrors) return null; - await TravelBizActionsAsync(AyaEvent.Modified, putObject, dbObject, null); + // await TravelBizActionsAsync(AyaEvent.Modified, putObject, dbObject, null); ct.Replace(dbObject, putObject); try { @@ -4248,103 +4248,195 @@ namespace AyaNova.Biz // private async Task TravelPopulateVizFields(WorkOrderItemTravel o) { - await Task.CompletedTask; - // if (o.WorkOrderOverseerId != null) - // o.WorkOrderOverseerViz = await ct.User.AsNoTracking().Where(x => x.Id == o.WorkOrderOverseerId).Select(x => x.Name).FirstOrDefaultAsync(); - } + if (o.UserId != null) + o.UserViz = await ct.User.AsNoTracking().Where(x => x.Id == o.UserId).Select(x => x.Name).FirstOrDefaultAsync(); + TravelRate Rate = null; + if (o.TravelRateId != null) + Rate = await ct.TravelRate.AsNoTracking().FirstOrDefaultAsync(x => x.Id == o.TravelRateId); + TaxCode Tax = null; + if (o.TaxCodeSaleId != null) + Tax = await ct.TaxCode.AsNoTracking().FirstOrDefaultAsync(z => z.Id == o.TaxCodeSaleId); + if (Tax != null) + o.TaxCodeSaleViz = Tax.Name; - - //////////////////////////////////////////////////////////////////////////////////////////////// - //BIZ ACTIONS - // - // - private async Task TravelBizActionsAsync(AyaEvent ayaEvent, WorkOrderItemTravel newObj, WorkOrderItemTravel oldObj, IDbContextTransaction transaction) - { - //automatic actions on record change, called AFTER validation - - //currently no processing required except for created or modified at this time - if (ayaEvent != AyaEvent.Created && ayaEvent != AyaEvent.Modified) - return; - - //SET TAXES AND PRICING - - //by default apply all automatic actions with further restrictions possible below - bool ApplyTax = true; - bool ApplyPricingUpdate = true; - - //if modifed, see what has changed and should be re-applied - if (ayaEvent == AyaEvent.Modified) + o.PriceViz = 0; + if (Rate != null) { - //If it wasn't a service rate change there is no need to set pricing - if (newObj.TravelRateId == oldObj.TravelRateId) - { - ApplyPricingUpdate = false; - } - //If taxes haven't change then no need to update taxes - if (newObj.TaxCodeSaleId == oldObj.TaxCodeSaleId) - ApplyTax = false; + o.CostViz = Rate.Cost; + o.ListPriceViz = Rate.Charge; + o.UnitOfMeasureViz = Rate.Unit; + o.PriceViz = Rate.Charge;//default price used if not manual or contract override } - //Tax code - if (ApplyTax) + //manual price overrides anything + if (o.PriceOverride != null) + o.PriceViz = (decimal)o.PriceOverride; + else { - //Default in case nothing to apply - newObj.TaxAPct = 0; - newObj.TaxBPct = 0; - newObj.TaxOnTax = false; - - if (newObj.TaxCodeSaleId != null) + //not manual so could potentially have a contract adjustment + var c = await GetCurrentWorkOrderContractFromRelatedAsync(AyaType.WorkOrderItem, o.WorkOrderItemId); + if (c != null) { - var t = await ct.TaxCode.AsNoTracking().FirstOrDefaultAsync(z => z.Id == newObj.TaxCodeSaleId); - if (t != null) + decimal pct = 0; + ContractOverrideType cot = ContractOverrideType.PriceDiscount; + + bool TaggedAdjustmentInEffect = false; + + //POTENTIAL CONTRACT ADJUSTMENTS + //First check if there is a matching tagged Travel rate contract discount, that takes precedence + if (c.ContractTravelRateOverrideItems.Count > 0) { - newObj.TaxAPct = t.TaxAPct; - newObj.TaxBPct = t.TaxBPct; - newObj.TaxOnTax = t.TaxOnTax; + //Iterate all contract tagged items in order of ones with the most tags first + foreach (var csr in c.ContractTravelRateOverrideItems.OrderByDescending(z => z.Tags.Count)) + if (csr.Tags.All(z => Rate.Tags.Any(x => x == z))) + { + if (csr.OverridePct != 0) + { + pct = csr.OverridePct / 100; + cot = csr.OverrideType; + TaggedAdjustmentInEffect = true; + } + } + } + + //Generic discount? + if (!TaggedAdjustmentInEffect && c.TravelRatesOverridePct != 0) + { + pct = c.TravelRatesOverridePct / 100; + cot = c.TravelRatesOverrideType; + } + + //apply if discount found + if (pct != 0) + { + if (cot == ContractOverrideType.CostMarkup) + o.PriceViz = o.CostViz + (o.CostViz * pct); + else if (cot == ContractOverrideType.PriceDiscount) + o.PriceViz = o.ListPriceViz - (o.ListPriceViz * pct); } } } - //Pricing - if (ApplyPricingUpdate) - { - //default in case nothing to apply - newObj.Cost = 0; - newObj.ListPrice = 0; - newObj.Price = 0; + //Calculate totals and taxes + //NET + o.NetViz = o.PriceViz * o.TravelRateQuantity; - //in v7 it was ok to have no service rate selected - //not sure why but carried forward to v8 so.. - if (newObj.TravelRateId != null) + //TAX + o.TaxAViz = 0; + o.TaxBViz = 0; + if (Tax != null) + { + if (Tax.TaxAPct != 0) { - var s = await ct.TravelRate.AsNoTracking().FirstOrDefaultAsync(z => z.Id == newObj.TravelRateId); - if (s != null) + o.TaxAViz = o.NetViz * (Tax.TaxAPct / 100); + } + if (Tax.TaxBPct != 0) + { + if (Tax.TaxOnTax) { - newObj.Cost = s.Cost; - newObj.ListPrice = s.Charge; - var Contract = await GetCurrentWorkOrderContractFromRelatedAsync(AyaType.WorkOrderItem, newObj.WorkOrderItemId); - TravelSetListPrice(newObj, Contract); + o.TaxBViz = (o.NetViz + o.TaxAViz) * (Tax.TaxBPct / 100); + } + else + { + o.TaxBViz = o.NetViz * (Tax.TaxBPct / 100); } } } + o.LineTotalViz = o.NetViz + o.TaxAViz + o.TaxBViz; } - //////////////////////////////////////////////////////////////////////////////////////////////// - // SET PER UNIT LIST PRICE - // - //(called by woitemtravel save and also by header save on change of contract) - private static void TravelSetListPrice(WorkOrderItemTravel o, Contract c) - { - if (c == null || c.ServiceRatesOverridePct == 0) - { - o.Price = o.ListPrice;//default with no contract - return; - } - if (c.ServiceRatesOverrideType == ContractOverrideType.CostMarkup) - o.Price = o.Cost + (o.Cost * c.ServiceRatesOverridePct); - else if (c.ServiceRatesOverrideType == ContractOverrideType.PriceDiscount) - o.Price = o.ListPrice - (o.ListPrice * c.ServiceRatesOverridePct); - } + + // //////////////////////////////////////////////////////////////////////////////////////////////// + // //BIZ ACTIONS + // // + // // + // private async Task TravelBizActionsAsync(AyaEvent ayaEvent, WorkOrderItemTravel newObj, WorkOrderItemTravel oldObj, IDbContextTransaction transaction) + // { + // //automatic actions on record change, called AFTER validation + + // //currently no processing required except for created or modified at this time + // if (ayaEvent != AyaEvent.Created && ayaEvent != AyaEvent.Modified) + // return; + + // //SET TAXES AND PRICING + + // //by default apply all automatic actions with further restrictions possible below + // bool ApplyTax = true; + // bool ApplyPricingUpdate = true; + + // //if modifed, see what has changed and should be re-applied + // if (ayaEvent == AyaEvent.Modified) + // { + // //If it wasn't a service rate change there is no need to set pricing + // if (newObj.TravelRateId == oldObj.TravelRateId) + // { + // ApplyPricingUpdate = false; + // } + // //If taxes haven't change then no need to update taxes + // if (newObj.TaxCodeSaleId == oldObj.TaxCodeSaleId) + // ApplyTax = false; + // } + + // //Tax code + // if (ApplyTax) + // { + // //Default in case nothing to apply + // newObj.TaxAPct = 0; + // newObj.TaxBPct = 0; + // newObj.TaxOnTax = false; + + // if (newObj.TaxCodeSaleId != null) + // { + // var t = await ct.TaxCode.AsNoTracking().FirstOrDefaultAsync(z => z.Id == newObj.TaxCodeSaleId); + // if (t != null) + // { + // newObj.TaxAPct = t.TaxAPct; + // newObj.TaxBPct = t.TaxBPct; + // newObj.TaxOnTax = t.TaxOnTax; + // } + // } + // } + + // //Pricing + // if (ApplyPricingUpdate) + // { + // //default in case nothing to apply + // newObj.Cost = 0; + // newObj.ListPrice = 0; + // newObj.Price = 0; + + // //in v7 it was ok to have no service rate selected + // //not sure why but carried forward to v8 so.. + // if (newObj.TravelRateId != null) + // { + // var s = await ct.TravelRate.AsNoTracking().FirstOrDefaultAsync(z => z.Id == newObj.TravelRateId); + // if (s != null) + // { + // newObj.Cost = s.Cost; + // newObj.ListPrice = s.Charge; + // var Contract = await GetCurrentWorkOrderContractFromRelatedAsync(AyaType.WorkOrderItem, newObj.WorkOrderItemId); + // TravelSetListPrice(newObj, Contract); + // } + // } + // } + // } + + // //////////////////////////////////////////////////////////////////////////////////////////////// + // // SET PER UNIT LIST PRICE + // // + // //(called by woitemtravel save and also by header save on change of contract) + // private static void TravelSetListPrice(WorkOrderItemTravel o, Contract c) + // { + // if (c == null || c.ServiceRatesOverridePct == 0) + // { + // o.Price = o.ListPrice;//default with no contract + // return; + // } + // if (c.ServiceRatesOverrideType == ContractOverrideType.CostMarkup) + // o.Price = o.Cost + (o.Cost * c.ServiceRatesOverridePct); + // else if (c.ServiceRatesOverrideType == ContractOverrideType.PriceDiscount) + // o.Price = o.ListPrice - (o.ListPrice * c.ServiceRatesOverridePct); + // } diff --git a/server/AyaNova/models/WorkOrderItemTravel.cs b/server/AyaNova/models/WorkOrderItemTravel.cs index 2f1b6268..a7c84cc6 100644 --- a/server/AyaNova/models/WorkOrderItemTravel.cs +++ b/server/AyaNova/models/WorkOrderItemTravel.cs @@ -12,17 +12,23 @@ namespace AyaNova.Models public uint Concurrency { get; set; } public long? UserId { get; set; } + [NotMapped] + public string UserViz { get; set; } public DateTime? TravelStartDate { get; set; } public DateTime? TravelStopDate { get; set; } public long? TravelRateId { get; set; } + [NotMapped] + 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; } + [NotMapped] + public string TaxCodeSaleViz { get; set; } public decimal Distance { get; set; } - + // //PRICE FIELDS // [Required] // public decimal Cost { get; set; } @@ -45,7 +51,7 @@ namespace AyaNova.Models // [NotMapped] // public decimal LineTotalViz { get; set; } - //Standard pricing fields (mostly to support printed reports though some show in UI) + //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 [NotMapped]