diff --git a/server/biz/TrialRequestStatus.cs b/server/biz/TrialRequestStatus.cs new file mode 100644 index 0000000..aa16a33 --- /dev/null +++ b/server/biz/TrialRequestStatus.cs @@ -0,0 +1,10 @@ +namespace Sockeye.Biz +{ + public enum TrialRequestStatus + { + NotSet = 0, + Approved = 1, + Rejected = 2 + } + +}//eons \ No newline at end of file diff --git a/server/biz/VendorBiz.cs b/server/biz/VendorBiz.cs new file mode 100644 index 0000000..03dc88b --- /dev/null +++ b/server/biz/VendorBiz.cs @@ -0,0 +1,575 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Linq; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Sockeye.Biz +{ + internal class VendorBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, IImportAbleObject, INotifiableObject + { + internal VendorBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.Vendor; + } + + internal static VendorBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new VendorBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new VendorBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Vendor.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(Vendor newObject) + { + await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.Vendor.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + await HandlePotentialNotificationEvent(SockEvent.Created, newObject); + return newObject; + } + } + + // //////////////////////////////////////////////////////////////////////////////////////////////// + // //DUPLICATE + // // + // internal async Task DuplicateAsync(long id) + // { + // var dbObject = await GetAsync(id, false); + // if (dbObject == null) + // { + // AddError(ApiErrorCode.NOT_FOUND, "id"); + // return null; + // } + // var newObject = new Vendor(); + // CopyObject.Copy(dbObject, newObject, "Wiki"); + // string newUniqueName = string.Empty; + // bool NotUnique = true; + // long l = 1; + // do + // { + // newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObject.Name, l++, 255); + // NotUnique = await ct.Vendor.AnyAsync(z => z.Name == newUniqueName); + // } while (NotUnique); + // newObject.Name = newUniqueName; + // newObject.Id = 0; + // newObject.Concurrency = 0; + // await ct.Vendor.AddAsync(newObject); + // await ct.SaveChangesAsync(); + // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + // await SearchIndexAsync(newObject, true); + // await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + // await HandlePotentialNotificationEvent(SockEvent.Created, newObject); + // return newObject; + // } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Vendor.AsNoTracking().SingleOrDefaultAsync(z => z.Id == id); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(Vendor putObject) + { + var dbObject = await GetAsync(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 ValidateAsync(putObject, dbObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + await SearchIndexAsync(putObject, false); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + await HandlePotentialNotificationEvent(SockEvent.Modified, putObject, dbObject); + return putObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + Vendor dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + await ValidateCanDeleteAsync(dbObject); + if (HasErrors) + return false; + { + var IDList = await ct.Review.AsNoTracking().Where(x => x.AType == SockType.Vendor && x.ObjectId == id).Select(x => x.Id).ToListAsync(); + if (IDList.Count() > 0) + { + ReviewBiz b = new ReviewBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in IDList) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"Review [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + } + ct.Vendor.Remove(dbObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, 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(SockEvent.Deleted, dbObject); + return true; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET LIST FOR QBI MAPPING + // + internal async Task> GetNameIdActiveItemsAsync() + { + return await ct.Vendor.AsNoTracking().OrderBy(x => x.Name).Select(x => new NameIdActiveItem { Name = x.Name, Id = x.Id, Active = x.Active }).ToListAsync(); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(Vendor obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id, SockType specificType) + { + var obj = await GetAsync(id, false); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(Vendor obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Notes) + .AddText(obj.Name) + .AddText(obj.Wiki) + .AddText(obj.Tags) + .AddText(obj.WebAddress) + .AddText(obj.Contact) + .AddText(obj.ContactNotes) + .AddText(obj.AlertNotes) + .AddText(obj.AccountNumber) + .AddText(obj.Phone1) + .AddText(obj.Phone2) + .AddText(obj.Phone3) + .AddText(obj.Phone4) + .AddText(obj.Phone5) + .AddText(obj.EmailAddress) + .AddText(obj.PostAddress) + .AddText(obj.PostCity) + .AddText(obj.PostRegion) + .AddText(obj.PostCountry) + .AddText(obj.PostCode) + .AddText(obj.Address) + .AddText(obj.City) + .AddText(obj.Region) + .AddText(obj.Country) + .AddText(obj.AddressPostal) + .AddCustomFields(obj.CustomFields); + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + private async Task ValidateAsync(Vendor proposedObj, Vendor currentObj) + { + + //skip validation if seeding + if (ServerBootConfig.SEEDING) return; + + bool isNew = currentObj == null; + + //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.Vendor.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 == SockType.Vendor.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 async Task ValidateCanDeleteAsync(Vendor inObj) + { + //Referential integrity + //FOREIGN KEY CHECKS + if (await ct.User.AnyAsync(m => m.VendorId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("User")); + if (await ct.UnitModel.AnyAsync(m => m.VendorId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("UnitModel")); + if (await ct.Unit.AnyAsync(m => m.PurchasedFromVendorId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Unit")); + + + //part has three potential references, if any match that's good enough since it's the same message + if (await ct.Part.AnyAsync(z => z.ManufacturerId == inObj.Id) == true) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Part")); + else if (await ct.Part.AnyAsync(z => z.WholeSalerId == inObj.Id) == true) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Part")); + else if (await ct.Part.AnyAsync(z => z.AlternativeWholeSalerId == inObj.Id) == true) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Part")); + + if (await ct.PurchaseOrder.AnyAsync(m => m.VendorId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("PurchaseOrder")); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORTING + // + public async Task GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + var idList = dataListSelectedRequest.SelectedRowIds; + 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.Vendor.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; + batchResults = null; + foreach (Vendor w in orderedList) + { + if (!ReportRenderManager.KeepGoing(jobId)) return null; + var jo = JObject.FromObject(w); + if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"])) + jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); + ReportData.Add(jo); + } + orderedList = null; + } + return ReportData; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT + // + + + public async Task GetExportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + //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(dataListSelectedRequest, jobId); + } + + + + + public async Task> ImportData(AyImportData importData) + { + List ImportResult = new List(); + string ImportTag = ImportUtil.GetImportTag(); + //ignore these fields + var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + foreach (JObject j in importData.Data) + { + try + { + long existingId = await ct.Vendor.AsNoTracking().Where(z => z.Name == (string)j["Name"]).Select(x => x.Id).FirstOrDefaultAsync(); + if (existingId == 0) + { + if (importData.DoImport) + { + //import this record + var Target = j.ToObject(jsset); + Target.Tags.Add(ImportTag); + var res = await CreateAsync(Target); + if (res == null) + { + ImportResult.Add($"❌ {Target.Name}\r\n{this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"✔️ {Target.Name}"); + } + } + } + else + { + if (importData.DoUpdate) + { + //update this record with any data provided + //load existing record + var Target = await GetAsync((long)existingId); + var Source = j.ToObject(jsset); + var propertiesToUpdate = j.Properties().Select(p => p.Name).ToList(); + propertiesToUpdate.Remove("Name"); + ImportUtil.Update(Source, Target, propertiesToUpdate); + var res = await PutAsync(Target); + if (res == null) + { + ImportResult.Add($"❌ {Target.Name} - {this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"✔️ {Target.Name}"); + } + } + } + } + catch (Exception ex) + { + ImportResult.Add($"❌ Exception processing import\n record:{j.ToString()}\nError:{ex.Message}\nSource:{ex.Source}\nStack:{ex.StackTrace.ToString()}"); + } + } + 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($"VendorBiz.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.Vendor.AsNoTracking().Select(z => z.Id).ToListAsync(); + bool SaveIt = false; + + //--------------------------------- + //case 4192 + TimeSpan ProgressAndCancelCheckSpan = new TimeSpan(0, 0, ServerBootConfig.JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS); + DateTime LastProgressCheck = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1)); + var TotalRecords = idList.LongCount(); + long CurrentRecord = -1; + //--------------------------------- + foreach (long id in idList) + { + try + { + //-------------------------------- + //case 4192 + //Update progress / cancel requested? + CurrentRecord++; + if (DateUtil.IsAfterDuration(LastProgressCheck, ProgressAndCancelCheckSpan)) + { + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{CurrentRecord}/{TotalRecords}"); + if (await JobsBiz.GetJobStatusAsync(job.GId) == JobStatus.CancelRequested) + break; + LastProgressCheck = DateTime.UtcNow; + } + //--------------------------------- + SaveIt = false; + ClearErrors(); + Vendor 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++; + } + } + + //delay so we're not tying up all the resources in a tight loop + await Task.Delay(Sockeye.Util.ServerBootConfig.JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY); + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors id({id})"); + await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + } + } + + //--------------------------------- + //case 4192 + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{++CurrentRecord}/{TotalRecords}"); + //--------------------------------- + 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(SockEvent SockEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) + { + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + if (ServerBootConfig.SEEDING || ServerBootConfig.MIGRATING) return; + log.LogDebug($"HandlePotentialNotificationEvent processing: [SockType:{this.BizType}, SockEvent:{SockEvent}]"); + + bool isNew = currentObj == null; + + + //STANDARD EVENTS FOR ALL OBJECTS + await NotifyEventHelper.ProcessStandardObjectEvents(SockEvent, proposedObj, ct); + + //SPECIFIC EVENTS FOR THIS OBJECT + + }//end of process notifications + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/models/AyContext.cs b/server/models/AyContext.cs index f2973fc..f3ea603 100644 --- a/server/models/AyContext.cs +++ b/server/models/AyContext.cs @@ -56,6 +56,8 @@ namespace Sockeye.Models public virtual DbSet IntegrationItem { get; set; } public virtual DbSet IntegrationLog { get; set; } + public virtual DbSet License { get; set; } + //Note: had to add this constructor to work with the code in startup.cs that gets the connection string from the appsettings.json file //and commented out the above on configuring public AyContext(DbContextOptions options) : base(options) @@ -114,7 +116,7 @@ namespace Sockeye.Models //SERIALIZED OBJECTS // - //modelBuilder.Entity().Property(z => z.Serial).UseIdentityByDefaultColumn(); + modelBuilder.Entity().Property(z => z.CaseId).UseIdentityByDefaultColumn(); //## NOTE: if more added here then must also update globalbizsettingscontroller.seeds and client ////////////////////////////////////////////////////////////// diff --git a/server/models/License.cs b/server/models/License.cs new file mode 100644 index 0000000..5e8d13e --- /dev/null +++ b/server/models/License.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, + //otherwise the server will call it an invalid record if the field isn't sent from client + + public class License : ICoreBizObjectModel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + public DateTime Created { get; set; } + public long CustomerId { get; set; } + [NotMapped] + public string CustomerViz { get; set; } + public string RegTo { get; set; } + public string Key { get; set; } + public string FetchCode { get; set; } + public string FetchEmail { get; set; } + public DateTime FetchedOn { get; set; } + public string DbId { get; set; } + public DateTime LicenseExpire { get; set; } + public DateTime MaintenanceExpire { get; set; } + + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } + + + public License() + { + Tags = new List(); + } + + [NotMapped, JsonIgnore] + public SockType SType { get => SockType.License; } + + }//eoc + +}//eons +/* + await ExecQueryAsync("CREATE TABLE alicense (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, created TIMESTAMPTZ NOT NULL, " + + "customerid BIGINT NOT NULL REFERENCES acustomer(id), regto TEXT NOT NULL, key TEXT NOT NULL, fetchcode TEXT, fetchemail TEXT, " + + "fetchedon TIMESTAMPTZ, dbid TEXT, licenseexpire TIMESTAMPTZ, maintenanceexpire TIMESTAMPTZ NOT NULL, " + + "wiki TEXT, customfields TEXT, tags VARCHAR(255) ARRAY )"); +*/ \ No newline at end of file diff --git a/server/models/TrialLicenseRequest.cs b/server/models/TrialLicenseRequest.cs new file mode 100644 index 0000000..92b0450 --- /dev/null +++ b/server/models/TrialLicenseRequest.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, + //otherwise the server will call it an invalid record if the field isn't sent from client + + public class TrialLicenseRequest : ICoreBizObjectModel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + public string DbId { get; set; } + public string CompanyName { get; set; } + public string ContactName { get; set; } + public string Email { get; set; } + public string EmailConfirmCode { get; set; } + public bool EmailValidated { get; set; } = false; + public DateTime Requested { get; set; } + public DateTime? Processed { get; set; } + public TrialRequestStatus Status { get; set; } = TrialRequestStatus.NotSet; + public string RejectReason { get; set; } + public string Key { get; set; } + public DateTime FetchedOn { get; set; } + public bool Perpetual { get; set; } = false; + + public TrialLicenseRequest() + { + + } + + [NotMapped, JsonIgnore] + public SockType SType { get => SockType.TrialLicenseRequest; } + + }//eoc + +}//eons +/* + "CREATE TABLE atriallicenserequest (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, dbid TEXT NOT NULL, companyname TEXT NOT NULL, " ++ "contactname TEXT NOT NULL, email TEXT NOT NULL, emailconfirmcode TEXT NOT NULL, emailvalidated BOOL DEFAULT false, " ++ "requested TIMESTAMPTZ, processed TIMESTAMPTZ, status INTEGER NOT NULL DEFAULT 0, rejectreason TEXT, key TEXT, " ++ "fetched TIMESTAMPTZ, perpetual BOOL DEFAULT false NOT NULL )" +*/ \ No newline at end of file diff --git a/server/models/Vendor.cs b/server/models/Vendor.cs new file mode 100644 index 0000000..ce5d06a --- /dev/null +++ b/server/models/Vendor.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, + //otherwise the server will call it an invalid record if the field isn't sent from client + //#### MIRRORED IN QBI !! + public class Vendor : ICoreBizObjectModel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public string Name { get; set; } + public bool Active { get; set; } + public string Notes { get; set; } + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } + + public string Contact { get; set; } + public string ContactNotes { get; set; } + public string AlertNotes { get; set; } + public string WebAddress { get; set; } + public string AccountNumber { 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 string AddressPostal { get; set; } + public decimal? Latitude { get; set; } + public decimal? Longitude { get; set; } + + + + public Vendor() + { + Tags = new List(); + } + + [NotMapped, JsonIgnore] + public SockType SType { get => SockType.Vendor; } + + }//eoc + +}//eons + diff --git a/server/util/AySchema.cs b/server/util/AySchema.cs index 4760a56..1768aa6 100644 --- a/server/util/AySchema.cs +++ b/server/util/AySchema.cs @@ -875,14 +875,14 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); await ExecQueryAsync("CREATE TABLE alicense (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, created TIMESTAMPTZ NOT NULL, " + "customerid BIGINT NOT NULL REFERENCES acustomer(id), regto TEXT NOT NULL, key TEXT NOT NULL, fetchcode TEXT, fetchemail TEXT, " - + "fetchedon TIMESTAMPTZ, fetched BOOL default false, dbid TEXT, licenseexpire TIMESTAMPTZ, maintenanceexpire TIMESTAMPTZ NOT NULL, " - + "wiki TEXT, customfields TEXT, tags VARCHAR(255) ARRAY, toid BIGINT NOT NULL REFERENCES auser(id) )"); + + "fetchedon TIMESTAMPTZ, dbid TEXT, licenseexpire TIMESTAMPTZ, maintenanceexpire TIMESTAMPTZ NOT NULL, " + + "wiki TEXT, customfields TEXT, tags VARCHAR(255) ARRAY )"); await ExecQueryAsync("CREATE TABLE atriallicenserequest (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, dbid TEXT NOT NULL, companyname TEXT NOT NULL, " + "contactname TEXT NOT NULL, email TEXT NOT NULL, emailconfirmcode TEXT NOT NULL, emailvalidated BOOL DEFAULT false, " - + "requested TIMESTAMPTZ, processed TIMESTAMPTZ, status INTEGER NOT NULL DEFAULT 0, rejectreason TEXT, key TEXT, " - + "fetched TIMESTAMPTZ, perpetual BOOL DEFAULT false NOT NULL )"); + + "requested TIMESTAMPTZ NOT NULL, processed TIMESTAMPTZ, status INTEGER NOT NULL DEFAULT 0, rejectreason TEXT, key TEXT, " + + "fetchedon TIMESTAMPTZ, perpetual BOOL DEFAULT false NOT NULL )"); await ExecQueryAsync("CREATE TABLE asubscriptionserver (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, active BOOL NOT NULL DEFAULT true, created TIMESTAMPTZ NOT NULL, "