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>
|
/// <summary>
|
||||||
/// Post WorkOrder
|
/// Post WorkOrder
|
||||||
/// </summary>
|
/// </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>
|
/// <param name="apiVersion">Automatically filled from route path, no need to specify in body</param>
|
||||||
/// <returns></returns>
|
/// <returns>A created workorder ready to fill out</returns>
|
||||||
[HttpPost]
|
[HttpPost("Create")]
|
||||||
public async Task<IActionResult> PostWorkOrder([FromBody] WorkOrder inObj, ApiVersion apiVersion)
|
public async Task<IActionResult> PostWorkOrder([FromQuery] long? workorderTemplateId, long? customerId, uint? serial, ApiVersion apiVersion)
|
||||||
{
|
{
|
||||||
if (!serverState.IsOpen)
|
if (!serverState.IsOpen)
|
||||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||||
@@ -183,7 +143,7 @@ namespace AyaNova.Api.Controllers
|
|||||||
return BadRequest(new ApiErrorResponse(ModelState));
|
return BadRequest(new ApiErrorResponse(ModelState));
|
||||||
|
|
||||||
//Create and validate
|
//Create and validate
|
||||||
WorkOrder o = await biz.CreateAsync(inObj);
|
WorkOrder o = await biz.CreateAsync(workorderTemplateId,customerId, serial);
|
||||||
if (o == null)
|
if (o == null)
|
||||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -60,28 +60,34 @@ namespace AyaNova.Biz
|
|||||||
//CREATE
|
//CREATE
|
||||||
|
|
||||||
//Called from route and also seeder
|
//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);
|
//Create and save to db a new workorder and return it
|
||||||
if (HasErrors)
|
//By default it should generate a unique id as the "name" field
|
||||||
return null;
|
//but need to support export to v8 plugin so need to be able to accept name already
|
||||||
else
|
|
||||||
{
|
|
||||||
//do stuff with WorkOrder
|
|
||||||
WorkOrder outObj = inObj;
|
|
||||||
|
|
||||||
outObj.Tags = TagUtil.NormalizeTags(outObj.Tags);
|
// await ValidateAsync(inObj, null);
|
||||||
outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields);
|
// if (HasErrors)
|
||||||
//Save to db
|
// return null;
|
||||||
await ct.WorkOrder.AddAsync(outObj);
|
// else
|
||||||
await ct.SaveChangesAsync();
|
// {
|
||||||
//Handle child and associated items:
|
//do stuff with WorkOrder
|
||||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct);
|
WorkOrder outObj = new WorkOrder();
|
||||||
await SearchIndexAsync(outObj, true);
|
outObj.Serial = serial ?? ServerBootConfig.WORKORDER_SERIAL.GetNext();
|
||||||
await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null);
|
|
||||||
|
|
||||||
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)
|
internal async Task<WorkOrder> DuplicateAsync(WorkOrder dbObj)
|
||||||
{
|
{
|
||||||
|
throw new System.NotImplementedException("STUB: WORKORDER DUPLICATE");
|
||||||
WorkOrder outObj = new WorkOrder();
|
WorkOrder outObj = new WorkOrder();
|
||||||
CopyObject.Copy(dbObj, outObj, "Wiki");
|
CopyObject.Copy(dbObj, outObj, "Wiki");
|
||||||
// outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255);
|
// outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255);
|
||||||
@@ -131,7 +137,7 @@ namespace AyaNova.Biz
|
|||||||
//put
|
//put
|
||||||
internal async Task<bool> PutAsync(WorkOrder dbObj, WorkOrder inObj)
|
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
|
//make a snapshot of the original for validation but update the original to preserve workflow
|
||||||
WorkOrder SnapshotOfOriginalDBObj = new WorkOrder();
|
WorkOrder SnapshotOfOriginalDBObj = new WorkOrder();
|
||||||
CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj);
|
CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj);
|
||||||
@@ -159,35 +165,6 @@ namespace AyaNova.Biz
|
|||||||
return true;
|
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)
|
private async Task SearchIndexAsync(WorkOrder obj, bool isNew)
|
||||||
@@ -216,6 +193,7 @@ namespace AyaNova.Biz
|
|||||||
//
|
//
|
||||||
internal async Task<bool> DeleteAsync(WorkOrder dbObj)
|
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
|
//Determine if the object can be deleted, do the deletion tentatively
|
||||||
//Probably also in here deal with tags and associated search text etc
|
//Probably also in here deal with tags and associated search text etc
|
||||||
|
|
||||||
|
|||||||
@@ -124,10 +124,10 @@ namespace AyaNova.Models
|
|||||||
.HasForeignKey<Widget>(b => b.UserId)
|
.HasForeignKey<Widget>(b => b.UserId)
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
.OnDelete(DeleteBehavior.NoAction);
|
||||||
|
|
||||||
//Workorder
|
// //Workorder
|
||||||
modelBuilder.Entity<WorkOrderItem>()
|
// modelBuilder.Entity<WorkOrderItem>()
|
||||||
.HasOne(p => p.WorkOrder)
|
// .HasOne(p => p.WorkOrder)
|
||||||
.WithMany(b => b.WorkorderItems);
|
// .WithMany(b => b.WorkorderItems);
|
||||||
|
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AyaNova.Biz;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace AyaNova.Models
|
namespace AyaNova.Models
|
||||||
{
|
{
|
||||||
//NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal,
|
//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
|
//otherwise the server will call it an invalid record if the field isn't sent from client
|
||||||
|
|
||||||
public partial class WorkOrder
|
public partial class WorkOrder
|
||||||
{
|
{
|
||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
public uint ConcurrencyToken { get; set; }
|
public uint ConcurrencyToken { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string Name { get; set; }
|
public uint Serial { get; set; }
|
||||||
public bool Active { get; set; }
|
public bool Active { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public string Wiki {get;set;}
|
public string Wiki {get;set;}
|
||||||
@@ -30,6 +29,14 @@ namespace AyaNova.Models
|
|||||||
Tags = new List<string>();
|
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
|
}//eoc
|
||||||
|
|
||||||
}//eons
|
}//eons
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AyaNova.Biz;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace AyaNova.Models
|
namespace AyaNova.Models
|
||||||
@@ -23,6 +22,7 @@ namespace AyaNova.Models
|
|||||||
public List<string> Tags { get; set; }
|
public List<string> Tags { get; set; }
|
||||||
|
|
||||||
//Principle
|
//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 int WorkorderId { get; set; }//fk
|
||||||
public WorkOrder WorkOrder { get; set; }
|
public WorkOrder WorkOrder { get; set; }
|
||||||
|
|
||||||
@@ -33,6 +33,12 @@ namespace AyaNova.Models
|
|||||||
Tags = new List<string>();
|
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
|
}//eoc
|
||||||
|
|
||||||
}//eons
|
}//eons
|
||||||
|
|||||||
@@ -383,13 +383,13 @@ namespace AyaNova.Util
|
|||||||
await ExecQueryAsync("CREATE INDEX avendor_tags ON avendor using GIN(tags)");
|
await ExecQueryAsync("CREATE INDEX avendor_tags ON avendor using GIN(tags)");
|
||||||
|
|
||||||
//WORKORDER
|
//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)");
|
"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)");
|
await ExecQueryAsync("CREATE INDEX aworkorder_tags ON aworkorder using GIN(tags)");
|
||||||
|
|
||||||
//WORKORDERITEM
|
//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)");
|
"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 UNIQUE INDEX aworkorderitem_name_id_idx ON aworkorderitem (id, name);");
|
||||||
await ExecQueryAsync("CREATE INDEX aworkorderitem_tags ON aworkorderitem using GIN(tags)");
|
await ExecQueryAsync("CREATE INDEX aworkorderitem_tags ON aworkorderitem using GIN(tags)");
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ namespace AyaNova.Util
|
|||||||
|
|
||||||
//AUTOID's
|
//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 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
|
//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)
|
#if (DEBUG)
|
||||||
@@ -226,12 +226,22 @@ namespace AyaNova.Util
|
|||||||
//query for most recently used serial number
|
//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
|
//(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)
|
// 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();
|
var serializedObject = ct.Widget.AsNoTracking().OrderByDescending(x => x.Id).FirstOrDefault();
|
||||||
uint MaxWidgetId = 0;
|
WIDGET_SERIAL = new AutoId(serializedObject == null ? 0 : serializedObject.Serial);
|
||||||
if (MostRecentWidget != null)
|
|
||||||
MaxWidgetId = MostRecentWidget.Serial;
|
|
||||||
WIDGET_SERIAL = new AutoId(MaxWidgetId);
|
|
||||||
|
|
||||||
|
// 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...
|
//OTHER SERIALS HERE IN FUTURE...
|
||||||
|
|||||||
Reference in New Issue
Block a user