From 80c59b44c9940ef368dc6b0ffbf4d5eb744ec8b0 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Mon, 23 Aug 2021 18:16:23 +0000 Subject: [PATCH] --- server/AyaNova/biz/WorkOrderBiz.cs | 351 +++++++++++++++-------------- 1 file changed, 187 insertions(+), 164 deletions(-) diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index 5edd83e6..3572a271 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -4005,64 +4005,177 @@ namespace AyaNova.Biz // private async Task PartInventoryAdjustmentAsync(AyaEvent ayaEvent, WorkOrderItemPart newObj, WorkOrderItemPart oldObj, IDbContextTransaction transaction) { - if (AyaNova.Util.ServerGlobalBizSettings.Cache.UseInventory) - { - PartInventoryBiz pib = new PartInventoryBiz(ct, UserId, UserTranslationId, CurrentUserRoles); - //DELETED, HANDLE INVENTORY / SERIALS - if (ayaEvent == AyaEvent.Deleted && oldObj.Quantity != 0) + PartInventoryBiz pib = new PartInventoryBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + + //DELETED, HANDLE INVENTORY / SERIALS + if (ayaEvent == AyaEvent.Deleted && oldObj.Quantity != 0) + { + //NEGATIVE BLOCK >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + //was originally a return of inventory so needs to consume the inventory back again + if (oldObj.Quantity < 0) { - //NEGATIVE BLOCK >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - //was originally a return of inventory so needs reversal - if (oldObj.Quantity < 0) + dtInternalPartInventory pi = + new dtInternalPartInventory + { + PartId = oldObj.PartId, + PartWarehouseId = oldObj.PartWarehouseId, + Quantity = oldObj.Quantity * -1,//was originally returned (negative) so needs to be consumed + SourceType = AyaType.WorkOrderItemPart, + SourceId = oldObj.Id, + Description = await Translate("WorkOrderItemPart") + $" {oldObj.Serials} " + await Translate("EventDeleted") + }; + if (await pib.CreateAsync(pi) == null) { - 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) { - if (pib.HasErrors) + foreach (var e in pib.Errors) { - 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); - } + 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 + return; } else + { //Consume serial numbers from part + if (!string.IsNullOrWhiteSpace(oldObj.Serials)) + await PartBiz.RemoveSerialsAsync(oldObj.PartId, oldObj.Serials, ct, UserId); + } + //<<<<<<<<<<<<<<<<<<<<<< NEGATIVE BLOCK + } + else + { + //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) { - //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") - }; + 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 + { + + if (newObj.Quantity < 0) + { + //NEGATIVE BLOCK >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + //NOTES: new record with negative quantity, this should only be a case where a customer is returning unused parts that were originally consumed by them on a work order + //this already requires the inventory full role during validation so if we're here it's allowed to happen + //in v7 this was openly allowed by anyone who could make a work order but it was a loophole to circumvent inventory adjustment system + //in v8 we allow it only for the narrowly defined pupose of reversing a prior work order consumption of parts but it's not really enforced in code other than requiring a + //inventory full role to do it so that the inventory person is at least involved in what's happening and can think it through + + //RETURN INVENTORY + dtInternalPartInventory pi = + new dtInternalPartInventory + { + PartId = newObj.PartId, + PartWarehouseId = newObj.PartWarehouseId, + Quantity = newObj.Quantity,//is negative + SourceType = AyaType.WorkOrderItemPart, + SourceId = newObj.Id, + Description = await Translate("WorkOrderItemPart") + $" {newObj.Serials} " + await Translate("EventCreated") + }; if (await pib.CreateAsync(pi) == null) + { + if (pib.HasErrors) { - AddError(ApiErrorCode.API_SERVER_ERROR, "generalerror", $"Error updating inventory ({pi.Description}):{pib.GetErrorsAsString()}"); + 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 + { //Append serial numbers from part + if (!string.IsNullOrWhiteSpace(newObj.Serials)) + await PartBiz.AppendSerialsAsync(newObj.PartId, newObj.Serials, ct, UserId); + } + + //<<<<<<<<<<<<<<<<<<<<<< NEGATIVE BLOCK + } + else + { + //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); + } + } + } + + + //UPDATED, HANDLE INVENTORY / UPDATE SERIALS AS REQUIRED + if (ayaEvent == AyaEvent.Modified) + { + //INVENTORY + if (newObj.PartId != oldObj.PartId || newObj.Quantity != oldObj.Quantity) + { + //OUT with the old + if (oldObj.Quantity != 0)//zero quantity doesn't affect inventory or serials + { + dtInternalPartInventory piOld = 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(piOld) == null) + { + AddError(ApiErrorCode.API_SERVER_ERROR, "generalerror", $"Error updating inventory ({piOld.Description}):{pib.GetErrorsAsString()}"); return; } else @@ -4072,56 +4185,22 @@ namespace AyaNova.Biz } } - } - - - //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 - { - - if (newObj.Quantity < 0) - { - //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 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) + //IN with the new + if (newObj.Quantity != 0) + {//NOTE: zero quantity is considered to be a placeholder and no serials will be consumed, nor inventory affected + dtInternalPartInventory piNew = new dtInternalPartInventory { - 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); - } - } + 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(piNew) == null) + { + AddError(ApiErrorCode.API_SERVER_ERROR, "generalerror", $"Error updating inventory ({piNew.Description}):{pib.GetErrorsAsString()}"); return; } else @@ -4131,78 +4210,21 @@ namespace AyaNova.Biz } } } - - - //UPDATED, HANDLE INVENTORY / UPDATE SERIALS AS REQUIRED - if (ayaEvent == AyaEvent.Modified) + //SERIALS + else if (newObj.Serials != oldObj.Serials) { - //INVENTORY - if (newObj.PartId != oldObj.PartId || newObj.Quantity != oldObj.Quantity) - { - //OUT with the old - if (oldObj.Quantity != 0)//zero quantity doesn't affect inventory or serials - { - dtInternalPartInventory piOld = 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(piOld) == null) - { - AddError(ApiErrorCode.API_SERVER_ERROR, "generalerror", $"Error updating inventory ({piOld.Description}):{pib.GetErrorsAsString()}"); - return; - } - else - { //return serial numbers to part - if (!string.IsNullOrWhiteSpace(oldObj.Serials)) - await PartBiz.AppendSerialsAsync(oldObj.PartId, oldObj.Serials, ct, UserId); - } - } + //NOTE: zero quantity is considered to be a placeholder and no serials will be consumed (hence not returned either) - //IN with the new - if (newObj.Quantity != 0) - {//NOTE: zero quantity is considered to be a placeholder and no serials will be consumed, nor inventory affected - dtInternalPartInventory piNew = 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") - }; + //return serial numbers to part + if (oldObj.Quantity != 0 && !string.IsNullOrWhiteSpace(oldObj.Serials)) + await PartBiz.AppendSerialsAsync(oldObj.PartId, oldObj.Serials, ct, UserId); - if (await pib.CreateAsync(piNew) == null) - { - AddError(ApiErrorCode.API_SERVER_ERROR, "generalerror", $"Error updating inventory ({piNew.Description}):{pib.GetErrorsAsString()}"); - return; - } - else - { //Consume serial numbers from part - if (!string.IsNullOrWhiteSpace(newObj.Serials)) - await PartBiz.RemoveSerialsAsync(newObj.PartId, newObj.Serials, ct, UserId); - } - } - } - //SERIALS - else if (newObj.Serials != oldObj.Serials) - { - //NOTE: zero quantity is considered to be a placeholder and no serials will be consumed (hence not returned either) - - //return serial numbers to part - if (oldObj.Quantity != 0 && !string.IsNullOrWhiteSpace(oldObj.Serials)) - await PartBiz.AppendSerialsAsync(oldObj.PartId, oldObj.Serials, ct, UserId); - - //Consume serial numbers from part - if (newObj.Quantity != 0 && !string.IsNullOrWhiteSpace(newObj.Serials)) - await PartBiz.RemoveSerialsAsync(newObj.PartId, newObj.Serials, ct, UserId); - } + //Consume serial numbers from part + if (newObj.Quantity != 0 && !string.IsNullOrWhiteSpace(newObj.Serials)) + await PartBiz.RemoveSerialsAsync(newObj.PartId, newObj.Serials, ct, UserId); } } + } @@ -4253,8 +4275,9 @@ namespace AyaNova.Biz return; } - 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")); + if (AyaNova.Util.ServerGlobalBizSettings.Cache.UseInventory) + 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());