From dc280f41cd8881a92c7459f874e9b520d719ac4e Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Fri, 16 Jul 2021 17:46:50 +0000 Subject: [PATCH] --- server/AyaNova/biz/WorkOrderBiz.cs | 173 ++++++++++++++--------------- 1 file changed, 84 insertions(+), 89 deletions(-) diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index 6b1b9978..d8045a15 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -169,96 +169,47 @@ namespace AyaNova.Biz } } - // //////////////////////////////////////////////////////////////////////////////////////////////// - // //DUPLICATE - // // - // internal async Task WorkOrderDuplicateAsync(long id) - // { - // WorkOrder dbObject = await WorkOrderGetAsync(id, false); - // if (dbObject == null) - // { - // AddError(ApiErrorCode.NOT_FOUND, "id"); - // return null; - // } - // WorkOrder newObject = new WorkOrder(); - // CopyObject.Copy(dbObject, newObject, "Wiki, Serial, States"); - - // //walk the tree and reset all id's and concurrencies - // //TOP - // newObject.Id = 0; - // newObject.Concurrency = 0; - // foreach (var o in newObject.Items) - // { - // o.Id = 0; - // o.Concurrency = 0; - // foreach (var v in o.Expenses) - // { v.Id = 0; v.Concurrency = 0; } - // foreach (var v in o.Labors) - // { v.Id = 0; v.Concurrency = 0; } - // foreach (var v in o.Loans) - // { v.Id = 0; v.Concurrency = 0; } - // foreach (var v in o.OutsideServices) - // { v.Id = 0; v.Concurrency = 0; } - // foreach (var v in o.PartRequests) - // { v.Id = 0; v.Concurrency = 0; } - // foreach (var v in o.Parts) - // { v.Id = 0; v.Concurrency = 0; } - // foreach (var v in o.ScheduledUsers) - // { v.Id = 0; v.Concurrency = 0; } - // foreach (var v in o.Tasks) - // { v.Id = 0; v.Concurrency = 0; } - // foreach (var v in o.Travels) - // { v.Id = 0; v.Concurrency = 0; } - // foreach (var v in o.Units) - // { v.Id = 0; v.Concurrency = 0; } - // } - // await ct.WorkOrder.AddAsync(newObject); - // await ct.SaveChangesAsync(); - // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); - // await WorkOrderSearchIndexAsync(newObject, true); - // await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); - // await WorkOrderPopulateVizFields(newObject, false);//doing this here ahead of notification because notification may require the viz field lookup anyway and afaict no harm in it - // await WorkOrderHandlePotentialNotificationEvent(AyaEvent.Created, newObject); - // return newObject; - // } + //workorder needs to be fetched internally from several places for rule checking etc + //this just gets it raw and lets others process + private async Task WorkOrderGetFullAsync(long id) + { + //https://docs.microsoft.com/en-us/ef/core/querying/related-data + //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections + return await ct.WorkOrder.AsSplitQuery().AsNoTracking() + .Include(s => s.States) + .Include(w => w.Items.OrderBy(item => item.Sequence)) + .ThenInclude(wi => wi.Expenses) + .Include(w => w.Items) + .ThenInclude(wi => wi.Labors) + .Include(w => w.Items) + .ThenInclude(wi => wi.Loans) + .Include(w => w.Items) + .ThenInclude(wi => wi.Parts) + .Include(w => w.Items) + .ThenInclude(wi => wi.PartRequests) + .Include(w => w.Items) + .ThenInclude(wi => wi.ScheduledUsers) + .Include(w => w.Items) + .ThenInclude(wi => wi.Tasks.OrderBy(t => t.Sequence)) + .Include(w => w.Items) + .ThenInclude(wi => wi.Travels) + .Include(w => w.Items) + .ThenInclude(wi => wi.Units) + .Include(w => w.Items) + .ThenInclude(wi => wi.OutsideServices) + .SingleOrDefaultAsync(z => z.Id == id); + } //////////////////////////////////////////////////////////////////////////////////////////////// // GET // internal async Task WorkOrderGetAsync(long id, bool populateDisplayFields, bool logTheGetEvent = true) { - //Note: there could be rules checking here in future, i.e. can only get own workorder or something - //if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now - //https://docs.microsoft.com/en-us/ef/core/querying/related-data - //docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections - var ret = - await ct.WorkOrder.AsSplitQuery().AsNoTracking() - .Include(s => s.States) - .Include(w => w.Items.OrderBy(item => item.Sequence)) - .ThenInclude(wi => wi.Expenses) - .Include(w => w.Items) - .ThenInclude(wi => wi.Labors) - .Include(w => w.Items) - .ThenInclude(wi => wi.Loans) - .Include(w => w.Items) - .ThenInclude(wi => wi.Parts) - .Include(w => w.Items) - .ThenInclude(wi => wi.PartRequests) - .Include(w => w.Items) - .ThenInclude(wi => wi.ScheduledUsers) - .Include(w => w.Items) - .ThenInclude(wi => wi.Tasks.OrderBy(t => t.Sequence)) - .Include(w => w.Items) - .ThenInclude(wi => wi.Travels) - .Include(w => w.Items) - .ThenInclude(wi => wi.Units) - .Include(w => w.Items) - .ThenInclude(wi => wi.OutsideServices) - .SingleOrDefaultAsync(z => z.Id == id); + var ret = await WorkOrderGetFullAsync(id); if (ret != null) { @@ -2652,7 +2603,7 @@ namespace AyaNova.Biz uid: Date.now() //used for */ if (proposedObj.ChargeAmount != 0) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ChargeAmount"); - // if (proposedObj.TaxPaid != 0) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "TaxPaid"); + // if (proposedObj.TaxPaid != 0) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "TaxPaid"); if (proposedObj.ChargeTaxCodeId != null) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ChargeTaxCodeId"); if (proposedObj.ReimburseUser != false) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ReimburseUser"); if (proposedObj.ChargeToCustomer != false) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ChargeToCustomer"); @@ -6007,6 +5958,10 @@ namespace AyaNova.Biz */ #region WorkOrderItemUnit level + + //this is set by validation for further processing later if applicable + private Contract currentUnitContract = null; + //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task UnitExistsAsync(long id) @@ -6019,6 +5974,10 @@ namespace AyaNova.Biz // internal async Task UnitCreateAsync(WorkOrderItemUnit newObject) { + //todo: contract stuff and validation of no other existing contracted unit + //assumptions: this create only gets called if there is an existing woheader saved in all cases + + await UnitValidateAsync(newObject, null); if (HasErrors) return null; @@ -6184,7 +6143,7 @@ namespace AyaNova.Biz //TODO: ADD VALIDATIONS: // - A work order *MUST* have only one Unit with a Contract, if there is already a unit with a contract on this workorder then a new one cannot be added and it will reject with a validation error - + // a unit record is saved only *after* there is already a header (by api users and our client software) so can easily check and set here //run validation and biz rules bool isNew = currentObj == null; @@ -6224,6 +6183,35 @@ namespace AyaNova.Biz + //Contracted unit? Only one per work order is allowed + if (isNew || proposedObj.UnitId != currentObj.UnitId) + { + //See if this unit has a contract, if so then see if the contract is active, if so then iterate workorder graph and check all other units for same + //if any found then reject this + var proposedContractInfo = await ct.Unit.AsNoTracking().Where(x => x.Id == proposedObj.Id).Select(x => new { x.ContractExpires, x.ContractId }).FirstOrDefaultAsync(); + if (proposedContractInfo.ContractId != null && proposedContractInfo.ContractExpires > DateTime.UtcNow) + { + //added woitemunit has a contract and apparently unexpired so need to check if contract is still active + currentUnitContract = await GetFullyPopulatedContractGraphFromIdAsync(proposedContractInfo.ContractId); + if (currentUnitContract != null && currentUnitContract.Active) + { + //iterate work order and check for other contracted unit + var woId = await GetWorkOrderIdFromRelativeAsync(AyaType.WorkOrderItem, proposedObj.WorkOrderItemId, ct); + var w = await WorkOrderGetFullAsync(woId.WorkOrderId); + + //iterate, look for *other* woitemunit records, are they contracted already? + } + else + { + currentUnitContract = null;//just in case it's non active but present so later biz actions don't process it + } + } + + //check if there is another contracted unit already on this work order + } + + + //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemUnit.ToString()); @@ -6433,17 +6421,24 @@ namespace AyaNova.Biz if (id == null) return null; if (mFetchedContractAlready == false) { - mContractInEffect = await ct.Contract.AsSplitQuery().AsNoTracking() - .Include(c => c.ServiceRateItems) - .Include(c => c.TravelRateItems) - .Include(c => c.ContractPartOverrideItems) - .Include(c => c.ContractTravelRateOverrideItems) - .Include(c => c.ContractServiceRateOverrideItems) - .FirstOrDefaultAsync(z => z.Id == id); + mContractInEffect = await GetFullyPopulatedContractGraphFromIdAsync(id); } return mContractInEffect; } + internal async Task GetFullyPopulatedContractGraphFromIdAsync(long? id) + { + if (id == null) return null; + return await ct.Contract.AsSplitQuery().AsNoTracking() + .Include(c => c.ServiceRateItems) + .Include(c => c.TravelRateItems) + .Include(c => c.ContractPartOverrideItems) + .Include(c => c.ContractTravelRateOverrideItems) + .Include(c => c.ContractServiceRateOverrideItems) + .FirstOrDefaultAsync(z => z.Id == id); + + } + //////////////////////////////////////////////////////////////////////////////////////////////// //GET CURRENT STATUS FOR WORKORDER FROM RELATIVE