Files
raven/server/AyaNova/biz/WorkOrderBiz.cs
2021-03-25 18:03:01 +00:00

2704 lines
134 KiB
C#

using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using AyaNova.Util;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Models;
using System.Linq;
using System;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
namespace AyaNova.Biz
{
/*
###############
MUST be able to update any section at any level independantly
Fetch must be able to get entire graph or from any level down or single header only as required
probably need a woheaderfetch route separately
*/
internal class WorkOrderBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, INotifiableObject
{
// //Feature specific roles
// internal static AuthorizationRoles RolesAllowedToChangeSerial = AuthorizationRoles.BizAdminFull | AuthorizationRoles.DispatchFull | AuthorizationRoles.AccountingFull;
internal WorkOrderBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles)
{
ct = dbcontext;
UserId = currentUserId;
UserTranslationId = userTranslationId;
CurrentUserRoles = UserRoles;
BizType = AyaType.WorkOrder;
}
internal static WorkOrderBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null)
{
if (httpContext != null)
return new WorkOrderBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items));
else//when called internally for internal ops there will be no context so need to set default values for that
return new WorkOrderBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull);
}
/*
██╗ ██╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗
██║ ██║██╔═══██╗██╔══██╗██║ ██╔╝ ██╔═══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗
██║ █╗ ██║██║ ██║██████╔╝█████╔╝█████╗██║ ██║██████╔╝██║ ██║█████╗ ██████╔╝
██║███╗██║██║ ██║██╔══██╗██╔═██╗╚════╝██║ ██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗
╚███╔███╔╝╚██████╔╝██║ ██║██║ ██╗ ╚██████╔╝██║ ██║██████╔╝███████╗██║ ██║
╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝
*/
#region WorkOrder level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> WorkOrderExistsAsync(long id)
{
return await ct.WorkOrder.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrder> WorkOrderCreateAsync(WorkOrder newObject)
{
await WorkOrderValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrder.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct);
await WorkOrderSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DUPLICATE
//
internal async Task<WorkOrder> WorkOrderDuplicateAsync(long id)
{
WorkOrder dbObject = await WorkOrderGetAsync(id, false);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
WorkOrder newObject = new WorkOrder();
CopyObject.Copy(dbObject, newObject, "Wiki,Serial");
newObject.Id = 0;
newObject.Concurrency = 0;
await ct.WorkOrder.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct);
await WorkOrderSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrder> WorkOrderGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrder.AsNoTracking()
.Include(w => w.Items)
.ThenInclude(wi => wi.Expenses)
.Include(w => w.Items)
.ThenInclude(wi => wi.Labors)
.Include(w => w.Items)
.ThenInclude(wi => wi.Loans)
.Include(w => w.Items)
.ThenInclude(wi => wi.Parts)
.Include(w => w.Items)
.ThenInclude(wi => wi.PartRequests)
.Include(w => w.Items)
.ThenInclude(wi => wi.ScheduledUsers)
.Include(w => w.Items)
.ThenInclude(wi => wi.Tasks)
.Include(w => w.Items)
.ThenInclude(wi => wi.Travels)
.Include(w => w.Items)
.ThenInclude(wi => wi.Units)
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrder> WorkOrderPutAsync(WorkOrder putObject)
{
WorkOrder dbObject = await WorkOrderGetAsync(putObject.Id, false);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
if (dbObject.Concurrency != putObject.Concurrency)
{
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
putObject.Tags = TagBiz.NormalizeTags(putObject.Tags);
putObject.CustomFields = JsonUtil.CompactJson(putObject.CustomFields);
await WorkOrderValidateAsync(putObject, dbObject);
if (HasErrors)
return null;
ct.Replace(dbObject, putObject);
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await WorkOrderExistsAsync(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 WorkOrderSearchIndexAsync(putObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject);
return putObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> WorkOrderDeleteAsync(long id)
{
using (var transaction = await ct.Database.BeginTransactionAsync())
{
try
{
WorkOrder dbObject = await ct.WorkOrder.SingleOrDefaultAsync(z => z.Id == id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND);
return false;
}
WorkOrderValidateCanDelete(dbObject);
if (HasErrors)
return false;
//collect the child id's to delete
var ItemIds = await ct.WorkOrderItem.AsNoTracking().Where(z => z.WorkOrderId == id).Select(z => z.Id).ToListAsync();
//Delete children
foreach (long ItemId in ItemIds)
if (!await ItemDeleteAsync(ItemId, transaction))
return false;
ct.WorkOrder.Remove(dbObject);
await ct.SaveChangesAsync();
await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Serial.ToString(), 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;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
//GET ANCESTOR TYPE AND ID
//
internal static async Task<AyaTypeId> GetAncestor(AyaType ayaType, long id, AyContext ct)
{
long woitemid = 0;
switch (ayaType)
{
case AyaType.WorkOrderItem:
woitemid = id;
break;
case AyaType.WorkOrderItemExpense:
woitemid = await ct.WorkOrderItemExpense.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync();
break;
case AyaType.WorkOrderItemLabor:
woitemid = await ct.WorkOrderItemLabor.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync();
break;
case AyaType.WorkOrderItemLoan:
woitemid = await ct.WorkOrderItemLoan.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync();
break;
case AyaType.WorkOrderItemPart:
woitemid = await ct.WorkOrderItemPart.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync();
break;
case AyaType.WorkOrderItemPartRequest:
woitemid = await ct.WorkOrderItemPartRequest.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync();
break;
case AyaType.WorkOrderItemScheduledUser:
woitemid = await ct.WorkOrderItemScheduledUser.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync();
break;
case AyaType.WorkOrderItemTask:
woitemid = await ct.WorkOrderItemTask.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync();
break;
case AyaType.WorkOrderItemTravel:
woitemid = await ct.WorkOrderItemTravel.Where(z => z.Id == id).Select(z => z.WorkOrderItemId).SingleOrDefaultAsync();
break;
default:
throw new System.NotSupportedException($"WorkOrderBiz::GetAncestor -> AyaType {ayaType.ToString()} is not supported");
}
return new AyaTypeId(AyaType.WorkOrder, await ct.WorkOrderItem.Where(z => z.Id == woitemid).Select(z => z.WorkOrderId).SingleOrDefaultAsync());
}
////////////////////////////////////////////////////////////////////////////////////////////////
//SEARCH
//
private async Task WorkOrderSearchIndexAsync(WorkOrder obj, bool isNew)
{
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType);
SearchParams.AddText(obj.Notes).AddText(obj.Serial).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> GetSearchResultSummary(long id)
{
var obj = await ct.WorkOrder.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Serial).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
//Can save or update?
private async Task WorkOrderValidateAsync(WorkOrder proposedObj, WorkOrder currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
/*
todo: workorder status list first, it's a table of created items, keep properties from v7 but add the following properties:
SelectRoles - who can select the status (still shows if they can't select but that's the current status, like active does)
This is best handled at the client. It prefetches all the status out of the normal picklist process, more like how other things are separately handled now without a picklist
client then knows if a status is available or not and can process to only present available ones
#### Server can use a biz rule to ensure that it can't be circumvented
UI defaults to any role
DeselectRoles - who can unset this status (important for process control)
UI defaults to any role
CompletedStatus bool - this is a final status indicating all work on the workorder is completed, affects notification etc
UI defaults to false but when set to true auto sets lockworkorder to true (but user can just unset lockworkorder)
LockWorkorder - this status is considered read only and the workorder is locked
Just a read only thing, can just change status to "unlock" it
to support states where no one should work on a wo for whatever reason but it's not necessarily completed
e.g. "Hold for inspection", "On hold" generally etc
*/
// //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.WorkOrder.AnyAsync(z => z.Name == proposedObj.Name && z.Id != proposedObj.Id))
// {
// AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name");
// }
// }
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrder.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 WorkOrderValidateCanDelete(WorkOrder dbObject)
{
//whatever needs to be check to delete this object
}
//############### NOTIFICATION TODO
/*
todo: workorder notifications remove #30 and #32 as redundant
WorkorderStatusChange = 4,//* Workorder object, any *change* of status including from no status (new) to a specific conditional status ID value
WorkorderStatusAge = 24,//* Workorder object Created / Updated, conditional on exact status selected IdValue, Tags conditional, advance notice can be set
//THESE TWO ARE REDUNDANT:
this is actually workorderstatuschange because can just pick any status under workorderstatuschange to be notified about
WorkorderFinished = 30, //*Service work order is set to any status that is flagged as a "Finished" type of status. Customer & User
//This one could be accomplished with WorkorderStatusAge, just pick a finished status and set a time frame and wala!
WorkorderFinishedFollowUp = 32, //* Service workorder closed status follow up again after this many TIMESPAN
todo: CHANGE WorkorderFinishStatusOverdue = 15,//* Workorder object not set to a "Finished" flagged workorder status type in selected time span from creation of workorder
Change this to a new type that is based on so many days *without* being set to a particular status
but first check if tied to contract response time stuff, how that's handled
that's closeby date in v7 but isn't that deprecated now without a "close"?
maybe I do need the Finished status bool thing above
*/
////////////////////////////////////////////////////////////////////////////////////////////////
//REPORTING
//
public async Task<JArray> 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.WorkOrder.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 (WorkOrder w in orderedList)
{
await PopulateVizFields(w);
var jo = JObject.FromObject(w);
if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"]))
jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]);
ReportData.Add(jo);
}
}
return ReportData;
}
//populate viz fields from provided object
private async Task PopulateVizFields(WorkOrder o)
{
// if (o.WorkOrderOverseerId != null)
// o.WorkOrderOverseerViz = await ct.User.AsNoTracking().Where(x => x.Id == o.WorkOrderOverseerId).Select(x => x.Name).FirstOrDefaultAsync();
}
////////////////////////////////////////////////////////////////////////////////////////////////
// IMPORT EXPORT
//
public async Task<JArray> 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<List<string>> ImportData(JArray ja)
// {
// List<string> ImportResult = new List<string>();
// 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<WorkOrder>(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 WorkOrderCreateAsync(w);
// if (res == null)
// {
// ImportResult.Add($"* {w.Serial} - {this.GetErrorsAsString()}");
// this.ClearErrors();
// }
// else
// {
// ImportResult.Add($"{w.Serial} - ok");
// }
// }
// return ImportResult;
// }
////////////////////////////////////////////////////////////////////////////////////////////////
//JOB / OPERATIONS
//
public async Task HandleJobAsync(OpsJob job)
{
switch (job.JobType)
{
case JobType.BatchCoreObjectOperation:
await ProcessBatchJobAsync(job);
break;
default:
throw new System.ArgumentOutOfRangeException($"WorkOrder.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<long> idList = new List<long>();
long FailedObjectCount = 0;
JObject jobData = JObject.Parse(job.JobInfo);
if (jobData.ContainsKey("idList"))
idList = ((JArray)jobData["idList"]).ToObject<List<long>>();
else
idList = await ct.Widget.Select(z => z.Id).ToListAsync();
bool SaveIt = false;
foreach (long id in idList)
{
try
{
SaveIt = false;
ClearErrors();
ICoreBizObjectModel o = null;
//save a fetch if it's a delete
if (job.SubType != JobSubType.Delete)
o = await GetWorkOrderGraphItem(job.AType, id);
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 DeleteWorkOrderGraphItem(job.AType, 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 PutWorkOrderGraphItem(job.AType, 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<WorkOrderBiz>();
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
//todo: contract response time notification
}//end of process notifications
#endregion workorder level
/*
██╗████████╗███████╗███╗ ███╗███████╗
██║╚══██╔══╝██╔════╝████╗ ████║██╔════╝
██║ ██║ █████╗ ██╔████╔██║███████╗
██║ ██║ ██╔══╝ ██║╚██╔╝██║╚════██║
██║ ██║ ███████╗██║ ╚═╝ ██║███████║
╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝
*/
#region WorkOrderItem level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> ItemExistsAsync(long id)
{
return await ct.WorkOrderItem.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItem> ItemCreateAsync(WorkOrderItem dtNewObject)
{
WorkOrderItem newObject = new WorkOrderItem();
CopyObject.Copy(dtNewObject, newObject);
await ItemValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrderItem.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItem, AyaEvent.Created), ct);
await ItemSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrderItem> ItemGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrderItem
.Include(wi => wi.Expenses)
.Include(wi => wi.Labors)
.Include(wi => wi.Loans)
.Include(wi => wi.Parts)
.Include(wi => wi.PartRequests)
.Include(wi => wi.ScheduledUsers)
.Include(wi => wi.Tasks)
.Include(wi => wi.Travels)
.Include(wi => wi.Units)
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItem, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItem> ItemPutAsync(WorkOrderItem dtPutObject)
{
WorkOrderItem dbObject = await ct.WorkOrderItem.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
// make a snapshot of the original for validation but update the original to preserve workflow
WorkOrderItem SnapshotOfOriginalDBObj = new WorkOrderItem();
CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
//Replace the db object with the PUT object
CopyObject.Copy(dtPutObject, dbObject, "Id");
dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await ItemValidateAsync(dbObject, SnapshotOfOriginalDBObj);
if (HasErrors)
return null;
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await ItemExistsAsync(dtPutObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItem, AyaEvent.Modified), ct);
await ItemSearchIndexAsync(dbObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> ItemDeleteAsync(long id, Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction parentTransaction = null)
{
Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction transaction = null;
if (parentTransaction == null)
transaction = await ct.Database.BeginTransactionAsync();
try
{
WorkOrderItem dbObject = await ct.WorkOrderItem.SingleOrDefaultAsync(z => z.Id == id);
ItemValidateCanDelete(dbObject);
if (HasErrors)
return false;
//collect the child id's to delete
var ExpenseIds = await ct.WorkOrderItemExpense.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync();
var LaborIds = await ct.WorkOrderItemLabor.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync();
var LoanIds = await ct.WorkOrderItemLoan.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync();
var PartIds = await ct.WorkOrderItemPart.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync();
var PartRequestIds = await ct.WorkOrderItemPartRequest.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync();
var ScheduledUserIds = await ct.WorkOrderItemScheduledUser.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync();
var TaskIds = await ct.WorkOrderItemTask.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync();
var TravelIds = await ct.WorkOrderItemTravel.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync();
var UnitIds = await ct.WorkOrderItemUnit.Where(z => z.WorkOrderItemId == id).Select(z => z.Id).ToListAsync();
//Delete children
foreach (long ItemId in ExpenseIds)
if (!await ExpenseDeleteAsync(ItemId))
return false;
foreach (long ItemId in LaborIds)
if (!await LaborDeleteAsync(ItemId))
return false;
foreach (long ItemId in LoanIds)
if (!await LoanDeleteAsync(ItemId))
return false;
foreach (long ItemId in PartIds)
if (!await PartDeleteAsync(ItemId))
return false;
foreach (long ItemId in PartRequestIds)
if (!await PartRequestDeleteAsync(ItemId))
return false;
foreach (long ItemId in ScheduledUserIds)
if (!await ScheduledUserDeleteAsync(ItemId))
return false;
foreach (long ItemId in TaskIds)
if (!await TaskDeleteAsync(ItemId))
return false;
foreach (long ItemId in TravelIds)
if (!await TravelDeleteAsync(ItemId))
return false;
foreach (long ItemId in UnitIds)
if (!await UnitDeleteAsync(ItemId))
return false;
ct.WorkOrderItem.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItem, dbObject.Id, "wo:" + dbObject.WorkOrderId.ToString(), ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItem, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
//all good do the commit if it's ours
if (parentTransaction == null)
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;
}
finally
{
if (parentTransaction == null)
await transaction.DisposeAsync();
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task ItemValidateAsync(WorkOrderItem proposedObj, WorkOrderItem currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
//does it have a valid workorder id
if (proposedObj.WorkOrderId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderId");
else if (!await WorkOrderExistsAsync(proposedObj.WorkOrderId))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderId");
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItem.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);//note: this is passed only to add errors
//validate custom fields
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
}
}
private void ItemValidateCanDelete(WorkOrderItem obj)
{
if (obj == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return;
}
//re-check rights here necessary due to traversal delete from Principle object
if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItem))
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return;
}
}
private async Task ItemSearchIndexAsync(WorkOrderItem obj, bool isNew)
{
//SEARCH INDEXING
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItem);
SearchParams.AddText(obj.Notes).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> ItemGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItem.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
#endregion work order item level
/*
███████╗██╗ ██╗██████╗ ███████╗███╗ ██╗███████╗███████╗███████╗
██╔════╝╚██╗██╔╝██╔══██╗██╔════╝████╗ ██║██╔════╝██╔════╝██╔════╝
█████╗ ╚███╔╝ ██████╔╝█████╗ ██╔██╗ ██║███████╗█████╗ ███████╗
██╔══╝ ██╔██╗ ██╔═══╝ ██╔══╝ ██║╚██╗██║╚════██║██╔══╝ ╚════██║
███████╗██╔╝ ██╗██║ ███████╗██║ ╚████║███████║███████╗███████║
╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═══╝╚══════╝╚══════╝╚══════╝
*/
#region WorkOrderItemExpense level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> ExpenseExistsAsync(long id)
{
return await ct.WorkOrderItemExpense.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItemExpense> ExpenseCreateAsync(WorkOrderItemExpense newObject)
{
await ExpenseValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrderItemExpense.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemExpense, AyaEvent.Created), ct);
await ExpenseSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrderItemExpense> ExpenseGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrderItemExpense
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemExpense, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItemExpense> ExpensePutAsync(WorkOrderItemExpense dtPutObject)
{
WorkOrderItemExpense dbObject = await ct.WorkOrderItemExpense.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
// make a snapshot of the original for validation but update the original to preserve workflow
WorkOrderItemExpense SnapshotOfOriginalDBObj = new WorkOrderItemExpense();
CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
CopyObject.Copy(dtPutObject, dbObject, "Id");
dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await ExpenseValidateAsync(dbObject, SnapshotOfOriginalDBObj);
if (HasErrors) return null;
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await ExpenseExistsAsync(dtPutObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemExpense, AyaEvent.Modified), ct);
await ExpenseSearchIndexAsync(dbObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> ExpenseDeleteAsync(long id)
{
WorkOrderItemExpense dbObject = await ct.WorkOrderItemExpense.SingleOrDefaultAsync(z => z.Id == id);
ExpenseValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.WorkOrderItemExpense.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemExpense, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemExpense, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
await HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task ExpenseValidateAsync(WorkOrderItemExpense proposedObj, WorkOrderItemExpense currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
if (proposedObj.WorkOrderItemId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId");
else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId");
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemExpense.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);//note: this is passed only to add errors
//validate custom fields
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
}
}
private void ExpenseValidateCanDelete(WorkOrderItemExpense obj)
{
if (obj == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return;
}
//re-check rights here necessary due to traversal delete from Principle object
if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemExpense))
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return;
}
}
//////////////////////////////////////////////
//INDEXING
//
private async Task ExpenseSearchIndexAsync(WorkOrderItemExpense obj, bool isNew)
{
//SEARCH INDEXING
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemExpense);
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> ExpenseGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItemExpense.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
#endregion work order item LABOR level
/*
██╗ █████╗ ██████╗ ██████╗ ██████╗
██║ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗
██║ ███████║██████╔╝██║ ██║██████╔╝
██║ ██╔══██║██╔══██╗██║ ██║██╔══██╗
███████╗██║ ██║██████╔╝╚██████╔╝██║ ██║
╚══════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝
*/
#region WorkOrderItemLabor level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> LaborExistsAsync(long id)
{
return await ct.WorkOrderItemLabor.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItemLabor> LaborCreateAsync(WorkOrderItemLabor newObject)
{
await LaborValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrderItemLabor.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemLabor, AyaEvent.Created), ct);
await LaborSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrderItemLabor> LaborGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrderItemLabor
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemLabor, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItemLabor> LaborPutAsync(WorkOrderItemLabor dtPutObject)
{
WorkOrderItemLabor dbObject = await ct.WorkOrderItemLabor.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
// make a snapshot of the original for validation but update the original to preserve workflow
WorkOrderItemLabor SnapshotOfOriginalDBObj = new WorkOrderItemLabor();
CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
CopyObject.Copy(dtPutObject, dbObject, "Id");
dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await LaborValidateAsync(dbObject, SnapshotOfOriginalDBObj);
if (HasErrors) return null;
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await LaborExistsAsync(dtPutObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemLabor, AyaEvent.Modified), ct);
await LaborSearchIndexAsync(dbObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> LaborDeleteAsync(long id)
{
WorkOrderItemLabor dbObject = await ct.WorkOrderItemLabor.SingleOrDefaultAsync(z => z.Id == id);
LaborValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.WorkOrderItemLabor.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemLabor, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemLabor, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
await HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task LaborValidateAsync(WorkOrderItemLabor proposedObj, WorkOrderItemLabor currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
if (proposedObj.WorkOrderItemId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId");
else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId");
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemLabor.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);//note: this is passed only to add errors
//validate custom fields
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
}
}
private void LaborValidateCanDelete(WorkOrderItemLabor obj)
{
if (obj == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return;
}
//re-check rights here necessary due to traversal delete from Principle object
if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemLabor))
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return;
}
}
//////////////////////////////////////////////
//INDEXING
//
private async Task LaborSearchIndexAsync(WorkOrderItemLabor obj, bool isNew)
{
//SEARCH INDEXING
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemLabor);
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> LaborGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItemLabor.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
#endregion work order item LABOR level
/*
██╗ ██████╗ █████╗ ███╗ ██╗
██║ ██╔═══██╗██╔══██╗████╗ ██║
██║ ██║ ██║███████║██╔██╗ ██║
██║ ██║ ██║██╔══██║██║╚██╗██║
███████╗╚██████╔╝██║ ██║██║ ╚████║
*/
#region WorkOrderItemLoan level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> LoanExistsAsync(long id)
{
return await ct.WorkOrderItemLoan.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItemLoan> LoanCreateAsync(WorkOrderItemLoan newObject)
{
await LoanValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrderItemLoan.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemLoan, AyaEvent.Created), ct);
await LoanSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrderItemLoan> LoanGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrderItemLoan
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemLoan, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItemLoan> LoanPutAsync(WorkOrderItemLoan dtPutObject)
{
WorkOrderItemLoan dbObject = await ct.WorkOrderItemLoan.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
// make a snapshot of the original for validation but update the original to preserve workflow
WorkOrderItemLoan SnapshotOfOriginalDBObj = new WorkOrderItemLoan();
CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
CopyObject.Copy(dtPutObject, dbObject, "Id");
dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await LoanValidateAsync(dbObject, SnapshotOfOriginalDBObj);
if (HasErrors) return null;
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await LoanExistsAsync(dtPutObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemLoan, AyaEvent.Modified), ct);
await LoanSearchIndexAsync(dbObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> LoanDeleteAsync(long id)
{
WorkOrderItemLoan dbObject = await ct.WorkOrderItemLoan.SingleOrDefaultAsync(z => z.Id == id);
LoanValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.WorkOrderItemLoan.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemLoan, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemLoan, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
await HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task LoanValidateAsync(WorkOrderItemLoan proposedObj, WorkOrderItemLoan currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
if (proposedObj.WorkOrderItemId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId");
else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId");
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemLoan.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);//note: this is passed only to add errors
//validate custom fields
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
}
}
private void LoanValidateCanDelete(WorkOrderItemLoan obj)
{
if (obj == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return;
}
//re-check rights here necessary due to traversal delete from Principle object
if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemLoan))
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return;
}
}
//////////////////////////////////////////////
//INDEXING
//
private async Task LoanSearchIndexAsync(WorkOrderItemLoan obj, bool isNew)
{
//SEARCH INDEXING
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemLoan);
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> LoanGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItemLoan.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
#endregion work order item LABOR level
/*
██████╗ █████╗ ██████╗ ████████╗███████╗
██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝
██████╔╝███████║██████╔╝ ██║ ███████╗
██╔═══╝ ██╔══██║██╔══██╗ ██║ ╚════██║
██║ ██║ ██║██║ ██║ ██║ ███████║
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝
*/
#region WorkOrderItemPart level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> PartExistsAsync(long id)
{
return await ct.WorkOrderItemPart.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItemPart> CreatePartAsync(WorkOrderItemPart newObject)
{
await PartValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrderItemPart.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemPart, AyaEvent.Created), ct);
await PartSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrderItemPart> PartGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrderItemPart
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemPart, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItemPart> PartPutAsync(WorkOrderItemPart dtPutObject)
{
WorkOrderItemPart dbObject = await ct.WorkOrderItemPart.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
// make a snapshot of the original for validation but update the original to preserve workflow
WorkOrderItemPart SnapshotOfOriginalDBObj = new WorkOrderItemPart();
CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
CopyObject.Copy(dtPutObject, dbObject, "Id");
dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await PartValidateAsync(dbObject, SnapshotOfOriginalDBObj);
if (HasErrors) return null;
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await PartExistsAsync(dtPutObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemPart, AyaEvent.Modified), ct);
await PartSearchIndexAsync(dbObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> PartDeleteAsync(long id)
{
WorkOrderItemPart dbObject = await ct.WorkOrderItemPart.SingleOrDefaultAsync(z => z.Id == id);
PartValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.WorkOrderItemPart.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemPart, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemPart, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
await HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task PartValidateAsync(WorkOrderItemPart proposedObj, WorkOrderItemPart currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
if (proposedObj.WorkOrderItemId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId");
else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId");
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemPart.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);//note: this is passed only to add errors
//validate custom fields
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
}
}
private void PartValidateCanDelete(WorkOrderItemPart obj)
{
if (obj == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return;
}
//re-check rights here necessary due to traversal delete from Principle object
if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemPart))
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return;
}
}
private async Task PartSearchIndexAsync(WorkOrderItemPart obj, bool isNew)
{
//SEARCH INDEXING
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemPart);
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> PartGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItemPart.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
#endregion work order item LABOR level
/*
██████╗ █████╗ ██████╗ ████████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗███████╗███████╗████████╗███████╗
██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝ ██╔══██╗██╔════╝██╔═══██╗██║ ██║██╔════╝██╔════╝╚══██╔══╝██╔════╝
██████╔╝███████║██████╔╝ ██║█████╗██████╔╝█████╗ ██║ ██║██║ ██║█████╗ ███████╗ ██║ ███████╗
██╔═══╝ ██╔══██║██╔══██╗ ██║╚════╝██╔══██╗██╔══╝ ██║▄▄ ██║██║ ██║██╔══╝ ╚════██║ ██║ ╚════██║
██║ ██║ ██║██║ ██║ ██║ ██║ ██║███████╗╚██████╔╝╚██████╔╝███████╗███████║ ██║ ███████║
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝
*/
#region WorkOrderItemPartRequest level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> PartRequestExistsAsync(long id)
{
return await ct.WorkOrderItemPartRequest.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItemPartRequest> PartRequestCreateAsync(WorkOrderItemPartRequest newObject)
{
await PartRequestValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrderItemPartRequest.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemPartRequest, AyaEvent.Created), ct);
await PartRequestSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrderItemPartRequest> PartRequestGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrderItemPartRequest
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemPartRequest, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItemPartRequest> PartRequestPutAsync(WorkOrderItemPartRequest dtPutObject)
{
WorkOrderItemPartRequest dbObject = await ct.WorkOrderItemPartRequest.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
// make a snapshot of the original for validation but update the original to preserve workflow
WorkOrderItemPartRequest SnapshotOfOriginalDBObj = new WorkOrderItemPartRequest();
CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
CopyObject.Copy(dtPutObject, dbObject, "Id");
dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await PartRequestValidateAsync(dbObject, SnapshotOfOriginalDBObj);
if (HasErrors) return null;
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await PartRequestExistsAsync(dtPutObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemPartRequest, AyaEvent.Modified), ct);
await PartRequestSearchIndexAsync(dbObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> PartRequestDeleteAsync(long id)
{
WorkOrderItemPartRequest dbObject = await ct.WorkOrderItemPartRequest.SingleOrDefaultAsync(z => z.Id == id);
PartRequestValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.WorkOrderItemPartRequest.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemPartRequest, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemPartRequest, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
await HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task PartRequestValidateAsync(WorkOrderItemPartRequest proposedObj, WorkOrderItemPartRequest currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
if (proposedObj.WorkOrderItemId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId");
else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId");
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemPartRequest.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);//note: this is passed only to add errors
//validate custom fields
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
}
}
private void PartRequestValidateCanDelete(WorkOrderItemPartRequest obj)
{
if (obj == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return;
}
//re-check rights here necessary due to traversal delete from Principle object
if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemPartRequest))
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return;
}
}
//////////////////////////////////////////////
//INDEXING
//
private async Task PartRequestSearchIndexAsync(WorkOrderItemPartRequest obj, bool isNew)
{
//SEARCH INDEXING
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemPartRequest);
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> PartRequestGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItemPartRequest.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
#endregion work order item LABOR level
/*
███████╗ ██████╗██╗ ██╗███████╗██████╗ ██╗ ██╗██╗ ███████╗██████╗ ██╗ ██╗███████╗███████╗██████╗ ███████╗
██╔════╝██╔════╝██║ ██║██╔════╝██╔══██╗██║ ██║██║ ██╔════╝██╔══██╗ ██║ ██║██╔════╝██╔════╝██╔══██╗██╔════╝
███████╗██║ ███████║█████╗ ██║ ██║██║ ██║██║ █████╗ ██║ ██║█████╗██║ ██║███████╗█████╗ ██████╔╝███████╗
╚════██║██║ ██╔══██║██╔══╝ ██║ ██║██║ ██║██║ ██╔══╝ ██║ ██║╚════╝██║ ██║╚════██║██╔══╝ ██╔══██╗╚════██║
███████║╚██████╗██║ ██║███████╗██████╔╝╚██████╔╝███████╗███████╗██████╔╝ ╚██████╔╝███████║███████╗██║ ██║███████║
╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝
*/
#region WorkOrderItemScheduledUser level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> ScheduledUserExistsAsync(long id)
{
return await ct.WorkOrderItemScheduledUser.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItemScheduledUser> ScheduledUserCreateAsync(WorkOrderItemScheduledUser newObject)
{
await ScheduledUserValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrderItemScheduledUser.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemScheduledUser, AyaEvent.Created), ct);
await ScheduledUserSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrderItemScheduledUser> ScheduledUserGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrderItemScheduledUser
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemScheduledUser, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItemScheduledUser> ScheduledUserPutAsync(WorkOrderItemScheduledUser dtPutObject)
{
WorkOrderItemScheduledUser dbObject = await ct.WorkOrderItemScheduledUser.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
// make a snapshot of the original for validation but update the original to preserve workflow
WorkOrderItemScheduledUser SnapshotOfOriginalDBObj = new WorkOrderItemScheduledUser();
CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
CopyObject.Copy(dtPutObject, dbObject, "Id");
dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await ScheduledUserValidateAsync(dbObject, SnapshotOfOriginalDBObj);
if (HasErrors) return null;
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await ScheduledUserExistsAsync(dtPutObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemScheduledUser, AyaEvent.Modified), ct);
await ScheduledUserSearchIndexAsync(dbObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> ScheduledUserDeleteAsync(long id)
{
WorkOrderItemScheduledUser dbObject = await ct.WorkOrderItemScheduledUser.SingleOrDefaultAsync(z => z.Id == id);
ScheduledUserValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.WorkOrderItemScheduledUser.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemScheduledUser, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemScheduledUser, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
await HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task ScheduledUserValidateAsync(WorkOrderItemScheduledUser proposedObj, WorkOrderItemScheduledUser currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
if (proposedObj.WorkOrderItemId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId");
else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId");
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemScheduledUser.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);//note: this is passed only to add errors
//validate custom fields
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
}
}
private void ScheduledUserValidateCanDelete(WorkOrderItemScheduledUser obj)
{
if (obj == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return;
}
//re-check rights here necessary due to traversal delete from Principle object
if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemScheduledUser))
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return;
}
}
//////////////////////////////////////////////
//INDEXING
//
private async Task ScheduledUserSearchIndexAsync(WorkOrderItemScheduledUser obj, bool isNew)
{
//SEARCH INDEXING
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemScheduledUser);
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> ScheduledUserGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItemScheduledUser.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
#endregion work order item LABOR level
/*
████████╗ █████╗ ███████╗██╗ ██╗
╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
██║ ███████║███████╗█████╔╝
██║ ██╔══██║╚════██║██╔═██╗
██║ ██║ ██║███████║██║ ██╗
╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
*/
#region WorkOrderItemTask level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> TaskExistsAsync(long id)
{
return await ct.WorkOrderItemTask.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItemTask> TaskCreateAsync(WorkOrderItemTask newObject)
{
await TaskValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrderItemTask.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemTask, AyaEvent.Created), ct);
await TaskSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrderItemTask> TaskGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrderItemTask
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemTask, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItemTask> TaskPutAsync(WorkOrderItemTask dtPutObject)
{
WorkOrderItemTask dbObject = await ct.WorkOrderItemTask.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
// make a snapshot of the original for validation but update the original to preserve workflow
WorkOrderItemTask SnapshotOfOriginalDBObj = new WorkOrderItemTask();
CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
CopyObject.Copy(dtPutObject, dbObject, "Id");
dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await TaskValidateAsync(dbObject, SnapshotOfOriginalDBObj);
if (HasErrors) return null;
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await TaskExistsAsync(dtPutObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemTask, AyaEvent.Modified), ct);
await TaskSearchIndexAsync(dbObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> TaskDeleteAsync(long id)
{
WorkOrderItemTask dbObject = await ct.WorkOrderItemTask.SingleOrDefaultAsync(z => z.Id == id);
TaskValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.WorkOrderItemTask.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemTask, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemTask, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
await HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task TaskValidateAsync(WorkOrderItemTask proposedObj, WorkOrderItemTask currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
if (proposedObj.WorkOrderItemId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId");
else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId");
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemTask.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);//note: this is passed only to add errors
//validate custom fields
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
}
}
private void TaskValidateCanDelete(WorkOrderItemTask obj)
{
if (obj == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return;
}
//re-check rights here necessary due to traversal delete from Principle object
if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemTask))
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return;
}
}
//////////////////////////////////////////////
//INDEXING
//
private async Task TaskSearchIndexAsync(WorkOrderItemTask obj, bool isNew)
{
//SEARCH INDEXING
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemTask);
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> TaskGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItemTask.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
#endregion work order item LABOR level
/*
████████╗██████╗ █████╗ ██╗ ██╗███████╗██╗
╚══██╔══╝██╔══██╗██╔══██╗██║ ██║██╔════╝██║
██║ ██████╔╝███████║██║ ██║█████╗ ██║
██║ ██╔══██╗██╔══██║╚██╗ ██╔╝██╔══╝ ██║
██║ ██║ ██║██║ ██║ ╚████╔╝ ███████╗███████╗
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚══════╝
*/
#region WorkOrderItemTravel level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> TravelExistsAsync(long id)
{
return await ct.WorkOrderItemTravel.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItemTravel> TravelCreateAsync(WorkOrderItemTravel newObject)
{
await TravelValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrderItemTravel.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemTravel, AyaEvent.Created), ct);
await TravelSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrderItemTravel> TravelGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrderItemTravel
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemTravel, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItemTravel> TravelPutAsync(WorkOrderItemTravel dtPutObject)
{
WorkOrderItemTravel dbObject = await ct.WorkOrderItemTravel.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
// make a snapshot of the original for validation but update the original to preserve workflow
WorkOrderItemTravel SnapshotOfOriginalDBObj = new WorkOrderItemTravel();
CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
CopyObject.Copy(dtPutObject, dbObject, "Id");
dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await TravelValidateAsync(dbObject, SnapshotOfOriginalDBObj);
if (HasErrors) return null;
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await TravelExistsAsync(dtPutObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemTravel, AyaEvent.Modified), ct);
await TravelSearchIndexAsync(dbObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> TravelDeleteAsync(long id)
{
WorkOrderItemTravel dbObject = await ct.WorkOrderItemTravel.SingleOrDefaultAsync(z => z.Id == id);
TravelValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.WorkOrderItemTravel.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemTravel, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemTravel, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
await HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task TravelValidateAsync(WorkOrderItemTravel proposedObj, WorkOrderItemTravel currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
if (proposedObj.WorkOrderItemId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId");
else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId");
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemTravel.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);//note: this is passed only to add errors
//validate custom fields
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
}
}
private void TravelValidateCanDelete(WorkOrderItemTravel obj)
{
if (obj == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return;
}
//re-check rights here necessary due to traversal delete from Principle object
if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemTravel))
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return;
}
}
//////////////////////////////////////////////
//INDEXING
//
private async Task TravelSearchIndexAsync(WorkOrderItemTravel obj, bool isNew)
{
//SEARCH INDEXING
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemTravel);
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> TravelGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItemTravel.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
#endregion work order item LABOR level
/*
██╗ ██╗███╗ ██╗██╗████████╗
██║ ██║████╗ ██║██║╚══██╔══╝
██║ ██║██╔██╗ ██║██║ ██║
██║ ██║██║╚██╗██║██║ ██║
╚██████╔╝██║ ╚████║██║ ██║
╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝
*/
#region WorkOrderItemUnit level
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> UnitExistsAsync(long id)
{
return await ct.WorkOrderItemUnit.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<WorkOrderItemUnit> UnitCreateAsync(WorkOrderItemUnit newObject)
{
await UnitValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
await ct.WorkOrderItemUnit.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, AyaType.WorkOrderItemUnit, AyaEvent.Created), ct);
await UnitSearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
await HandlePotentialNotificationEvent(AyaEvent.Created, newObject);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GET
//
internal async Task<WorkOrderItemUnit> UnitGetAsync(long id, bool logTheGetEvent = true)
{
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
var ret =
await ct.WorkOrderItemUnit
.SingleOrDefaultAsync(z => z.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, AyaType.WorkOrderItemUnit, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<WorkOrderItemUnit> UnitPutAsync(WorkOrderItemUnit dtPutObject)
{
WorkOrderItemUnit dbObject = await ct.WorkOrderItemUnit.SingleOrDefaultAsync(z => z.Id == dtPutObject.Id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
// make a snapshot of the original for validation but update the original to preserve workflow
WorkOrderItemUnit SnapshotOfOriginalDBObj = new WorkOrderItemUnit();
CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
CopyObject.Copy(dtPutObject, dbObject, "Id");
dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
ct.Entry(dbObject).OriginalValues["Concurrency"] = dtPutObject.Concurrency;
await UnitValidateAsync(dbObject, SnapshotOfOriginalDBObj);
if (HasErrors) return null;
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await UnitExistsAsync(dtPutObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, AyaType.WorkOrderItemUnit, AyaEvent.Modified), ct);
await UnitSearchIndexAsync(dbObject, false);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
await HandlePotentialNotificationEvent(AyaEvent.Modified, dbObject, SnapshotOfOriginalDBObj);
return dbObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> UnitDeleteAsync(long id)
{
WorkOrderItemUnit dbObject = await ct.WorkOrderItemUnit.SingleOrDefaultAsync(z => z.Id == id);
UnitValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.WorkOrderItemUnit.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, AyaType.WorkOrderItemUnit, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, AyaType.WorkOrderItemUnit, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
await HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task UnitValidateAsync(WorkOrderItemUnit proposedObj, WorkOrderItemUnit currentObj)
{
//run validation and biz rules
bool isNew = currentObj == null;
if (proposedObj.WorkOrderItemId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "WorkOrderItemId");
else if (!await ItemExistsAsync(proposedObj.WorkOrderItemId))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "WorkOrderItemId");
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemUnit.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);//note: this is passed only to add errors
//validate custom fields
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
}
}
private void UnitValidateCanDelete(WorkOrderItemUnit obj)
{
if (obj == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return;
}
//re-check rights here necessary due to traversal delete from Principle object
if (!Authorized.HasDeleteRole(CurrentUserRoles, AyaType.WorkOrderItemUnit))
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return;
}
}
//////////////////////////////////////////////
//INDEXING
//
private async Task UnitSearchIndexAsync(WorkOrderItemUnit obj, bool isNew)
{
//SEARCH INDEXING
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, AyaType.WorkOrderItemUnit);
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
if (isNew)
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
else
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
}
public async Task<Search.SearchIndexProcessObjectParameters> UnitGetSearchResultSummary(long id)
{
var obj = await ct.WorkOrderItemUnit.SingleOrDefaultAsync(z => z.Id == id);
var SearchParams = new Search.SearchIndexProcessObjectParameters();
if (obj != null)
SearchParams.AddText(obj.Notes).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
return SearchParams;
}
#endregion work order item LABOR level
#region Utility
public async Task<ICoreBizObjectModel> GetWorkOrderGraphItem(AyaType ayaType, long id)
{
switch (ayaType)
{
case AyaType.WorkOrder:
return await WorkOrderGetAsync(id, false) as ICoreBizObjectModel;
case AyaType.WorkOrderItem:
return await ItemGetAsync(id, false);
case AyaType.WorkOrderItemExpense:
return await ExpenseGetAsync(id, false);
case AyaType.WorkOrderItemLabor:
return await LaborGetAsync(id, false);
case AyaType.WorkOrderItemLoan:
return await LoanGetAsync(id, false);
case AyaType.WorkOrderItemPart:
return await PartGetAsync(id, false);
case AyaType.WorkOrderItemPartRequest:
return await PartRequestGetAsync(id, false);
case AyaType.WorkOrderItemScheduledUser:
return await ScheduledUserGetAsync(id, false);
case AyaType.WorkOrderItemTask:
return await TaskGetAsync(id, false);
case AyaType.WorkOrderItemTravel:
return await TravelGetAsync(id, false);
case AyaType.WorkOrderItemUnit:
return await UnitGetAsync(id, false);
default:
throw new System.ArgumentOutOfRangeException($"WorkOrder::GetWorkOrderGraphItem -> Invalid ayaType{ayaType}");
}
}
public async Task<ICoreBizObjectModel> PutWorkOrderGraphItem(AyaType ayaType, ICoreBizObjectModel o)
{
ClearErrors();
switch (ayaType)
{
case AyaType.WorkOrder:
if (o is WorkOrder)
{
WorkOrder dto = new WorkOrder();
CopyObject.Copy(o, dto);
return await WorkOrderPutAsync((WorkOrder)dto);
}
return await WorkOrderPutAsync((WorkOrder)o) as ICoreBizObjectModel;
case AyaType.WorkOrderItem:
if (o is WorkOrderItem)
{
WorkOrderItem dto = new WorkOrderItem();
CopyObject.Copy(o, dto);
return await ItemPutAsync((WorkOrderItem)dto);
}
return await ItemPutAsync((WorkOrderItem)o);
case AyaType.WorkOrderItemExpense:
return await ExpensePutAsync((WorkOrderItemExpense)o);
case AyaType.WorkOrderItemLabor:
return await LaborPutAsync((WorkOrderItemLabor)o);
case AyaType.WorkOrderItemLoan:
return await LoanPutAsync((WorkOrderItemLoan)o);
case AyaType.WorkOrderItemPart:
return await PartPutAsync((WorkOrderItemPart)o);
case AyaType.WorkOrderItemPartRequest:
return await PartRequestPutAsync((WorkOrderItemPartRequest)o);
case AyaType.WorkOrderItemScheduledUser:
return await ScheduledUserPutAsync((WorkOrderItemScheduledUser)o);
case AyaType.WorkOrderItemTask:
return await TaskPutAsync((WorkOrderItemTask)o);
case AyaType.WorkOrderItemTravel:
return await TravelPutAsync((WorkOrderItemTravel)o);
case AyaType.WorkOrderItemUnit:
return await UnitPutAsync((WorkOrderItemUnit)o);
default:
throw new System.ArgumentOutOfRangeException($"WorkOrder::PutWorkOrderGraphItem -> Invalid ayaType{ayaType}");
}
}
public async Task<bool> DeleteWorkOrderGraphItem(AyaType ayaType, long id)
{
switch (ayaType)
{
case AyaType.WorkOrder:
return await WorkOrderDeleteAsync(id);
case AyaType.WorkOrderItem:
return await ItemDeleteAsync(id);
case AyaType.WorkOrderItemExpense:
return await ExpenseDeleteAsync(id);
case AyaType.WorkOrderItemLabor:
return await LaborDeleteAsync(id);
case AyaType.WorkOrderItemLoan:
return await LoanDeleteAsync(id);
case AyaType.WorkOrderItemPart:
return await PartDeleteAsync(id);
case AyaType.WorkOrderItemPartRequest:
return await PartRequestDeleteAsync(id);
case AyaType.WorkOrderItemScheduledUser:
return await ScheduledUserDeleteAsync(id);
case AyaType.WorkOrderItemTask:
return await TaskDeleteAsync(id);
case AyaType.WorkOrderItemTravel:
return await TravelDeleteAsync(id);
case AyaType.WorkOrderItemUnit:
return await UnitDeleteAsync(id);
default:
throw new System.ArgumentOutOfRangeException($"WorkOrder::GetWorkOrderGraphItem -> Invalid ayaType{ayaType}");
}
}
#endregion utility
/////////////////////////////////////////////////////////////////////
}//eoc
}//eons