From ac52aaa5ad5c4ac17af958bed01be09cc8019b39 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Tue, 19 Jan 2021 23:25:48 +0000 Subject: [PATCH] --- .../Controllers/PartWarehouseController.cs | 160 ++++++ .../AyaNova/DataList/PartWarehouseDataList.cs | 96 ++++ .../AyaNova/PickList/PartWarehousePickList.cs | 57 +++ server/AyaNova/PickList/PickListFactory.cs | 2 + server/AyaNova/biz/AyaFormFieldDefinitions.cs | 33 ++ .../AyaNova/biz/BizObjectExistsInDatabase.cs | 2 + server/AyaNova/biz/BizObjectFactory.cs | 2 + server/AyaNova/biz/BizRoles.cs | 16 + server/AyaNova/biz/PartWarehouseBiz.cs | 456 ++++++++++++++++++ server/AyaNova/resource/de.json | 19 +- server/AyaNova/resource/en.json | 17 + server/AyaNova/resource/es.json | 19 +- server/AyaNova/resource/fr.json | 19 +- server/AyaNova/util/AySchema.cs | 1 + 14 files changed, 896 insertions(+), 3 deletions(-) create mode 100644 server/AyaNova/Controllers/PartWarehouseController.cs create mode 100644 server/AyaNova/DataList/PartWarehouseDataList.cs create mode 100644 server/AyaNova/PickList/PartWarehousePickList.cs create mode 100644 server/AyaNova/biz/PartWarehouseBiz.cs diff --git a/server/AyaNova/Controllers/PartWarehouseController.cs b/server/AyaNova/Controllers/PartWarehouseController.cs new file mode 100644 index 00000000..6b08c03c --- /dev/null +++ b/server/AyaNova/Controllers/PartWarehouseController.cs @@ -0,0 +1,160 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using AyaNova.Models; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Biz; + + + +namespace AyaNova.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/project")] + [Produces("application/json")] + [Authorize] + public class PartWarehouseController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public PartWarehouseController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create PartWarehouse + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostPartWarehouse([FromBody] PartWarehouse newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + PartWarehouseBiz biz = PartWarehouseBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + PartWarehouse o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(PartWarehouseController.GetPartWarehouse), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + /// + /// Duplicate PartWarehouse + /// (Wiki and Attachments are not duplicated) + /// + /// Source object id + /// From route path + /// PartWarehouse + [HttpPost("duplicate/{id}")] + public async Task DuplicatePartWarehouse([FromRoute] long id, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + PartWarehouseBiz biz = PartWarehouseBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + PartWarehouse o = await biz.DuplicateAsync(id); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(PartWarehouseController.GetPartWarehouse), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + /// + /// Get PartWarehouse + /// + /// + /// PartWarehouse + [HttpGet("{id}")] + public async Task GetPartWarehouse([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + PartWarehouseBiz biz = PartWarehouseBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + /// + /// Put (update) PartWarehouse + /// + /// + /// + [HttpPut] + public async Task PutPartWarehouse([FromBody] PartWarehouse updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + PartWarehouseBiz biz = PartWarehouseBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject);//In future may need to return entire object, for now just concurrency token + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ; + } + + /// + /// Delete PartWarehouse + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeletePartWarehouse([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + PartWarehouseBiz biz = PartWarehouseBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/AyaNova/DataList/PartWarehouseDataList.cs b/server/AyaNova/DataList/PartWarehouseDataList.cs new file mode 100644 index 00000000..93862ed5 --- /dev/null +++ b/server/AyaNova/DataList/PartWarehouseDataList.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using AyaNova.Biz; +namespace AyaNova.DataList +{ + internal class PartWarehouseDataList : AyaDataList + { + public PartWarehouseDataList() + { + DefaultListObjectType = AyaType.PartWarehouse; + SQLFrom = "from apartwarehouse"; + var RoleSet = BizRoles.GetRoleSet(DefaultListObjectType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + + //######## DEFAULT VIEW WHEN NO VIEW CHOSEN ############ + //Default ListView + dynamic dlistView = new JArray(); + dynamic cm = null; + + cm = new JObject(); + cm.fld = "PartWarehouseName"; + cm.sort = "+"; + dlistView.Add(cm); + + cm = new JObject(); + cm.fld = "Active"; + dlistView.Add(cm); + + cm = new JObject(); + cm.fld = "Tags"; + dlistView.Add(cm); + + DefaultListView = dlistView.ToString(Newtonsoft.Json.Formatting.None); + + + //NOTE: Due to the join, all the sql id and name fields that can conflict with the joined table need to be specified completely + FieldDefinitions = new List(); + + FieldDefinitions.Add(new AyaDataListFieldDefinition + { + TKey = "PartWarehouseName", + FieldKey = "PartWarehouseName", + AyaObjectType = (int)AyaType.PartWarehouse, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "apartwarehouse.id", + SqlValueColumnName = "apartwarehouse.name", + IsRowId = true + }); + + FieldDefinitions.Add(new AyaDataListFieldDefinition + { + TKey = "PartWarehouseNotes", + FieldKey = "PartWarehouseNotes", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "apartwarehouse.notes" + }); + + FieldDefinitions.Add(new AyaDataListFieldDefinition + { + TKey = "Active", + FieldKey = "Active", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "apartwarehouse.active" + }); + + FieldDefinitions.Add(new AyaDataListFieldDefinition + { + TKey = "Tags", + FieldKey = "Tags", + UiFieldDataType = (int)UiFieldDataType.Tags, + SqlValueColumnName = "apartwarehouse.tags" + }); + + + + //----------- + + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom1", FieldKey = "partwarehousecustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom2", FieldKey = "partwarehousecustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom3", FieldKey = "partwarehousecustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom4", FieldKey = "partwarehousecustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom5", FieldKey = "partwarehousecustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom6", FieldKey = "partwarehousecustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom7", FieldKey = "partwarehousecustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom8", FieldKey = "partwarehousecustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom9", FieldKey = "partwarehousecustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom10", FieldKey = "partwarehousecustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom11", FieldKey = "partwarehousecustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom12", FieldKey = "partwarehousecustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom13", FieldKey = "partwarehousecustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom14", FieldKey = "partwarehousecustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom15", FieldKey = "partwarehousecustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + FieldDefinitions.Add(new AyaDataListFieldDefinition { TKey = "PartWarehouseCustom16", FieldKey = "partwarehousecustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "apartwarehouse.customfields" }); + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/AyaNova/PickList/PartWarehousePickList.cs b/server/AyaNova/PickList/PartWarehousePickList.cs new file mode 100644 index 00000000..108c5171 --- /dev/null +++ b/server/AyaNova/PickList/PartWarehousePickList.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using AyaNova.Biz; +namespace AyaNova.PickList +{ + internal class PartWarehousePickList : AyaPickList + { + public PartWarehousePickList() + { + + DefaultListObjectType = AyaType.PartWarehouse; + SQLFrom = "from apartwarehouse"; + AllowedRoles = BizRoles.GetRoleSet(DefaultListObjectType).Select; + dynamic dTemplate = new JArray(); + dynamic cm = null; + + cm = new JObject(); + cm.fld = "PartWarehouseName"; + dTemplate.Add(cm); + + cm = new JObject(); + cm.fld = "Tags"; + dTemplate.Add(cm); + + base.DefaultTemplate = dTemplate.ToString(Newtonsoft.Json.Formatting.None); + + //NOTE: Due to the join, all the sql id and name fields that can conflict with the joined table need to be specified completely + ColumnDefinitions = new List(); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Active", + FieldKey = "Active", + ColumnDataType = UiFieldDataType.Bool, + SqlValueColumnName = "apartwarehouse.active", + IsActiveColumn = true + }); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "PartWarehouseName", + FieldKey = "PartWarehouseName", + ColumnDataType = UiFieldDataType.Text, + SqlIdColumnName = "apartwarehouse.id", + SqlValueColumnName = "apartwarehouse.name", + IsRowId = true + }); + + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Tags", + FieldKey = "Tags", + ColumnDataType = UiFieldDataType.Tags, + SqlValueColumnName = "apartwarehouse.tags" + }); + + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/AyaNova/PickList/PickListFactory.cs b/server/AyaNova/PickList/PickListFactory.cs index f19ae676..1b5213ae 100644 --- a/server/AyaNova/PickList/PickListFactory.cs +++ b/server/AyaNova/PickList/PickListFactory.cs @@ -28,6 +28,8 @@ namespace AyaNova.PickList return new VendorPickList() as IAyaPickList; case AyaType.Part: return new PartPickList() as IAyaPickList; + case AyaType.PartWarehouse: + return new PartWarehousePickList() as IAyaPickList; case AyaType.PartAssembly: return new PartAssemblyPickList() as IAyaPickList; case AyaType.Project: diff --git a/server/AyaNova/biz/AyaFormFieldDefinitions.cs b/server/AyaNova/biz/AyaFormFieldDefinitions.cs index 981b60e3..11a4d603 100644 --- a/server/AyaNova/biz/AyaFormFieldDefinitions.cs +++ b/server/AyaNova/biz/AyaFormFieldDefinitions.cs @@ -499,6 +499,39 @@ namespace AyaNova.Biz } #endregion + #region PartWarehouse + { + + List l = new List(); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseName", FieldKey = "PartWarehouseName", Hideable = false }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseNotes", FieldKey = "PartWarehouseNotes" }); + l.Add(new AyaFormFieldDefinition { TKey = "Active", FieldKey = "Active", Hideable = false }); + l.Add(new AyaFormFieldDefinition { TKey = "Tags", FieldKey = "Tags" }); + l.Add(new AyaFormFieldDefinition { TKey = "Wiki", FieldKey = "Wiki" }); + l.Add(new AyaFormFieldDefinition { TKey = "Attachments", FieldKey = "Attachments" }); + + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom1", FieldKey = "PartWarehouseCustom1", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom2", FieldKey = "PartWarehouseCustom2", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom3", FieldKey = "PartWarehouseCustom3", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom4", FieldKey = "PartWarehouseCustom4", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom5", FieldKey = "PartWarehouseCustom5", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom6", FieldKey = "PartWarehouseCustom6", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom7", FieldKey = "PartWarehouseCustom7", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom8", FieldKey = "PartWarehouseCustom8", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom9", FieldKey = "PartWarehouseCustom9", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom10", FieldKey = "PartWarehouseCustom10", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom11", FieldKey = "PartWarehouseCustom11", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom12", FieldKey = "PartWarehouseCustom12", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom13", FieldKey = "PartWarehouseCustom13", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom14", FieldKey = "PartWarehouseCustom14", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom15", FieldKey = "PartWarehouseCustom15", IsCustomField = true }); + l.Add(new AyaFormFieldDefinition { TKey = "PartWarehouseCustom16", FieldKey = "PartWarehouseCustom16", IsCustomField = true }); + _ayaFormFields.Add(AyaType.PartWarehouse.ToString(), l); + + } + #endregion + + #region PartAssembly { diff --git a/server/AyaNova/biz/BizObjectExistsInDatabase.cs b/server/AyaNova/biz/BizObjectExistsInDatabase.cs index 808be436..4a193481 100644 --- a/server/AyaNova/biz/BizObjectExistsInDatabase.cs +++ b/server/AyaNova/biz/BizObjectExistsInDatabase.cs @@ -48,6 +48,8 @@ namespace AyaNova.Biz return await ct.Memo.AnyAsync(z => z.Id == id); case AyaType.Part: return await ct.Part.AnyAsync(z => z.Id == id); + case AyaType.PartWarehouse: + return await ct.PartWarehouse.AnyAsync(z => z.Id == id); case AyaType.PartAssembly: return await ct.PartAssembly.AnyAsync(z => z.Id == id); case AyaType.PM: diff --git a/server/AyaNova/biz/BizObjectFactory.cs b/server/AyaNova/biz/BizObjectFactory.cs index 842689c1..9ec2372e 100644 --- a/server/AyaNova/biz/BizObjectFactory.cs +++ b/server/AyaNova/biz/BizObjectFactory.cs @@ -49,6 +49,8 @@ namespace AyaNova.Biz return new LoanUnitBiz(ct, userId, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, roles); case AyaType.Part: return new PartBiz(ct, userId, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, roles); + case AyaType.PartWarehouse: + return new PartWarehouseBiz(ct, userId, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, roles); case AyaType.PartAssembly: return new PartAssemblyBiz(ct, userId, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, roles); case AyaType.PM: diff --git a/server/AyaNova/biz/BizRoles.cs b/server/AyaNova/biz/BizRoles.cs index 9755894d..78579d56 100644 --- a/server/AyaNova/biz/BizRoles.cs +++ b/server/AyaNova/biz/BizRoles.cs @@ -146,6 +146,22 @@ namespace AyaNova.Biz }); + + //////////////////////////////////////////////////////////// + //Part + // + roles.Add(AyaType.PartWarehouse, new BizRoleSet() + { + Change = AuthorizationRoles.InventoryFull + | AuthorizationRoles.BizAdminFull + | AuthorizationRoles.AccountingFull, + ReadFullRecord = AuthorizationRoles.DispatchFull + | AuthorizationRoles.InventoryLimited + | AuthorizationRoles.BizAdminLimited + | AuthorizationRoles.DispatchLimited, + Select = AuthorizationRoles.All + }); + //////////////////////////////////////////////////////////// //Part assembly // diff --git a/server/AyaNova/biz/PartWarehouseBiz.cs b/server/AyaNova/biz/PartWarehouseBiz.cs new file mode 100644 index 00000000..55426a1c --- /dev/null +++ b/server/AyaNova/biz/PartWarehouseBiz.cs @@ -0,0 +1,456 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using Microsoft.Extensions.Logging; +using AyaNova.Models; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace AyaNova.Biz +{ + internal class PartWarehouseBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, IImportAbleObject, INotifiableObject + { + internal PartWarehouseBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.PartWarehouse; + } + + internal static PartWarehouseBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new PartWarehouseBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new PartWarehouseBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.PartWarehouse.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(PartWarehouse newObject) + { + await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.PartWarehouse.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + await HandlePotentialNotificationEvent(AyaEvent.Created, newObject); + return newObject; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + internal async Task DuplicateAsync(long id) + { + var dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + PartWarehouse newObject = new PartWarehouse(); + CopyObject.Copy(dbObject, newObject, "Wiki"); + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObject.Name, l++, 255); + NotUnique = await ct.PartWarehouse.AnyAsync(m => m.Name == newUniqueName); + } while (NotUnique); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.PartWarehouse.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + await HandlePotentialNotificationEvent(AyaEvent.Created, newObject); + return newObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.PartWarehouse.AsNoTracking().SingleOrDefaultAsync(m => m.Id == id); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, AyaEvent.Retrieved), ct); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(PartWarehouse putObject) + { + var dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + + putObject.Tags = TagBiz.NormalizeTags(putObject.Tags); + putObject.CustomFields = JsonUtil.CompactJson(putObject.CustomFields); + await ValidateAsync(putObject, dbObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(putObject, false); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + await HandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject); + return dbObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + var dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.PartWarehouse.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); + await transaction.CommitAsync(); + await HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); + } + catch + { + //Just re-throw for now, let exception handler deal, but in future may want to deal with this more here + throw; + } + return true; + } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(PartWarehouse obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await GetAsync(id, false); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(PartWarehouse obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Notes) + .AddText(obj.Name) + .AddText(obj.Wiki) + .AddText(obj.Tags) + .AddCustomFields(obj.CustomFields); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + private async Task ValidateAsync(PartWarehouse proposedObj, PartWarehouse currentObj) + { + + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.PartWarehouse.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.PartWarehouse.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + private void ValidateCanDelete(PartWarehouse inObj) + { + //whatever needs to be check to delete this object + + //Can't delete the default warehouse + if (inObj.Id == 1) + { + AddError(ApiErrorCode.INVALID_OPERATION, "generalerror", "Default part warehouse can not be deleted"); + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORTING + // + public async Task GetReportData(long[] idList) + { + JArray ReportData = new JArray(); + while (idList.Any()) + { + var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); + idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); + //query for this batch, comes back in db natural order unfortunately + var batchResults = await ct.PartWarehouse.AsNoTracking().Where(z => batch.Contains(z.Id)).ToArrayAsync(); + //order the results back into original + var orderedList = from id in batch join z in batchResults on id equals z.Id select z; + foreach (PartWarehouse w in orderedList) + { + var jo = JObject.FromObject(w); + if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"])) + jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); + ReportData.Add(jo); + } + } + return ReportData; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT + // + + + public async Task GetExportData(long[] idList) + { + //for now just re-use the report data code + //this may turn out to be the pattern for most biz object types but keeping it seperate allows for custom usage from time to time + return await GetReportData(idList); + } + + + + + public async Task> ImportData(JArray ja) + { + List ImportResult = new List(); + string ImportTag = $"imported-{FileUtil.GetSafeDateFileName()}"; + + var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new AyaNova.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + foreach (JObject j in ja) + { + var w = j.ToObject(jsset); + if (j["CustomFields"] != null) + w.CustomFields = j["CustomFields"].ToString(); + w.Tags.Add(ImportTag);//so user can find them all and revert later if necessary + var res = await CreateAsync(w); + if (res == null) + { + ImportResult.Add($"* {w.Name} - {this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"{w.Name} - ok"); + } + } + return ImportResult; + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + //Hand off the particular job to the corresponding processing code + //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so + //basically any error condition during job processing should throw up an exception if it can't be handled + switch (job.JobType) + { + case JobType.BatchCoreObjectOperation: + await ProcessBatchJobAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"PartWarehouseBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + } + + + + private async Task ProcessBatchJobAsync(OpsJob job) + { + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob {job.SubType}"); + List idList = new List(); + long FailedObjectCount = 0; + JObject jobData = JObject.Parse(job.JobInfo); + if (jobData.ContainsKey("idList")) + idList = ((JArray)jobData["idList"]).ToObject>(); + else + idList = await ct.PartWarehouse.AsNoTracking().Select(z => z.Id).ToListAsync(); + bool SaveIt = false; + foreach (long id in idList) + { + try + { + SaveIt = false; + ClearErrors(); + PartWarehouse o = null; + //save a fetch if it's a delete + if (job.SubType != JobSubType.Delete) + o = await GetAsync(id, false); + switch (job.SubType) + { + case JobSubType.TagAddAny: + case JobSubType.TagAdd: + case JobSubType.TagRemoveAny: + case JobSubType.TagRemove: + case JobSubType.TagReplaceAny: + case JobSubType.TagReplace: + SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); + break; + case JobSubType.Delete: + if (!await DeleteAsync(id)) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + break; + default: + throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); + } + if (SaveIt) + { + o = await PutAsync(o); + if (o == null) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + } + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors id({id})"); + await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + } + } + await JobsBiz.LogJobAsync(job.GId, $"LT:BatchJob {job.SubType} {idList.Count}{(FailedObjectCount > 0 ? " - LT:Failed " + FailedObjectCount : "")}"); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + } + + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // NOTIFICATION PROCESSING + // + public async Task HandlePotentialNotificationEvent(AyaEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) + { + ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger(); + if (ServerBootConfig.SEEDING) return; + log.LogDebug($"HandlePotentialNotificationEvent processing: [AyaType:{this.BizType}, AyaEvent:{ayaEvent}]"); + + bool isNew = currentObj == null; + + + //STANDARD EVENTS FOR ALL OBJECTS + await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct); + + //SPECIFIC EVENTS FOR THIS OBJECT + + }//end of process notifications + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/resource/de.json b/server/AyaNova/resource/de.json index 78bd4a34..2b7362f7 100644 --- a/server/AyaNova/resource/de.json +++ b/server/AyaNova/resource/de.json @@ -654,7 +654,24 @@ "PartSerialNumbersAvailable": "Verfügbare Seriennummern", "PartWarehouseDescription": "Beschreibung", "PartWarehouseList": "Teilelager", - "PartWarehouseName": "Teilelager - Name", + "PartWarehouseName": "Teilelager - Name", + "PartWarehouseNotes": "Anmerkungen", + "PartWarehouseCustom1": "Angepasstes Feld 1", + "PartWarehouseCustom2": "Angepasstes Feld 2", + "PartWarehouseCustom3": "Angepasstes Feld 3", + "PartWarehouseCustom4": "Angepasstes Feld 4", + "PartWarehouseCustom5": "Angepasstes Feld 5", + "PartWarehouseCustom6": "Angepasstes Feld 6", + "PartWarehouseCustom7": "Angepasstes Feld 7", + "PartWarehouseCustom8": "Angepasstes Feld 8", + "PartWarehouseCustom9": "Angepasstes Feld 9", + "PartWarehouseCustom10": "Angepasstes Feld 10", + "PartWarehouseCustom11": "Angepasstes Feld 11", + "PartWarehouseCustom12": "Angepasstes Feld 12", + "PartWarehouseCustom13": "Angepasstes Feld 13", + "PartWarehouseCustom14": "Angepasstes Feld 14", + "PartWarehouseCustom15": "Angepasstes Feld 15", + "PartWarehouseCustom16": "Angepasstes Feld 16", "PriorityColor": "Farbe", "PriorityList": "Prioritäten", "PriorityName": "Priorität - Name", diff --git a/server/AyaNova/resource/en.json b/server/AyaNova/resource/en.json index c504cf35..a3fe6674 100644 --- a/server/AyaNova/resource/en.json +++ b/server/AyaNova/resource/en.json @@ -655,6 +655,23 @@ "PartWarehouseDescription": "Description", "PartWarehouseList": "Parts Warehouses", "PartWarehouseName": "Part Warehouse Name", + "PartWarehouseNotes": "Notes", + "PartWarehouseCustom1": "Custom1", + "PartWarehouseCustom2": "Custom2", + "PartWarehouseCustom3": "Custom3", + "PartWarehouseCustom4": "Custom4", + "PartWarehouseCustom5": "Custom5", + "PartWarehouseCustom6": "Custom6", + "PartWarehouseCustom7": "Custom7", + "PartWarehouseCustom8": "Custom8", + "PartWarehouseCustom9": "Custom9", + "PartWarehouseCustom10": "Custom10", + "PartWarehouseCustom11": "Custom11", + "PartWarehouseCustom12": "Custom12", + "PartWarehouseCustom13": "Custom13", + "PartWarehouseCustom14": "Custom14", + "PartWarehouseCustom15": "Custom15", + "PartWarehouseCustom16": "Custom16", "PriorityColor": "Color", "PriorityList": "Priorities", "PriorityName": "Priority Name", diff --git a/server/AyaNova/resource/es.json b/server/AyaNova/resource/es.json index e776f2a2..7690a56d 100644 --- a/server/AyaNova/resource/es.json +++ b/server/AyaNova/resource/es.json @@ -653,7 +653,24 @@ "PartSerialNumbersAvailable": "Números de serie disponibles", "PartWarehouseDescription": "Descripción", "PartWarehouseList": "Almacenes de piezas", - "PartWarehouseName": "Nombre de almacén de la pieza", + "PartWarehouseName": "Nombre de almacén de la pieza", + "PartWarehouseNotes": "Notas", + "PartWarehouseCustom1": "Campo personalizado 1", + "PartWarehouseCustom2": "Campo personalizado 2", + "PartWarehouseCustom3": "Campo personalizado 3", + "PartWarehouseCustom4": "Campo personalizado 4", + "PartWarehouseCustom5": "Campo personalizado 5", + "PartWarehouseCustom6": "Campo personalizado 6", + "PartWarehouseCustom7": "Campo personalizado 7", + "PartWarehouseCustom8": "Campo personalizado 8", + "PartWarehouseCustom9": "Campo personalizado 9", + "PartWarehouseCustom10": "Campo personalizado 10", + "PartWarehouseCustom11": "Campo personalizado 11", + "PartWarehouseCustom12": "Campo personalizado 12", + "PartWarehouseCustom13": "Campo personalizado 13", + "PartWarehouseCustom14": "Campo personalizado 14", + "PartWarehouseCustom15": "Campo personalizado 15", + "PartWarehouseCustom16": "Campo personalizado 16", "PriorityColor": "Color", "PriorityList": "Prioridades", "PriorityName": "Nombre prioridad", diff --git a/server/AyaNova/resource/fr.json b/server/AyaNova/resource/fr.json index bb3b7014..8261cdfa 100644 --- a/server/AyaNova/resource/fr.json +++ b/server/AyaNova/resource/fr.json @@ -590,7 +590,7 @@ "PartNotes": "Remarques", "PartPartNumber": "Numéro de pièce", "PartRetail": "Détail", - "PartCost":"Coût", + "PartCost": "Coût", "PartTrackSerialNumber": "Numéro de série de suivi", "PartUPC": "CUP", "PartWholesalerID": "Grossiste", @@ -655,6 +655,23 @@ "PartWarehouseDescription": "Description", "PartWarehouseList": "Magasins de pièces", "PartWarehouseName": "Nom de magasin de pièces", + "PartWarehouseNotes": "Remarques", + "PartWarehouseCustom1": "Champ personnalisé 1", + "PartWarehouseCustom2": "Champ personnalisé 2", + "PartWarehouseCustom3": "Champ personnalisé 3", + "PartWarehouseCustom4": "Champ personnalisé 4", + "PartWarehouseCustom5": "Champ personnalisé 5", + "PartWarehouseCustom6": "Champ personnalisé 6", + "PartWarehouseCustom7": "Champ personnalisé 7", + "PartWarehouseCustom8": "Champ personnalisé 8", + "PartWarehouseCustom9": "Champ personnalisé 9", + "PartWarehouseCustom10": "Champ personnalisé 10", + "PartWarehouseCustom11": "Champ personnalisé 11", + "PartWarehouseCustom12": "Champ personnalisé 12", + "PartWarehouseCustom13": "Champ personnalisé 13", + "PartWarehouseCustom14": "Champ personnalisé 14", + "PartWarehouseCustom15": "Champ personnalisé 15", + "PartWarehouseCustom16": "Champ personnalisé 16", "PriorityColor": "Couleur", "PriorityList": "Priorités", "PriorityName": "Nom de priorité", diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 7629df8c..e1002b98 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -393,6 +393,7 @@ BEGIN when 63 then aytable = 'atravelrate'; when 64 then aytable = 'ataxcode'; when 65 then aytable = 'apartassembly'; + when 66 then aytable = 'apartwarehouse'; else RETURN format('??PUBLIC.AYGETNAME-UNKNOWN_TYPE:%S',ayobjecttype);-- This should not happen unless dev forgot to update this end case;