diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index 5814f344..77f77c39 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -2522,11 +2522,150 @@ namespace AyaNova.Biz //////////////////////////////////////////////////////////////////////////////////////////////// //VIZ POPULATE // - private async Task LoanPopulateVizFields(WorkOrderItemLoan o) + private async Task LoanPopulateVizFields(WorkOrderItemLoan o, List loanUnitRateEnumList = null) { - 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 (loanUnitRateEnumList == null) + loanUnitRateEnumList = await AyaNova.Api.Controllers.EnumListController.GetEnumList( + StringUtil.TrimTypeName(typeof(LoanUnitRateUnit).ToString()), + UserTranslationId, + CurrentUserRoles); + o.UnitOfMeasureViz = loanUnitRateEnumList.Where(x => x.Id == (long)o.Rate).Select(x => x.Name).First(); + + LoanUnit loanUnit = await ct.LoanUnit.AsNoTracking().FirstOrDefaultAsync(x => x.Id == o.LoanUnitId); + + TaxCode Tax = null; + if (o.TaxCodeId != null) + Tax = await ct.TaxCode.AsNoTracking().FirstOrDefaultAsync(z => z.Id == o.TaxCodeId); + if (Tax != null) + o.TaxCodeViz = Tax.Name; + + o.PriceViz = 0; + if (loanUnit != null) + { + switch(o.Rate){ + case LoanUnitRateUnit.None: + o.PriceViz=0; + break; + } + } + + //manual price overrides anything + if (o.PriceOverride != null) + o.PriceViz = (decimal)o.PriceOverride; + else + { + //not manual so could potentially have a contract adjustment + var c = await GetCurrentWorkOrderContractFromRelatedAsync(AyaType.WorkOrderItem, o.WorkOrderItemId); + if (c != null) + { + decimal pct = 0; + ContractOverrideType cot = ContractOverrideType.PriceDiscount; + + bool TaggedAdjustmentInEffect = false; + + //POTENTIAL CONTRACT ADJUSTMENTS + //First check if there is a matching tagged contract discount, that takes precedence + if (c.ContractPartOverrideItems.Count > 0) + { + //Iterate all contract tagged items in order of ones with the most tags first + foreach (var cp in c.ContractPartOverrideItems.OrderByDescending(z => z.Tags.Count)) + if (cp.Tags.All(z => part.Tags.Any(x => x == z))) + { + if (cp.OverridePct != 0) + { + pct = cp.OverridePct / 100; + cot = cp.OverrideType; + TaggedAdjustmentInEffect = true; + } + } + } + + //Generic discount? + if (!TaggedAdjustmentInEffect && c.ServiceRatesOverridePct != 0) + { + pct = c.ServiceRatesOverridePct / 100; + cot = c.ServiceRatesOverrideType; + } + + //apply if discount found + if (pct != 0) + { + if (cot == ContractOverrideType.CostMarkup) + o.PriceViz = o.Cost + (o.Cost * pct); + else if (cot == ContractOverrideType.PriceDiscount) + o.PriceViz = o.ListPrice - (o.ListPrice * pct); + } + } + } + + //Calculate totals and taxes + //NET + o.NetViz = o.PriceViz * o.Quantity; + + //TAX + o.TaxAViz = 0; + o.TaxBViz = 0; + if (Tax != null) + { + if (Tax.TaxAPct != 0) + { + o.TaxAViz = o.NetViz * (Tax.TaxAPct / 100); + } + if (Tax.TaxBPct != 0) + { + if (Tax.TaxOnTax) + { + 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; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //BIZ ACTIONS + // + // + private async Task LoanUnitBizActionsAsync(AyaEvent ayaEvent, WorkOrderItemLoan newObj, WorkOrderItemLoan 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; + + //SNAPSHOT PRICING + bool SnapshotPricing = true; + + //if modifed, see what has changed and should be re-applied + if (ayaEvent == AyaEvent.Modified) + { + //If it wasn't a complete part change there is no need to set pricing + if (newObj.LoanUnitId == oldObj.LoanUnitId && newObj.Rate==oldObj.Rate) + { + SnapshotPricing = false; + } + } + + + //Pricing + if (SnapshotPricing) + { + //default in case nothing to apply + newObj.Charges = 0; + + + LoanUnit loanUnit = await ct.LoanUnit.AsNoTracking().FirstOrDefaultAsync(x => x.Id == newObj.LoanUnitId); + if (loanUnit != null) + { + newObj.Charges = s.Cost; + newObj.ListPrice = s.Retail; + } + } } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/models/WorkOrderItemLoan.cs b/server/AyaNova/models/WorkOrderItemLoan.cs index 9925c1fa..f92375de 100644 --- a/server/AyaNova/models/WorkOrderItemLoan.cs +++ b/server/AyaNova/models/WorkOrderItemLoan.cs @@ -15,16 +15,41 @@ namespace AyaNova.Models public DateTime? OutDate { get; set; } public DateTime? DueDate { get; set; } public DateTime? ReturnDate { get; set; } - [Required] - public decimal Charges { get; set; } + // [Required] + // public decimal Charges { get; set; }//removed in favor of ListPRice snapshot which normalizes fields to other objects public long? TaxCodeId { get; set; } + [NotMapped] + public string TaxCodeViz { get; set; } [Required] public long LoanUnitId { get; set; } + [NotMapped] + public string LoanUnitViz { get; set; } [Required] public decimal Quantity { get; set; } [Required] 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 + + + [NotMapped] + public string UnitOfMeasureViz { get; set; }//"each", "hour" etc + [NotMapped] + 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 + [NotMapped] + public decimal NetViz { get; set; }//quantity * price (before taxes line total essentially) + [NotMapped] + public decimal TaxAViz { get; set; }//total amount of taxA + [NotMapped] + public decimal TaxBViz { get; set; }//total amount of taxB + [NotMapped] + public decimal LineTotalViz { get; set; }//line total netViz + taxes + //UTILITY FIELDS [NotMapped] diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 788a2751..dbbfa938 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -724,6 +724,7 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); await ExecQueryAsync("CREATE TABLE aloanunit (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT NOT NULL UNIQUE, active BOOL NOT NULL, " + "notes TEXT, wiki TEXT, customfields TEXT, tags VARCHAR(255) ARRAY, " + "serial TEXT, unitid BIGINT NULL REFERENCES aunit(id), defaultrate INTEGER NOT NULL, " + + "ratehourcost DECIMAL(38,18) NOT NULL DEFAULT 0, ratehalfdaycost DECIMAL(38,18) NOT NULL DEFAULT 0, ratedaycost DECIMAL(38,18) NOT NULL DEFAULT 0, rateweekcost DECIMAL(38,18) NOT NULL DEFAULT 0, ratemonthcost DECIMAL(38,18) NOT NULL DEFAULT 0, rateyearcost DECIMAL(38,18) NOT NULL DEFAULT 0, " + "ratehour DECIMAL(38,18) NOT NULL, ratehalfday DECIMAL(38,18) NOT NULL, rateday DECIMAL(38,18) NOT NULL, rateweek DECIMAL(38,18) NOT NULL, ratemonth DECIMAL(38,18) NOT NULL, rateyear DECIMAL(38,18) NOT NULL " + ")"); @@ -791,8 +792,8 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); //WORKORDERITEM LOAN await ExecQueryAsync("CREATE TABLE aworkorderitemloan (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, workorderitemid BIGINT NOT NULL REFERENCES aworkorderitem (id), " - + "notes TEXT, outdate TIMESTAMP, duedate TIMESTAMP, returndate TIMESTAMP, charges DECIMAL(38,18) NOT NULL default 0, taxcodeid BIGINT REFERENCES ataxcode, " - + "loanunitid BIGINT NOT NULL REFERENCES aloanunit, quantity DECIMAL(19,5) NOT NULL default 0, rate INTEGER NOT NULL" + + "notes TEXT, outdate TIMESTAMP, duedate TIMESTAMP, returndate TIMESTAMP,cost DECIMAL(38,18) NOT NULL default 0, listprice DECIMAL(38,18) NOT NULL default 0, priceoverride DECIMAL(38,18), " + + "taxcodeid BIGINT REFERENCES ataxcode, loanunitid BIGINT NOT NULL REFERENCES aloanunit, quantity DECIMAL(19,5) NOT NULL default 0, rate INTEGER NOT NULL" + ")"); await ExecQueryAsync("ALTER TABLE aloanunit ADD column workorderitemloanid BIGINT NULL REFERENCES aworkorderitemloan");