From 155be2f03f966075947c1c4325d44885d14eba16 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Mon, 23 Aug 2021 17:51:22 +0000 Subject: [PATCH] --- server/AyaNova/biz/WorkOrderBiz.cs | 180 ++++++++++++++++++++--------- server/AyaNova/resource/de.json | 3 +- server/AyaNova/resource/en.json | 3 +- server/AyaNova/resource/es.json | 3 +- server/AyaNova/resource/fr.json | 3 +- 5 files changed, 133 insertions(+), 59 deletions(-) diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index f6143cbf..5edd83e6 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -26,6 +26,7 @@ namespace AyaNova.Biz CurrentUserType = currentUserType; //Sub-role rights flags + UserHasInventoryFullRole = CurrentUserRoles.HasFlag(AuthorizationRoles.Inventory); UserIsTechRestricted = CurrentUserRoles.HasFlag(AuthorizationRoles.TechRestricted); UserIsSubContractorFull = CurrentUserType == UserType.ServiceContractor && CurrentUserRoles.HasFlag(AuthorizationRoles.SubContractor); UserIsSubContractorRestricted = CurrentUserType == UserType.ServiceContractor && CurrentUserRoles.HasFlag(AuthorizationRoles.SubContractorRestricted); @@ -88,6 +89,7 @@ namespace AyaNova.Biz internal bool UserCanViewPartCosts { get; set; } internal bool UserCanViewLaborOrTravelRateCosts { get; set; } internal bool UserCanViewLoanerCosts { get; set; } + internal bool UserHasInventoryFullRole { get; set; } @@ -145,18 +147,21 @@ namespace AyaNova.Biz await ct.SaveChangesAsync(); //INVENTORY ADJUSTMENTS - foreach (WorkOrderItem wi in newObject.Items) + if (AyaNova.Util.ServerGlobalBizSettings.Cache.UseInventory) { - foreach (WorkOrderItemPart wip in wi.Parts) + foreach (WorkOrderItem wi in newObject.Items) { - await PartInventoryAdjustmentAsync(AyaEvent.Created, wip, null, transaction); - if (HasErrors) + foreach (WorkOrderItemPart wip in wi.Parts) { - await transaction.RollbackAsync(); - return null; + await PartInventoryAdjustmentAsync(AyaEvent.Created, wip, null, transaction); + if (HasErrors) + { + await transaction.RollbackAsync(); + return null; + } } - } + } } //NOTE: not running individual notification here for children, seeder won't require it and that's all that posts an entire wo currently @@ -3694,7 +3699,8 @@ namespace AyaNova.Biz await ct.WorkOrderItemPart.AddAsync(newObject); await ct.SaveChangesAsync(); - await PartInventoryAdjustmentAsync(AyaEvent.Created, newObject, null, transaction); + if (AyaNova.Util.ServerGlobalBizSettings.Cache.UseInventory) + await PartInventoryAdjustmentAsync(AyaEvent.Created, newObject, null, transaction); if (HasErrors) { await transaction.RollbackAsync(); @@ -3750,7 +3756,8 @@ namespace AyaNova.Biz try { await ct.SaveChangesAsync(); - await PartInventoryAdjustmentAsync(AyaEvent.Modified, putObject, dbObject, transaction); + if (AyaNova.Util.ServerGlobalBizSettings.Cache.UseInventory) + await PartInventoryAdjustmentAsync(AyaEvent.Modified, putObject, dbObject, transaction); if (HasErrors) { await transaction.RollbackAsync(); @@ -3787,7 +3794,8 @@ namespace AyaNova.Biz await PartBizActionsAsync(AyaEvent.Deleted, null, dbObject); ct.WorkOrderItemPart.Remove(dbObject); await ct.SaveChangesAsync(); - await PartInventoryAdjustmentAsync(AyaEvent.Deleted, null, dbObject, transaction); + if (AyaNova.Util.ServerGlobalBizSettings.Cache.UseInventory) + await PartInventoryAdjustmentAsync(AyaEvent.Deleted, null, dbObject, transaction); if (HasErrors) { await transaction.RollbackAsync(); @@ -4001,64 +4009,126 @@ namespace AyaNova.Biz { PartInventoryBiz pib = new PartInventoryBiz(ct, UserId, UserTranslationId, CurrentUserRoles); - //DELETED, HANDLE INVENTORY / RETURN SERIALS + //DELETED, HANDLE INVENTORY / SERIALS if (ayaEvent == AyaEvent.Deleted && oldObj.Quantity != 0) { - dtInternalPartInventory pi = - new dtInternalPartInventory - { - PartId = oldObj.PartId, - PartWarehouseId = oldObj.PartWarehouseId, - Quantity = oldObj.Quantity, - SourceType = null,//null because the po no longer exists so this is technically a manual adjustment - SourceId = null,//'' - Description = await Translate("WorkOrderItemPart") + $" {oldObj.Serials} " + await Translate("EventDeleted") - }; - if (await pib.CreateAsync(pi) == null) + //NEGATIVE BLOCK >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + //was originally a return of inventory so needs reversal + if (oldObj.Quantity < 0) { - AddError(ApiErrorCode.API_SERVER_ERROR, "generalerror", $"Error updating inventory ({pi.Description}):{pib.GetErrorsAsString()}"); - return; + dtInternalPartInventory pi = + new dtInternalPartInventory + { + PartId = oldObj.PartId, + PartWarehouseId = oldObj.PartWarehouseId, + Quantity = oldObj.Quantity * -1,//was originally returned so needs to be removed + SourceType = AyaType.WorkOrderItemPart, + SourceId = oldObj.Id, + Description = await Translate("WorkOrderItemPart") + $" {oldObj.Serials} " + await Translate("EventDeleted") + }; + if (await pib.CreateAsync(pi) == null) + { + if (pib.HasErrors) + { + foreach (var e in pib.Errors) + { + if (e.Code == ApiErrorCode.INSUFFICIENT_INVENTORY) + AddError(e.Code, "Quantity", e.Message); + else + AddError(e.Code, e.Target, e.Message); + } + } + return; + } + else + { //Consume serial numbers from part + if (!string.IsNullOrWhiteSpace(oldObj.Serials)) + await PartBiz.RemoveSerialsAsync(oldObj.PartId, oldObj.Serials, ct, UserId); + } + //<<<<<<<<<<<<<<<<<<<<<< NEGATIVE BLOCK } else - { //return serial numbers to part - if (!string.IsNullOrWhiteSpace(oldObj.Serials)) - await PartBiz.AppendSerialsAsync(oldObj.PartId, oldObj.Serials, ct, UserId); + { + //was consumed inventory so return it + dtInternalPartInventory pi = + new dtInternalPartInventory + { + PartId = oldObj.PartId, + PartWarehouseId = oldObj.PartWarehouseId, + Quantity = oldObj.Quantity, + SourceType = null,//null because the po no longer exists so this is technically a manual adjustment + SourceId = null,//'' + Description = await Translate("WorkOrderItemPart") + $" {oldObj.Serials} " + await Translate("EventDeleted") + }; + if (await pib.CreateAsync(pi) == null) + { + AddError(ApiErrorCode.API_SERVER_ERROR, "generalerror", $"Error updating inventory ({pi.Description}):{pib.GetErrorsAsString()}"); + return; + } + else + { //return serial numbers to part + if (!string.IsNullOrWhiteSpace(oldObj.Serials)) + await PartBiz.AppendSerialsAsync(oldObj.PartId, oldObj.Serials, ct, UserId); + } } + } //CREATED, HANDLE INVENTORY / CONSUME SERIALS if (ayaEvent == AyaEvent.Created && newObj.Quantity != 0)//allow zero quantity parts on workorder as placeholder, serials will not be consumed { - dtInternalPartInventory pi = - new dtInternalPartInventory - { - PartId = newObj.PartId, - PartWarehouseId = newObj.PartWarehouseId, - Quantity = newObj.Quantity * -1, - SourceType = AyaType.WorkOrderItemPart, - SourceId = newObj.Id, - Description = await Translate("WorkOrderItemPart") + $" {newObj.Serials} " + await Translate("EventCreated") - }; - if (await pib.CreateAsync(pi) == null) + + if (newObj.Quantity < 0) { - if (pib.HasErrors) - { - foreach (var e in pib.Errors) - { - if (e.Code == ApiErrorCode.INSUFFICIENT_INVENTORY) - AddError(e.Code, "Quantity", e.Message); - else - AddError(e.Code, e.Target, e.Message); - } - } - //AddError(ApiErrorCode.API_SERVER_ERROR, "generalerror", $"Error updating inventory ({pi.Description}):{pib.GetErrorsAsString()}"); - return; + //NEGATIVE BLOCK >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + //RETURN INVENTORY + dtInternalPartInventory pi = + new dtInternalPartInventory + { + PartId = newObj.PartId, + PartWarehouseId = newObj.PartWarehouseId, + Quantity = newObj.Quantity * -1, + SourceType = AyaType.WorkOrderItemPart, + SourceId = newObj.Id, + Description = await Translate("WorkOrderItemPart") + $" {newObj.Serials} " + await Translate("EventCreated") + }; + + //<<<<<<<<<<<<<<<<<<<<<< NEGATIVE BLOCK } else - { //Consume serial numbers from part - if (!string.IsNullOrWhiteSpace(newObj.Serials)) - await PartBiz.RemoveSerialsAsync(newObj.PartId, newObj.Serials, ct, UserId); + { + //CONSUME INVENTORY + dtInternalPartInventory pi = + new dtInternalPartInventory + { + PartId = newObj.PartId, + PartWarehouseId = newObj.PartWarehouseId, + Quantity = newObj.Quantity * -1, + SourceType = AyaType.WorkOrderItemPart, + SourceId = newObj.Id, + Description = await Translate("WorkOrderItemPart") + $" {newObj.Serials} " + await Translate("EventCreated") + }; + if (await pib.CreateAsync(pi) == null) + { + if (pib.HasErrors) + { + foreach (var e in pib.Errors) + { + if (e.Code == ApiErrorCode.INSUFFICIENT_INVENTORY) + AddError(e.Code, "Quantity", e.Message); + else + AddError(e.Code, e.Target, e.Message); + } + } + return; + } + else + { //Consume serial numbers from part + if (!string.IsNullOrWhiteSpace(newObj.Serials)) + await PartBiz.RemoveSerialsAsync(newObj.PartId, newObj.Serials, ct, UserId); + } } } @@ -4183,8 +4253,8 @@ namespace AyaNova.Biz return; } - if (proposedObj.Quantity < 0)//negative quantities are not allowed - AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Quantity"); + if (proposedObj.Quantity < 0 && !UserHasInventoryFullRole)//negative quantities are not allowed unless the user has inventory full role + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Quantity", await Translate("InventoryRoleRequired")); //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemPart.ToString()); diff --git a/server/AyaNova/resource/de.json b/server/AyaNova/resource/de.json index 20da4c40..b5122888 100644 --- a/server/AyaNova/resource/de.json +++ b/server/AyaNova/resource/de.json @@ -2301,5 +2301,6 @@ "ExcludeDaysOfWeek":"Wochentage ausschließen", "ErrorGenBeforeTooSmall":"Muss kleiner sein als Wiederholungsintervall", "ErrorRepeatIntervalTooSmall":"Mindestens eine Stunde", - "QuantityRequired":"Erforderliche Menge" + "QuantityRequired":"Erforderliche Menge", + "InventoryRoleRequired":"Der Benutzer muss für diesen Vorgang eine Inventarrolle haben" } \ No newline at end of file diff --git a/server/AyaNova/resource/en.json b/server/AyaNova/resource/en.json index cb478e44..caa0afd9 100644 --- a/server/AyaNova/resource/en.json +++ b/server/AyaNova/resource/en.json @@ -2301,6 +2301,7 @@ "ExcludeDaysOfWeek":"Exclude days of week", "ErrorGenBeforeTooSmall":"Must be smaller than Repeat interval", "ErrorRepeatIntervalTooSmall":"Minimum one hour", - "QuantityRequired":"Quantity required" + "QuantityRequired":"Quantity required", + "InventoryRoleRequired":"User must have Inventory Role for this operation" } \ No newline at end of file diff --git a/server/AyaNova/resource/es.json b/server/AyaNova/resource/es.json index 4b7c1581..20bee289 100644 --- a/server/AyaNova/resource/es.json +++ b/server/AyaNova/resource/es.json @@ -2301,5 +2301,6 @@ "ExcludeDaysOfWeek":"Excluir días de la semana", "ErrorGenBeforeTooSmall":"Debe ser menor que el intervalo de repetición", "ErrorRepeatIntervalTooSmall":"Mínimo una hora", - "QuantityRequired":"Cantidad requerida" + "QuantityRequired":"Cantidad requerida", + "InventoryRoleRequired":"El usuario debe tener un rol de inventario para esta operación" } \ No newline at end of file diff --git a/server/AyaNova/resource/fr.json b/server/AyaNova/resource/fr.json index 4ea8f699..3349fc8d 100644 --- a/server/AyaNova/resource/fr.json +++ b/server/AyaNova/resource/fr.json @@ -2301,5 +2301,6 @@ "ExcludeDaysOfWeek":"Exclure les jours de la semaine", "ErrorGenBeforeTooSmall":"Doit être inférieur à l'intervalle de répétition", "ErrorRepeatIntervalTooSmall":"Au moins une heure", - "QuantityRequired":"Quantité Nécessaire" + "QuantityRequired":"Quantité Nécessaire", + "InventoryRoleRequired":"L'utilisateur doit avoir un rôle d'inventaire pour cette opération" } \ No newline at end of file