Fleshing otu workorder
This commit is contained in:
@@ -115,59 +115,19 @@ namespace AyaNova.Api.Controllers
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Patch (update) WorkOrder
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="concurrencyToken"></param>
|
||||
/// <param name="objectPatch"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPatch("{id}/{concurrencyToken}")]
|
||||
public async Task<IActionResult> PatchWorkOrder([FromRoute] long id, [FromRoute] uint concurrencyToken, [FromBody]JsonPatchDocument<WorkOrder> 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));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Post WorkOrder
|
||||
/// </summary>
|
||||
/// <param name="inObj"></param>
|
||||
/// <param name="workorderTemplateId"></param>
|
||||
/// <param name="customerId"></param>
|
||||
/// <param name="serial">force a workorder number, leave null to autogenerate the next one in sequence (mostly used for import)</param>
|
||||
/// <param name="apiVersion">Automatically filled from route path, no need to specify in body</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostWorkOrder([FromBody] WorkOrder inObj, ApiVersion apiVersion)
|
||||
/// <returns>A created workorder ready to fill out</returns>
|
||||
[HttpPost("Create")]
|
||||
public async Task<IActionResult> 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
|
||||
|
||||
@@ -60,28 +60,34 @@ namespace AyaNova.Biz
|
||||
//CREATE
|
||||
|
||||
//Called from route and also seeder
|
||||
internal async Task<WorkOrder> CreateAsync(WorkOrder inObj)
|
||||
internal async Task<WorkOrder> 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<WorkOrder> 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<bool> 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<bool> PatchAsync(WorkOrder dbObj, JsonPatchDocument<WorkOrder> objectPatch, uint concurrencyToken)
|
||||
{
|
||||
//Validate Patch is allowed
|
||||
if (!ValidateJsonPatch<WorkOrder>.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<bool> 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
|
||||
|
||||
|
||||
@@ -124,10 +124,10 @@ namespace AyaNova.Models
|
||||
.HasForeignKey<Widget>(b => b.UserId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
//Workorder
|
||||
modelBuilder.Entity<WorkOrderItem>()
|
||||
.HasOne(p => p.WorkOrder)
|
||||
.WithMany(b => b.WorkorderItems);
|
||||
// //Workorder
|
||||
// modelBuilder.Entity<WorkOrderItem>()
|
||||
// .HasOne(p => p.WorkOrder)
|
||||
// .WithMany(b => b.WorkorderItems);
|
||||
|
||||
|
||||
//-----------
|
||||
|
||||
@@ -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<string>();
|
||||
}
|
||||
|
||||
|
||||
//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
|
||||
|
||||
@@ -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<string> 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<string>();
|
||||
}
|
||||
|
||||
//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
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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...
|
||||
|
||||
Reference in New Issue
Block a user