From 4b9f303f8632ab5256b4930e1dfb830e996c9f5b Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Wed, 2 Dec 2020 18:29:18 +0000 Subject: [PATCH] --- server/AyaNova/biz/AyaFormFieldDefinitions.cs | 28 ++++ server/AyaNova/biz/HeadOfficeBiz.cs | 150 +++++++++++++++++- server/AyaNova/models/HeadOffice.cs | 65 ++++++++ server/AyaNova/util/AySchema.cs | 9 +- 4 files changed, 246 insertions(+), 6 deletions(-) diff --git a/server/AyaNova/biz/AyaFormFieldDefinitions.cs b/server/AyaNova/biz/AyaFormFieldDefinitions.cs index 79cf2372..b5f4dd18 100644 --- a/server/AyaNova/biz/AyaFormFieldDefinitions.cs +++ b/server/AyaNova/biz/AyaFormFieldDefinitions.cs @@ -192,6 +192,34 @@ namespace AyaNova.Biz l.Add(new AyaFormFieldDefinition { TKey = "Wiki", FieldKey = "Wiki" }); l.Add(new AyaFormFieldDefinition { TKey = "Attachments", FieldKey = "Attachments" }); + + //HEAD-OFFICE FIELDS + l.Add(new AyaFormFieldDefinition { TKey = "WebAddress", FieldKey = "WebAddress" }); + l.Add(new AyaFormFieldDefinition { TKey = "HeadOfficeAccountNumber", FieldKey = "AccountNumber" }); + l.Add(new AyaFormFieldDefinition { TKey = "UsesBanking", FieldKey = "UsesBanking" }); + l.Add(new AyaFormFieldDefinition { TKey = "Contract", FieldKey = "ContractId" }); + l.Add(new AyaFormFieldDefinition { TKey = "ContractExpires", FieldKey = "ContractExpires" }); + l.Add(new AyaFormFieldDefinition { TKey = "HeadOfficePhone1", FieldKey = "Phone1" }); + l.Add(new AyaFormFieldDefinition { TKey = "HeadOfficePhone2", FieldKey = "Phone2" }); + l.Add(new AyaFormFieldDefinition { TKey = "HeadOfficePhone3", FieldKey = "Phone3" }); + l.Add(new AyaFormFieldDefinition { TKey = "HeadOfficePhone4", FieldKey = "Phone4" }); + l.Add(new AyaFormFieldDefinition { TKey = "HeadOfficePhone5", FieldKey = "Phone5" }); + l.Add(new AyaFormFieldDefinition { TKey = "HeadOfficeEmail", FieldKey = "EmailAddress" }); + + //ADDRESS FIELDS + l.Add(new AyaFormFieldDefinition { TKey = "AddressPostalDeliveryAddress", FieldKey = "PostAddress" }); + l.Add(new AyaFormFieldDefinition { TKey = "AddressPostalCity", FieldKey = "PostCity" }); + l.Add(new AyaFormFieldDefinition { TKey = "AddressPostalStateProv", FieldKey = "PostRegion" }); + l.Add(new AyaFormFieldDefinition { TKey = "AddressPostalCountry", FieldKey = "PostCountry" }); + l.Add(new AyaFormFieldDefinition { TKey = "AddressPostalPostal", FieldKey = "PostCode" }); + l.Add(new AyaFormFieldDefinition { TKey = "AddressDeliveryAddress", FieldKey = "Address" }); + l.Add(new AyaFormFieldDefinition { TKey = "AddressCity", FieldKey = "City" }); + l.Add(new AyaFormFieldDefinition { TKey = "AddressStateProv", FieldKey = "Region" }); + l.Add(new AyaFormFieldDefinition { TKey = "AddressCountry", FieldKey = "Country" }); + l.Add(new AyaFormFieldDefinition { TKey = "AddressLatitude", FieldKey = "Latitude" }); + l.Add(new AyaFormFieldDefinition { TKey = "AddressLongitude", FieldKey = "Longitude" }); + + l.Add(new AyaFormFieldDefinition { TKey = "HeadOfficeCustom1", FieldKey = "HeadOfficeCustom1", IsCustomField = true }); l.Add(new AyaFormFieldDefinition { TKey = "HeadOfficeCustom2", FieldKey = "HeadOfficeCustom2", IsCustomField = true }); l.Add(new AyaFormFieldDefinition { TKey = "HeadOfficeCustom3", FieldKey = "HeadOfficeCustom3", IsCustomField = true }); diff --git a/server/AyaNova/biz/HeadOfficeBiz.cs b/server/AyaNova/biz/HeadOfficeBiz.cs index 23385065..185c50c9 100644 --- a/server/AyaNova/biz/HeadOfficeBiz.cs +++ b/server/AyaNova/biz/HeadOfficeBiz.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 HeadOfficeBiz : BizObject, ISearchAbleObject + internal class HeadOfficeBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, IImportAbleObject { internal HeadOfficeBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { @@ -207,7 +212,7 @@ namespace AyaNova.Biz if (string.IsNullOrWhiteSpace(proposedObj.Name)) AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); - + //If name is otherwise OK, check that name is unique if (!PropertyHasErrors("Name")) @@ -241,12 +246,149 @@ 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.HeadOffice.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 (HeadOffice 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.BulkCoreBizObjectOperation: + await ProcessBulkJobAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"HeadOfficeBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + } + + + + private async Task ProcessBulkJobAsync(OpsJob job) + { + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"Bulk job {job.SubType} started..."); + List idList = new List(); + long ProcessedObjectCount = 0; + JObject jobData = JObject.Parse(job.JobInfo); + if (jobData.ContainsKey("idList")) + idList = ((JArray)jobData["idList"]).ToObject>(); + else + idList = await ct.HeadOffice.Select(z => z.Id).ToListAsync(); + bool SaveIt = false; + foreach (long id in idList) + { + try + { + SaveIt = false; + ClearErrors(); + var 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.ProcessBulkTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); + break; + default: + throw new System.ArgumentOutOfRangeException($"ProcessBulkJob -> Invalid job Subtype{job.SubType}"); + } + if (SaveIt) + { + o = await PutAsync(o); + if (o == null) + await JobsBiz.LogJobAsync(job.GId, $"Error processing item {id}: {GetErrorsAsString()}"); + else + ProcessedObjectCount++; + } + else + ProcessedObjectCount++; + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(job.GId, $"Error processing item {id}"); + await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + } + } + await JobsBiz.LogJobAsync(job.GId, $"Bulk job {job.SubType} processed {ProcessedObjectCount} of {idList.Count}"); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + } + + ///////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/models/HeadOffice.cs b/server/AyaNova/models/HeadOffice.cs index 5ed7ed13..fb0b6310 100644 --- a/server/AyaNova/models/HeadOffice.cs +++ b/server/AyaNova/models/HeadOffice.cs @@ -24,9 +24,39 @@ namespace AyaNova.Models public List Tags { get; set; } + + public string WebAddress { get; set; } + public string AccountNumber { get; set; } + public bool UsesBanking { get; set; } + public long? ContractId { get; set; } + public DateTime? ContractExpires { get; set; } + public string Phone1 { get; set; } + public string Phone2 { get; set; } + public string Phone3 { get; set; } + public string Phone4 { get; set; } + public string Phone5 { get; set; } + public string EmailAddress { get; set; } + + //POSTAL ADDRESS + public string PostAddress { get; set; } + public string PostCity { get; set; } + public string PostRegion { get; set; } + public string PostCountry { get; set; } + public string PostCode { get; set; } + + //PHYSICAL ADDRESS + public string Address { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string Country { get; set; } + public decimal? Latitude { get; set; } + public decimal? Longitude { get; set; } + + public HeadOffice() { Tags = new List(); + UsesBanking = false; } [NotMapped, JsonIgnore] @@ -35,3 +65,38 @@ namespace AyaNova.Models }//eoc }//eons +/* + [AID] [uniqueidentifier] NOT NULL, + [ACREATED] [datetime] NULL, + [AMODIFIED] [datetime] NULL, + [AACTIVE] [bit] NOT NULL, + [ACREATOR] [uniqueidentifier] NULL, + [AMODIFIER] [uniqueidentifier] NULL, + [ANAME] [nvarchar](255) NULL, + [AWEBADDRESS] [nvarchar](255) NULL, + [ACLIENTGROUPID] [uniqueidentifier] NULL, + [ANOTES] [ntext] NULL, + [AREGIONID] [uniqueidentifier] NULL, + [AACCOUNTNUMBER] [nvarchar](255) 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, + [AUSESBANKING] [bit] NOT NULL, + [ACONTRACTID] [uniqueidentifier] NULL, + [ACONTRACTEXPIRES] [datetime] NULL, + [ACONTACTNOTES] [ntext] NULL, + [ACONTACT] [nvarchar](500) NULL, + [APHONE1] [nvarchar](255) NULL, + [APHONE2] [nvarchar](255) NULL, + [APHONE3] [nvarchar](255) NULL, + [APHONE4] [nvarchar](255) NULL, + [APHONE5] [nvarchar](255) NULL, + [AEMAIL] [nvarchar](255) NULL, +*/ \ No newline at end of file diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 63b9fc51..62c85e71 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -22,7 +22,7 @@ namespace AyaNova.Util //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImporting WHEN NEW TABLES ADDED!!!! private const int DESIRED_SCHEMA_LEVEL = 15; - internal const long EXPECTED_COLUMN_COUNT = 456; + internal const long EXPECTED_COLUMN_COUNT = 478; internal const long EXPECTED_INDEX_COUNT = 145; //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImporting WHEN NEW TABLES ADDED!!!! @@ -508,7 +508,12 @@ $BODY$; //HEADOFFICE await ExecQueryAsync("CREATE TABLE aheadoffice (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," + + "webaddress text, accountnumber text, usesbanking bool, contractexpires timestamp null, contractid bigint null references acontract(id), " + + "phone1 text, phone2 text, phone3 text, phone4 text, phone5 text, emailaddress text, " + + "postaddress text, postcity text, postregion text, postcountry text, postcode text, address text, city text, region text, country text, latitude decimal(8,6), longitude decimal(9,6)" + + + " )"); await ExecQueryAsync("CREATE UNIQUE INDEX aheadoffice_name_id_idx ON aheadoffice (id, name);"); await ExecQueryAsync("CREATE INDEX aheadoffice_tags ON aheadoffice using GIN(tags)"); await ExecQueryAsync("ALTER TABLE acustomer add column headofficeid bigint null references aheadoffice");