From 4f3a2b8b808cabaf1029cf0b5e717b2988d628ce Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Wed, 16 Dec 2020 15:19:40 +0000 Subject: [PATCH] --- .../AyaNova/Controllers/ProjectController.cs | 1 + server/AyaNova/biz/ProjectBiz.cs | 159 +++++++++++++++++- server/AyaNova/biz/VendorBiz.cs | 1 - server/AyaNova/models/Project.cs | 33 +++- server/AyaNova/util/AySchema.cs | 4 +- server/AyaNova/util/DbUtil.cs | 2 + 6 files changed, 194 insertions(+), 6 deletions(-) diff --git a/server/AyaNova/Controllers/ProjectController.cs b/server/AyaNova/Controllers/ProjectController.cs index b60aa8c9..e2fd8907 100644 --- a/server/AyaNova/Controllers/ProjectController.cs +++ b/server/AyaNova/Controllers/ProjectController.cs @@ -9,6 +9,7 @@ using AyaNova.Api.ControllerHelpers; using AyaNova.Biz; + namespace AyaNova.Api.Controllers { [ApiController] diff --git a/server/AyaNova/biz/ProjectBiz.cs b/server/AyaNova/biz/ProjectBiz.cs index 412c01e3..aa6dd941 100644 --- a/server/AyaNova/biz/ProjectBiz.cs +++ b/server/AyaNova/biz/ProjectBiz.cs @@ -1,12 +1,17 @@ +using System; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using System.Linq; using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; namespace AyaNova.Biz { - internal class ProjectBiz : BizObject, ISearchAbleObject + internal class ProjectBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, IImportAbleObject { internal ProjectBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { @@ -246,11 +251,159 @@ namespace AyaNova.Biz //////////////////////////////////////////////////////////////////////////////////////////////// - //JOB / OPERATIONS + //REPORTING + // + public async Task GetReportData(long[] idList) + { + JArray ReportData = new JArray(); + while (idList.Any()) + { + var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); + idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); + //query for this batch, comes back in db natural order unfortunately + var batchResults = await ct.Project.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 (Project w in orderedList) + { + var jo = JObject.FromObject(w); + if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"])) + jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); + ReportData.Add(jo); + } + } + return ReportData; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT // - //Other job handlers here... + public async Task GetExportData(long[] idList) + { + //for now just re-use the report data code + //this may turn out to be the pattern for most biz object types but keeping it seperate allows for custom usage from time to time + return await GetReportData(idList); + } + + + + + public async Task> ImportData(JArray ja) + { + List ImportResult = new List(); + string ImportTag = $"imported-{FileUtil.GetSafeDateFileName()}"; + + var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new AyaNova.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + foreach (JObject j in ja) + { + var w = j.ToObject(jsset); + if (j["CustomFields"] != null) + w.CustomFields = j["CustomFields"].ToString(); + w.Tags.Add(ImportTag);//so user can find them all and revert later if necessary + var res = await CreateAsync(w); + if (res == null) + { + ImportResult.Add($"* {w.Name} - {this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"{w.Name} - ok"); + } + } + return ImportResult; + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + //Hand off the particular job to the corresponding processing code + //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so + //basically any error condition during job processing should throw up an exception if it can't be handled + switch (job.JobType) + { + case JobType.BatchCoreObjectOperation: + await ProcessBatchJobAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"ProjectBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + } + + + + private async Task ProcessBatchJobAsync(OpsJob job) + { + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob {job.SubType}"); + List idList = new List(); + long FailedObjectCount = 0; + JObject jobData = JObject.Parse(job.JobInfo); + if (jobData.ContainsKey("idList")) + idList = ((JArray)jobData["idList"]).ToObject>(); + else + idList = await ct.Project.Select(z => z.Id).ToListAsync(); + bool SaveIt = false; + foreach (long id in idList) + { + try + { + SaveIt = false; + ClearErrors(); + Project o = null; + //save a fetch if it's a delete + if (job.SubType != JobSubType.Delete) + o = await GetAsync(id, false); + switch (job.SubType) + { + case JobSubType.TagAddAny: + case JobSubType.TagAdd: + case JobSubType.TagRemoveAny: + case JobSubType.TagRemove: + case JobSubType.TagReplaceAny: + case JobSubType.TagReplace: + SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); + break; + case JobSubType.Delete: + if (!await DeleteAsync(id)) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + break; + default: + throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); + } + if (SaveIt) + { + o = await PutAsync(o); + if (o == null) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + } + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors id({id})"); + await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + } + } + await JobsBiz.LogJobAsync(job.GId, $"LT:BatchJob {job.SubType} {idList.Count}{(FailedObjectCount > 0 ? " - LT:Failed " + FailedObjectCount : "")}"); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + } + + + ///////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/biz/VendorBiz.cs b/server/AyaNova/biz/VendorBiz.cs index 29626555..29ea1493 100644 --- a/server/AyaNova/biz/VendorBiz.cs +++ b/server/AyaNova/biz/VendorBiz.cs @@ -54,7 +54,6 @@ namespace AyaNova.Biz await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); await SearchIndexAsync(newObject, true); await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); - await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } diff --git a/server/AyaNova/models/Project.cs b/server/AyaNova/models/Project.cs index e7b7a023..b84a3be3 100644 --- a/server/AyaNova/models/Project.cs +++ b/server/AyaNova/models/Project.cs @@ -1,8 +1,9 @@ +using System; using System.Collections.Generic; +using AyaNova.Biz; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; -using AyaNova.Biz; namespace AyaNova.Models { @@ -22,10 +23,15 @@ namespace AyaNova.Models public string CustomFields { get; set; } public List Tags { get; set; } + public DateTime? DateStarted { get; set; } + public DateTime? DateCompleted { get; set; } + public long? ProjectOverseerId { get; set; } + public string AccountNumber { get; set; } public Project() { Tags = new List(); + DateStarted = DateTime.UtcNow; } [NotMapped, JsonIgnore] @@ -34,3 +40,28 @@ namespace AyaNova.Models }//eoc }//eons +/* + [AID] [uniqueidentifier] NOT NULL, + [ACREATED] [datetime] NOT NULL, + [AMODIFIED] [datetime] NOT NULL, + [ACREATOR] [uniqueidentifier] NOT NULL, + [AMODIFIER] [uniqueidentifier] NOT NULL, + [ANAME] [nvarchar](255) NULL, + [ADATECOMPLETED] [datetime] NULL, + [ANOTES] [ntext] NULL, + [APROJECTOVERSEERID] [uniqueidentifier] NULL, + [AACCOUNTNUMBER] [nvarchar](255) NULL, + [ADATESTARTED] [datetime] NULL, + [AACTIVE] [bit] NOT NULL, + [ACUSTOM1] [ntext] NULL, + [ACUSTOM2] [ntext] NULL, + [ACUSTOM3] [ntext] NULL, + [ACUSTOM4] [ntext] NULL, + [ACUSTOM5] [ntext] NULL, + [ACUSTOM6] [ntext] NULL, + [ACUSTOM7] [ntext] NULL, + [ACUSTOM8] [ntext] NULL, + [ACUSTOM9] [ntext] NULL, + [ACUSTOM0] [ntext] NULL, + [AREGIONID] [uniqueidentifier] NULL, +*/ \ No newline at end of file diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index ea31abaf..25856efe 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -541,7 +541,9 @@ $BODY$; //PROJECT await ExecQueryAsync("CREATE TABLE aproject (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text not null unique, active bool, " + - "notes text, wiki text, customfields text, tags varchar(255) ARRAY )"); + "notes text, wiki text, customfields text, tags varchar(255) ARRAY, " + + "datestarted timestamp null, datecompleted timestamp null, projectoverseerid bigint null references auser(id), accountnumber text)"); + await ExecQueryAsync("CREATE UNIQUE INDEX aproject_name_id_idx ON aproject (id, name);"); await ExecQueryAsync("CREATE INDEX aproject_tags ON aproject using GIN(tags)"); diff --git a/server/AyaNova/util/DbUtil.cs b/server/AyaNova/util/DbUtil.cs index 575c63af..17654c0c 100644 --- a/server/AyaNova/util/DbUtil.cs +++ b/server/AyaNova/util/DbUtil.cs @@ -393,6 +393,8 @@ namespace AyaNova.Util apiServerState.ResumePriorState(); + _log.LogInformation("Database erase completed"); + }