diff --git a/server/AyaNova/Controllers/WorkOrderController.cs b/server/AyaNova/Controllers/WorkOrderController.cs index 75089ffa..df8bda2f 100644 --- a/server/AyaNova/Controllers/WorkOrderController.cs +++ b/server/AyaNova/Controllers/WorkOrderController.cs @@ -115,59 +115,19 @@ namespace AyaNova.Api.Controllers - /// - /// Patch (update) WorkOrder - /// - /// - /// - /// - /// - [HttpPatch("{id}/{concurrencyToken}")] - public async Task PatchWorkOrder([FromRoute] long id, [FromRoute] uint concurrencyToken, [FromBody]JsonPatchDocument objectPatch) - { - //https://dotnetcoretutorials.com/2017/11/29/json-patch-asp-net-core/ - if (!serverState.IsOpen) - return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - - if (!ModelState.IsValid) - return BadRequest(new ApiErrorResponse(ModelState)); - - //Instantiate the business object handler - WorkOrderBiz biz = WorkOrderBiz.GetBiz(ct, HttpContext); - - var o = await biz.GetAsync(id, false); - if (o == null) - return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); - - if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) - return StatusCode(403, new ApiNotAuthorizedResponse()); - - try - { - //patch and validate - if (!await biz.PatchAsync(o, objectPatch, concurrencyToken)) - return BadRequest(new ApiErrorResponse(biz.Errors)); - } - catch (DbUpdateConcurrencyException) - { - if (!await biz.ExistsAsync(id)) - return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); - else - return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); - } - return Ok(ApiOkResponse.Response(new { ConcurrencyToken = o.ConcurrencyToken }, true)); - } /// /// Post WorkOrder /// - /// + /// + /// + /// force a workorder number, leave null to autogenerate the next one in sequence (mostly used for import) /// Automatically filled from route path, no need to specify in body - /// - [HttpPost] - public async Task PostWorkOrder([FromBody] WorkOrder inObj, ApiVersion apiVersion) + /// A created workorder ready to fill out + [HttpPost("Create")] + public async Task PostWorkOrder([FromQuery] long? workorderTemplateId, long? customerId, uint? serial, ApiVersion apiVersion) { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); @@ -183,7 +143,7 @@ namespace AyaNova.Api.Controllers return BadRequest(new ApiErrorResponse(ModelState)); //Create and validate - WorkOrder o = await biz.CreateAsync(inObj); + WorkOrder o = await biz.CreateAsync(workorderTemplateId,customerId, serial); if (o == null) return BadRequest(new ApiErrorResponse(biz.Errors)); else diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index b775fe31..e8974563 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -60,28 +60,34 @@ namespace AyaNova.Biz //CREATE //Called from route and also seeder - internal async Task CreateAsync(WorkOrder inObj) + internal async Task CreateAsync(long? workorderTemplateId, long? customerId, uint? serial) { - await ValidateAsync(inObj, null); - if (HasErrors) - return null; - else - { - //do stuff with WorkOrder - WorkOrder outObj = inObj; + //Create and save to db a new workorder and return it + //By default it should generate a unique id as the "name" field + //but need to support export to v8 plugin so need to be able to accept name already - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.WorkOrder.AddAsync(outObj); - await ct.SaveChangesAsync(); - //Handle child and associated items: - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); - await SearchIndexAsync(outObj, true); - await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + // await ValidateAsync(inObj, null); + // if (HasErrors) + // return null; + // else + // { + //do stuff with WorkOrder + WorkOrder outObj = new WorkOrder(); + outObj.Serial = serial ?? ServerBootConfig.WORKORDER_SERIAL.GetNext(); - return outObj; - } + + outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); + outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); + //Save to db + await ct.WorkOrder.AddAsync(outObj); + await ct.SaveChangesAsync(); + //Handle child and associated items: + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(outObj, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + + return outObj; + //} } @@ -93,7 +99,7 @@ namespace AyaNova.Biz internal async Task DuplicateAsync(WorkOrder dbObj) { - + throw new System.NotImplementedException("STUB: WORKORDER DUPLICATE"); WorkOrder outObj = new WorkOrder(); CopyObject.Copy(dbObj, outObj, "Wiki"); // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); @@ -131,7 +137,7 @@ namespace AyaNova.Biz //put internal async Task PutAsync(WorkOrder dbObj, WorkOrder inObj) { - + throw new System.NotImplementedException("STUB: WORKORDER PUT"); //make a snapshot of the original for validation but update the original to preserve workflow WorkOrder SnapshotOfOriginalDBObj = new WorkOrder(); CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); @@ -159,35 +165,6 @@ namespace AyaNova.Biz return true; } - //patch - internal async Task PatchAsync(WorkOrder dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) - { - //Validate Patch is allowed - if (!ValidateJsonPatch.Validate(this, objectPatch)) return false; - - //make a snapshot of the original for validation but update the original to preserve workflow - WorkOrder SnapshotOfOriginalDBObj = new WorkOrder(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Do the patching - objectPatch.ApplyTo(dbObj); - - dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); - dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); - - ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; - await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); - if (HasErrors) - return false; - - //Log event and save context - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); - await SearchIndexAsync(dbObj, false); - - await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); - - return true; - } private async Task SearchIndexAsync(WorkOrder obj, bool isNew) @@ -216,6 +193,7 @@ namespace AyaNova.Biz // internal async Task DeleteAsync(WorkOrder dbObj) { + throw new System.NotImplementedException("STUB: WORKORDER DELETE"); //Determine if the object can be deleted, do the deletion tentatively //Probably also in here deal with tags and associated search text etc diff --git a/server/AyaNova/models/AyContext.cs b/server/AyaNova/models/AyContext.cs index a2171dfc..fe041a7b 100644 --- a/server/AyaNova/models/AyContext.cs +++ b/server/AyaNova/models/AyContext.cs @@ -124,10 +124,10 @@ namespace AyaNova.Models .HasForeignKey(b => b.UserId) .OnDelete(DeleteBehavior.NoAction); - //Workorder - modelBuilder.Entity() - .HasOne(p => p.WorkOrder) - .WithMany(b => b.WorkorderItems); + // //Workorder + // modelBuilder.Entity() + // .HasOne(p => p.WorkOrder) + // .WithMany(b => b.WorkorderItems); //----------- diff --git a/server/AyaNova/models/WorkOrder.cs b/server/AyaNova/models/WorkOrder.cs index 0bd74db8..a2e4cd54 100644 --- a/server/AyaNova/models/WorkOrder.cs +++ b/server/AyaNova/models/WorkOrder.cs @@ -1,21 +1,20 @@ -using System; using System.Collections.Generic; -using AyaNova.Biz; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; namespace AyaNova.Models { //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, //otherwise the server will call it an invalid record if the field isn't sent from client - + public partial class WorkOrder { public long Id { get; set; } public uint ConcurrencyToken { get; set; } [Required] - public string Name { get; set; } + public uint Serial { get; set; } public bool Active { get; set; } public string Notes { get; set; } public string Wiki {get;set;} @@ -30,6 +29,14 @@ namespace AyaNova.Models Tags = new List(); } + + //Not persisted business properties + //NOTE: this could be a common class applied to everything for common biz rule stuff + //i.e. specific rights in situations based on rules, like candelete, canedit etc + [NotMapped] + public bool NonDataBaseExampleColumn { get; set; }//example of how to add a property that is not persisted but is used by both ends dynamically, should come up with a naming scheme so can see them at a glance + + }//eoc }//eons diff --git a/server/AyaNova/models/WorkOrderItem.cs b/server/AyaNova/models/WorkOrderItem.cs index 9a2b4627..5033810e 100644 --- a/server/AyaNova/models/WorkOrderItem.cs +++ b/server/AyaNova/models/WorkOrderItem.cs @@ -1,7 +1,6 @@ -using System; using System.Collections.Generic; -using AyaNova.Biz; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; namespace AyaNova.Models @@ -23,6 +22,7 @@ namespace AyaNova.Models public List Tags { get; set; } //Principle + [Required]//this required annotation should cause a cascade delete to happen automatically when wo is deleted, need to test that public int WorkorderId { get; set; }//fk public WorkOrder WorkOrder { get; set; } @@ -33,6 +33,12 @@ namespace AyaNova.Models Tags = new List(); } + //Not persisted business properties + //NOTE: this could be a common class applied to everything for common biz rule stuff + //i.e. specific rights in situations based on rules, like candelete, canedit etc + [NotMapped] + public bool NonDataBaseExampleColumn { get; set; }//example of how to add a property that is not persisted but is used by both ends dynamically, should come up with a naming scheme so can see them at a glance + }//eoc }//eons diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 22bc8b77..f0debf26 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -383,13 +383,13 @@ namespace AyaNova.Util await ExecQueryAsync("CREATE INDEX avendor_tags ON avendor using GIN(tags)"); //WORKORDER - await ExecQueryAsync("CREATE TABLE aworkorder (id BIGSERIAL PRIMARY KEY, name varchar(255) not null unique, active bool, " + + await ExecQueryAsync("CREATE TABLE aworkorder (id BIGSERIAL PRIMARY KEY, serial bigint not null, active bool, " + "notes text NULL, wiki text null, customfields text NULL, tags varchar(255) ARRAY NULL)"); - await ExecQueryAsync("CREATE UNIQUE INDEX aworkorder_name_id_idx ON aworkorder (id, name);"); + await ExecQueryAsync("CREATE INDEX aworkorder_number_id_idx ON aworkorder (id, serial);"); await ExecQueryAsync("CREATE INDEX aworkorder_tags ON aworkorder using GIN(tags)"); //WORKORDERITEM - await ExecQueryAsync("CREATE TABLE aworkorderitem (id BIGSERIAL PRIMARY KEY, name varchar(255) not null unique, active bool, " + + await ExecQueryAsync("CREATE TABLE aworkorderitem (id BIGSERIAL PRIMARY KEY, workorderid bigint not null REFERENCES aworkorder (id), name varchar(255) not null unique, active bool, " + "notes text NULL, wiki text null, customfields text NULL, tags varchar(255) ARRAY NULL)"); await ExecQueryAsync("CREATE UNIQUE INDEX aworkorderitem_name_id_idx ON aworkorderitem (id, name);"); await ExecQueryAsync("CREATE INDEX aworkorderitem_tags ON aworkorderitem using GIN(tags)"); diff --git a/server/AyaNova/util/ServerBootConfig.cs b/server/AyaNova/util/ServerBootConfig.cs index 4b735cff..209f966b 100644 --- a/server/AyaNova/util/ServerBootConfig.cs +++ b/server/AyaNova/util/ServerBootConfig.cs @@ -15,9 +15,9 @@ namespace AyaNova.Util //AUTOID's - //Get the highest id number issued previously and start the auto-id at that level + //Get the most recent id number issued previously and start the auto-id at that level internal static AutoId WIDGET_SERIAL { get; set; } - + internal static AutoId WORKORDER_SERIAL { get; set; } //Diagnostic static values used during development, may not be related to config at all, this is just a convenient class to put them in #if (DEBUG) @@ -226,12 +226,22 @@ namespace AyaNova.Util //query for most recently used serial number //(note, can't query for highest serial as it can and likely will reset or be changed manually from time to time // so the algorithm is to keep the most recent sequence going on startup of the server) - var MostRecentWidget = ct.Widget.AsNoTracking().OrderByDescending(x => x.Id).FirstOrDefault(); - uint MaxWidgetId = 0; - if (MostRecentWidget != null) - MaxWidgetId = MostRecentWidget.Serial; - WIDGET_SERIAL = new AutoId(MaxWidgetId); + var serializedObject = ct.Widget.AsNoTracking().OrderByDescending(x => x.Id).FirstOrDefault(); + WIDGET_SERIAL = new AutoId(serializedObject == null ? 0 : serializedObject.Serial); + // var MostRecentWidget = ct.Widget.AsNoTracking().OrderByDescending(x => x.Id).FirstOrDefault(); + // uint MaxWidgetId = 0; + // if (MostRecentWidget != null) + // MaxWidgetId = MostRecentWidget.Serial; + // WIDGET_SERIAL = new AutoId(MaxWidgetId); + + } + + //Workorder + if (WORKORDER_SERIAL == null) + { + var serializedObject = ct.WorkOrder.AsNoTracking().OrderByDescending(x => x.Id).FirstOrDefault(); + WORKORDER_SERIAL = new AutoId(serializedObject == null ? 0 : serializedObject.Serial); } //OTHER SERIALS HERE IN FUTURE...