From ae58f45cabbe07cd74505a7b5f45671147f1d3ee Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Mon, 19 Jul 2021 23:05:31 +0000 Subject: [PATCH] --- .vscode/launch.json | 2 +- docs/8.0/ayanova/docs/svc-contracts.md | 6 +- docs/8.0/ayanova/docs/svc-workorders.md | 6 + server/AyaNova/Controllers/UnitController.cs | 34 ++++- server/AyaNova/biz/WorkOrderBiz.cs | 127 ++++++++----------- server/AyaNova/resource/de.json | 3 +- server/AyaNova/resource/en.json | 3 +- server/AyaNova/resource/es.json | 3 +- server/AyaNova/resource/fr.json | 3 +- server/AyaNova/util/Seeder.cs | 6 +- 10 files changed, 110 insertions(+), 83 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index b77f3b95..fcc80a75 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -53,7 +53,7 @@ "AYANOVA_FOLDER_USER_FILES": "c:\\temp\\RavenTestData\\userfiles", "AYANOVA_FOLDER_BACKUP_FILES": "c:\\temp\\RavenTestData\\backupfiles", "AYANOVA_FOLDER_TEMPORARY_SERVER_FILES": "c:\\temp\\RavenTestData\\tempfiles", - "AYANOVA_SERVER_TEST_MODE": "false", + "AYANOVA_SERVER_TEST_MODE": "true", "AYANOVA_SERVER_TEST_MODE_SEEDLEVEL": "small", "AYANOVA_SERVER_TEST_MODE_TZ_OFFSET": "-7", "AYANOVA_BACKUP_PG_DUMP_PATH": "C:\\data\\code\\postgres_13\\bin\\" diff --git a/docs/8.0/ayanova/docs/svc-contracts.md b/docs/8.0/ayanova/docs/svc-contracts.md index d2cec6dc..35cf870d 100644 --- a/docs/8.0/ayanova/docs/svc-contracts.md +++ b/docs/8.0/ayanova/docs/svc-contracts.md @@ -22,9 +22,11 @@ Note that a change of Customer **and** a manual change of Contract on the same s ### Unit contract -When a Unit is selected on a work order and that Unit has a Contract, AyaNova will use that Contract for the entire work order automatically when that work order is first saved with the new Unit selection. +AyaNova does not automatically apply Unit Contracts as there are too many scenarios where it might conflict with other Contracted objects or other Users editing other portions of an existing work order coming into conflict. -Multiple Units with Contracts on the same work order are not permitted. AyaNova would not be able to determine which Contract should take effect when there are multiple Unit's with Contracts on the same work order so for this reason it will not allow a Unit to be added with a Contract if there is already a Unit with a Contract in effect on the Work order. To avoid this scenario only select one Contract holding Unit per work order, use a Customer or HeadOffice Contract instead of a Unit Contract or manually select a Contract instead of using automatic Contract selection if you want full control over what Contract is in effect in order to apply to multiple Units on the same work order. +When a Unit is selected on a work order and that Unit has an **Active and un-expired** Contract **and** that Contract is different from the currently selected Contract in the work order header, AyaNova will prompt the user if they want to use that Contract for the entire work order. If the user chooses to accept it the work order header Contract field will be set to the Unit's Contract. + +Multiple Units with Contracts on the same work order are permitted and not prevented so it's up to the user to ensure the validity of chosen Units / Contract. To avoid a conflicting contract situation only select one Contract holding Unit per work order, use a Customer or HeadOffice Contract instead of a Unit Contract or manually select independant contracts not associated with any other object instead of using automatic Contract selection if you want full control over what Contract is in effect in order to apply to multiple Units on the same work order. ### Contract expiry diff --git a/docs/8.0/ayanova/docs/svc-workorders.md b/docs/8.0/ayanova/docs/svc-workorders.md index f6d7fbb7..3319b226 100644 --- a/docs/8.0/ayanova/docs/svc-workorders.md +++ b/docs/8.0/ayanova/docs/svc-workorders.md @@ -23,6 +23,12 @@ docs / sections required * Work order item Unit - Enter a single Unit only in the work order item units collection if intend to run reports tying other work order item sections to that unit (e.g. labor hours spent on *that* unit, parts used with *that* unit, etc). Entering more than one unit per work order item will make it difficult to match other work order item record types to a particular unit when reporting. +## Add multiple units feature + + * In Work order item Units menu there is an "Add multiple units" option which allows the ability to search for Units by tag and / or restrict to a specific Customer only. This is a quick way to select and add many Units at once to a work order for those situations where that kind of service is common and it would be time consuming to have to manually add each Unit one by one. + + Note that unlike the normal single Unit selection feature which is restricted to the currently selected Customer's own Units only, the add multiple Units feature intentionally allows a User to select Units that do not belong to the currently selected Customer on the work order. This is to support some unusual requirements for certain companies who bulk service Units that are moved around between Customers regularly but are not treated as loaner or rental Units. + ## Misc features of note to document in own sections * Add multiple units (how to select, note that can select by range with shift click for checkboxes etc) diff --git a/server/AyaNova/Controllers/UnitController.cs b/server/AyaNova/Controllers/UnitController.cs index 43342114..dbbfbecb 100644 --- a/server/AyaNova/Controllers/UnitController.cs +++ b/server/AyaNova/Controllers/UnitController.cs @@ -300,7 +300,7 @@ namespace AyaNova.Api.Controllers + $"where aworkorderitemunit.unitid={id} " + "order by aworkorder.serial DESC " + "limit 3"; - using (var cmd = ct.Database.GetDbConnection().CreateCommand()) + using (var cmd = ct.Database.GetDbConnection().CreateCommand()) { await ct.Database.OpenConnectionAsync(); cmd.CommandText = q; @@ -343,5 +343,37 @@ namespace AyaNova.Api.Controllers public string WarrantyTerms { get; set; } } + + /// + /// Get Unit Contract name/id if active and not expired + /// + /// UnitId + /// Contract name and Id + [HttpGet("active-contract/{id}")] + public async Task GetUnitActiveContract([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.Unit)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var ret = new NameIdItem { Name = string.Empty, Id = 0 }; + var UnitContractInfo = await ct.Unit.AsNoTracking().Where(x => x.Id == id).Select(x => new { x.ContractId, x.ContractExpires }).FirstOrDefaultAsync(); + if (UnitContractInfo == null || UnitContractInfo.ContractExpires < DateTime.UtcNow)//none or expired + return Ok(ApiOkResponse.Response(ret)); + + var c = await ct.Contract.AsNoTracking().FirstOrDefaultAsync(x => x.Id == UnitContractInfo.ContractId); + if (c == null || c.Active == false) + return Ok(ApiOkResponse.Response(ret)); + + ret.Name = c.Name; + ret.Id = c.Id; + + return Ok(ApiOkResponse.Response(ret)); + } + + }//eoc }//eons \ No newline at end of file diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index a3607317..98b5495d 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -5939,9 +5939,7 @@ namespace AyaNova.Biz #region WorkOrderItemUnit level - //this is set by validation for further processing if applicable - private long? newOrChangedActiveUnitContractId = null; - private long newOrChangedActiveUnitWorkOrderId = 0; + //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS @@ -5975,14 +5973,6 @@ namespace AyaNova.Biz await UnitHandlePotentialNotificationEvent(AyaEvent.Created, newObject); await UnitPopulateVizFields(newObject); - //update workorder with new contract?? - //Note this is determined if applicable and set during validation - if (newOrChangedActiveUnitContractId != null && newOrChangedActiveUnitWorkOrderId != 0) - { - WorkOrder w = await ct.WorkOrder.AsNoTracking().FirstOrDefaultAsync(z => z.Id == newOrChangedActiveUnitWorkOrderId); - w.ContractId = newOrChangedActiveUnitContractId; - await WorkOrderPutAsync(w); - } return newObject; } } @@ -6042,14 +6032,7 @@ namespace AyaNova.Biz await UnitHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject); await UnitPopulateVizFields(putObject); - //update workorder with new contract?? - //Note this is determined if applicable and set during validation - if (newOrChangedActiveUnitContractId != null && newOrChangedActiveUnitWorkOrderId != 0) - { - WorkOrder w = await ct.WorkOrder.AsNoTracking().FirstOrDefaultAsync(z => z.Id == newOrChangedActiveUnitWorkOrderId); - w.ContractId = newOrChangedActiveUnitContractId; - await WorkOrderPutAsync(w); - } + return putObject; } @@ -6212,60 +6195,60 @@ namespace AyaNova.Biz if (proposedObj.UnitId < 1 || !await ct.Unit.AnyAsync(x => x.Id == proposedObj.UnitId)) AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "UnitId"); - //Contracted unit? Only one per work order is allowed - if (isNew || proposedObj.UnitId != currentObj.UnitId) - { - bool AlreadyHasAContractedUnit = false; - //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 proposedUnitInfo = await ct.Unit.AsNoTracking().Where(x => x.Id == proposedObj.UnitId).Select(x => new { x.ContractExpires, x.ContractId }).FirstOrDefaultAsync(); - if (proposedUnitInfo.ContractId != null && proposedUnitInfo.ContractExpires > DateTime.UtcNow) - { - //added woitemunit has a contract and apparently unexpired so need to check if contract is still active - newOrChangedActiveUnitContractId = proposedUnitInfo.ContractId; - if (await ct.Contract.AsNoTracking().Where(z => z.Id == proposedUnitInfo.ContractId).Select(x => x.Active).FirstOrDefaultAsync() == true) - { - //iterate work order and check for other contracted unit - var woId = await GetWorkOrderIdFromRelativeAsync(AyaType.WorkOrderItem, proposedObj.WorkOrderItemId, ct); - newOrChangedActiveUnitWorkOrderId = woId.WorkOrderId;//save for later contract update if necessary - var w = await WorkOrderGetFullAsync(woId.WorkOrderId); + // //Contracted unit? Only one per work order is allowed + // if (isNew || proposedObj.UnitId != currentObj.UnitId) + // { + // bool AlreadyHasAContractedUnit = false; + // //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 proposedUnitInfo = await ct.Unit.AsNoTracking().Where(x => x.Id == proposedObj.UnitId).Select(x => new { x.ContractExpires, x.ContractId }).FirstOrDefaultAsync(); + // if (proposedUnitInfo.ContractId != null && proposedUnitInfo.ContractExpires > DateTime.UtcNow) + // { + // //added woitemunit has a contract and apparently unexpired so need to check if contract is still active + // newOrChangedActiveUnitContractId = proposedUnitInfo.ContractId; + // if (await ct.Contract.AsNoTracking().Where(z => z.Id == proposedUnitInfo.ContractId).Select(x => x.Active).FirstOrDefaultAsync() == true) + // { + // //iterate work order and check for other contracted unit + // var woId = await GetWorkOrderIdFromRelativeAsync(AyaType.WorkOrderItem, proposedObj.WorkOrderItemId, ct); + // newOrChangedActiveUnitWorkOrderId = woId.WorkOrderId;//save for later contract update if necessary + // var w = await WorkOrderGetFullAsync(woId.WorkOrderId); - //iterate, look for *other* woitemunit records, are they contracted already? - foreach (WorkOrderItem wi in w.Items) - { - if (AlreadyHasAContractedUnit) continue; - foreach (WorkOrderItemUnit wiu in wi.Units) - { - if (isNew || wiu.Id != currentObj.Id) - { - var existingUnitInfo = await ct.Unit.AsNoTracking().Where(x => x.Id == wiu.UnitId).Select(x => new { x.ContractExpires, x.ContractId }).FirstOrDefaultAsync(); - if (existingUnitInfo != null) - { - if (existingUnitInfo.ContractId != null && existingUnitInfo.ContractExpires > DateTime.UtcNow) - { - //Ok, we have a pre-existing contract, is it active? - if (await ct.Contract.AsNoTracking().Where(x => x.Id == existingUnitInfo.ContractId).Select(x => x.Active).FirstOrDefaultAsync()) - { - AlreadyHasAContractedUnit = true; - continue; - } - } - } - } - } - } - if (AlreadyHasAContractedUnit) - { - AddError(ApiErrorCode.VALIDATION_WO_MULTIPLE_CONTRACTED_UNITS, "UnitId"); - return;//this is a completely disqualifying error - } - } - else - { - newOrChangedActiveUnitContractId = null;//just in case it's non active but present so later biz actions don't process it - } - } - } + // //iterate, look for *other* woitemunit records, are they contracted already? + // foreach (WorkOrderItem wi in w.Items) + // { + // if (AlreadyHasAContractedUnit) continue; + // foreach (WorkOrderItemUnit wiu in wi.Units) + // { + // if (isNew || wiu.Id != currentObj.Id) + // { + // var existingUnitInfo = await ct.Unit.AsNoTracking().Where(x => x.Id == wiu.UnitId).Select(x => new { x.ContractExpires, x.ContractId }).FirstOrDefaultAsync(); + // if (existingUnitInfo != null) + // { + // if (existingUnitInfo.ContractId != null && existingUnitInfo.ContractExpires > DateTime.UtcNow) + // { + // //Ok, we have a pre-existing contract, is it active? + // if (await ct.Contract.AsNoTracking().Where(x => x.Id == existingUnitInfo.ContractId).Select(x => x.Active).FirstOrDefaultAsync()) + // { + // AlreadyHasAContractedUnit = true; + // continue; + // } + // } + // } + // } + // } + // } + // if (AlreadyHasAContractedUnit) + // { + // AddError(ApiErrorCode.VALIDATION_WO_MULTIPLE_CONTRACTED_UNITS, "UnitId"); + // return;//this is a completely disqualifying error + // } + // } + // else + // { + // newOrChangedActiveUnitContractId = null;//just in case it's non active but present so later biz actions don't process it + // } + // } + // } diff --git a/server/AyaNova/resource/de.json b/server/AyaNova/resource/de.json index 96b39751..0c780c93 100644 --- a/server/AyaNova/resource/de.json +++ b/server/AyaNova/resource/de.json @@ -2280,5 +2280,6 @@ "UnitWarrantyInfo":"Garantieinformationen", "Warranty":"Garantie", "WarrantyExpires":"Gültig bis", - "RecentWorkOrders":"Letzte Arbeitsaufträge" + "RecentWorkOrders":"Letzte Arbeitsaufträge", + "ApplyUnitContract":"Vertrag '{0}' dieser Einheit auf Arbeitsauftrag anwenden?" } \ No newline at end of file diff --git a/server/AyaNova/resource/en.json b/server/AyaNova/resource/en.json index c4650ec1..ad4fa9b4 100644 --- a/server/AyaNova/resource/en.json +++ b/server/AyaNova/resource/en.json @@ -2280,7 +2280,8 @@ "UnitWarrantyInfo":"Warranty information", "Warranty":"Warranty", "WarrantyExpires":"Valid until", - "RecentWorkOrders":"Recent Work orders" + "RecentWorkOrders":"Recent Work orders", + "ApplyUnitContract":"Apply this Unit's Contract '{0}' to Work order?" } \ No newline at end of file diff --git a/server/AyaNova/resource/es.json b/server/AyaNova/resource/es.json index 59c14a07..0933e262 100644 --- a/server/AyaNova/resource/es.json +++ b/server/AyaNova/resource/es.json @@ -2280,5 +2280,6 @@ "UnitWarrantyInfo":"Información de garantía", "Warranty":"Garantía", "WarrantyExpires":"Válido hasta", - "RecentWorkOrders":"Órdenes de trabajo recientes" + "RecentWorkOrders":"Órdenes de trabajo recientes", + "ApplyUnitContract":"¿Aplicar el contrato '{0}' de esta unidad a la orden de trabajo?" } \ No newline at end of file diff --git a/server/AyaNova/resource/fr.json b/server/AyaNova/resource/fr.json index 1de0ab22..489e068e 100644 --- a/server/AyaNova/resource/fr.json +++ b/server/AyaNova/resource/fr.json @@ -2280,5 +2280,6 @@ "UnitWarrantyInfo":"Informations de garantie", "Warranty":"Garantie", "WarrantyExpires":"Valable jusque", - "RecentWorkOrders":"Ordres de travail récents" + "RecentWorkOrders":"Ordres de travail récents", + "ApplyUnitContract":"Appliquer le contrat '{0}' de cette unité à l'ordre de travail?" } \ No newline at end of file diff --git a/server/AyaNova/util/Seeder.cs b/server/AyaNova/util/Seeder.cs index 5f3c8617..692edd26 100644 --- a/server/AyaNova/util/Seeder.cs +++ b/server/AyaNova/util/Seeder.cs @@ -606,7 +606,7 @@ namespace AyaNova.Util c.Name = "Bronze"; c.Active = true; c.Notes = "These are notes providing additional information when users view the contract"; - c.AlertNotes = "These are alert notes displayed on workorders about this contract"; + c.AlertNotes = "These are alert notes displayed on workorders about this BRONZE contract"; c.PartsOverridePct = 5m; c.PartsOverrideType = ContractOverrideType.PriceDiscount; c.ServiceRatesOverridePct = 5m; @@ -641,7 +641,7 @@ namespace AyaNova.Util c.Name = "Silver"; c.Active = true; c.Notes = "These are notes providing additional information when users view the contract"; - c.AlertNotes = "These are alert notes displayed on workorders about this contract"; + c.AlertNotes = "These are alert notes displayed on workorders about this SILVER contract"; c.PartsOverridePct = 10m; c.PartsOverrideType = ContractOverrideType.PriceDiscount; c.ServiceRatesOverridePct = 10m; @@ -676,7 +676,7 @@ namespace AyaNova.Util c.Name = "Gold"; c.Active = true; c.Notes = "These are notes providing additional information when users view the contract"; - c.AlertNotes = "These are alert notes displayed on workorders about this contract"; + c.AlertNotes = "These are alert notes displayed on workorders about this GOLD contract"; c.PartsOverridePct = 15m; c.PartsOverrideType = ContractOverrideType.PriceDiscount; c.ServiceRatesOverridePct = 15m;