From 8d0e64af99c7dfd44debc673dabbdeacdd75f621 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Sun, 27 Mar 2022 23:26:26 +0000 Subject: [PATCH] Import added headoffice support, tests ok --- .vscode/launch.json | 2 +- .../8.0/ayanova/docs/adm-import-headoffice.md | 171 ++++++++++++++++++ docs/8.0/ayanova/docs/adm-import.md | 1 + server/AyaNova/biz/HeadOfficeBiz.cs | 96 ++++++++-- 4 files changed, 258 insertions(+), 12 deletions(-) create mode 100644 docs/8.0/ayanova/docs/adm-import-headoffice.md diff --git a/.vscode/launch.json b/.vscode/launch.json index 7c905164..2c3525c1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -48,7 +48,7 @@ "AYANOVA_DATA_PATH": "c:\\temp\\ravendata", "AYANOVA_USE_URLS": "http://*:7575;", //"AYANOVA_PERMANENTLY_ERASE_DATABASE":"true", - "AYANOVA_SERVER_TEST_MODE": "true", + "AYANOVA_SERVER_TEST_MODE": "false", "AYANOVA_SERVER_TEST_MODE_TZ_OFFSET": "-8", //"AYANOVA_REPORT_RENDERING_TIMEOUT":"1", "AYANOVA_SERVER_TEST_MODE_SEEDLEVEL": "small", diff --git a/docs/8.0/ayanova/docs/adm-import-headoffice.md b/docs/8.0/ayanova/docs/adm-import-headoffice.md new file mode 100644 index 00000000..b860594c --- /dev/null +++ b/docs/8.0/ayanova/docs/adm-import-headoffice.md @@ -0,0 +1,171 @@ +# Head office import / update specifications + +Names of fields listed here are the exact case and spelling required to be recognized by AyaNova for importing / updating. + +Any field in the import file that is not listed on this page will be removed before sending to the server for import. + +## Required fields + +- Name + +## Key field used to match to existing records + +- Name + +## Fields directly importable / updateable + +- Name (import only) +- Active +- Notes +- Wiki +- Tags +- WebAddress +- TechNotes +- AccountNumber +- ContractExpires +- Phone1 +- Phone2 +- Phone3 +- Phone4 +- Phone5 +- EmailAddress +- PostAddress +- PostCity +- PostRegion +- PostCountry +- PostCode +- Address +- City +- Region +- Country +- Latitude +- Longitude + +## Linked object fields + +The following linked objects are supported for importing (update is not supported for these fields): + +- Contract via "ContractViz" field which must contain the name of an existing Contract + +If the Contract is specified then the ContractExpires field can be used to set the expiry date of the Contract. + +If ContractExpires is omitted then it is set to the import date and time minus 1 minute so that it won't take effect until it's been set to a future date. + +## JSON file format + +The .json file must contain an array of Head office objects, so even a single object must be within [] square brackets in the import file. + +The first Head office record here illustrates importing a Head office with a link to an existing Contract. + +```JSON +[ + { + "Name": "Goodwin LLC", + "Active": true, + "Notes": "Triple-buffered mission-critical website", + "Wiki": null, + "Tags": [ + "jade", + "zone5", + "zone7" + ], + "WebAddress": "http://example.info", + "AccountNumber": "32906249", + "ContractViz": "Bronze", + "ContractExpires": "2023-02-01T08:00:00Z", + "Phone1": "1-629-420-0186", + "Phone2": "1-807-405-5544 x2470", + "Phone3": "352-364-6323 x45752", + "Phone4": null, + "Phone5": null, + "EmailAddress": "Heloise_Farrell61@example.com", + "PostAddress": "865 Tracey Views", + "PostCity": "New Mathew", + "PostRegion": "Louisiana", + "PostCountry": "Jordan", + "PostCode": "74354-4982", + "Address": "3400 Tyree Keys", + "City": "New Mathew", + "Region": "Louisiana", + "Country": "Jordan", + "Latitude": -56.030700, + "Longitude": 169.844400 + }, + { + "Name": "Bahringer - Stark", + "Active": true, + "Notes": "Enhanced reciprocal matrix", + "Wiki": null, + "Tags": [ + "green", + "red", + "zone8" + ], + "WebAddress": "http://example.name", + "AccountNumber": "71115129", + "ContractViz": null, + "ContractExpires": null, + "Phone1": "530.785.2024 x019", + "Phone2": "418-629-2283 x34011", + "Phone3": "347.447.3645", + "Phone4": null, + "Phone5": null, + "EmailAddress": "Alysa.Mertz@example.org", + "PostAddress": "0511 Deckow Stream", + "PostCity": "Lake Cotyhaven", + "PostRegion": "Nevada", + "PostCountry": "Comoros", + "PostCode": "41680", + "Address": "2438 Sibyl Neck", + "City": "Lake Cotyhaven", + "Region": "Nevada", + "Country": "Comoros", + "Latitude": -51.706500, + "Longitude": -22.044300 + }, + { + "Name": "Beer, Armstrong and Wiegand", + "Active": true, + "Notes": "Compatible clear-thinking concept", + "Wiki": null, + "Tags": [ + "zone3", + "zone9" + ], + "WebAddress": "https://example.org", + "AccountNumber": "95120864", + "ContractViz": null, + "ContractExpires": null, + "Phone1": "986-530-1111", + "Phone2": "1-695-482-1199 x321", + "Phone3": "653-466-6627 x117", + "Phone4": null, + "Phone5": null, + "EmailAddress": "Elisa41@example.org", + "PostAddress": "15461 Deangelo Tunnel", + "PostCity": "Kuvalisland", + "PostRegion": "Florida", + "PostCountry": "Sri Lanka", + "PostCode": "15255-8577", + "Address": "4695 Sandra Tunnel", + "City": "Kuvalisland", + "Region": "Florida", + "Country": "Sri Lanka", + "Latitude": -19.439100, + "Longitude": -112.588800 + } +] +``` + +## CSV file format + +The first row of the .csv file must contain column headers that match the field names listed above. + +The first Head office record here illustrates importing a Head office with a link to an existing Contract. + +``` +Name,Active,Notes,Wiki,Tags,WebAddress,AccountNumber,ContractViz,ContractExpires,Phone1,Phone2,Phone3,Phone4,Phone5,EmailAddress,PostAddress,PostCity,PostRegion,PostCountry,PostCode,Address,City,Region,Country,Latitude,Longitude +Goodwin LLC,true,Triple-buffered mission-critical website,,"jade,zone5,zone7",http://example.info,32906249,Bronze,2023-02-01T08:00:00Z,1-629-420-0186,1-807-405-5544 x2470,352-364-6323 x45752,,,Heloise_Farrell61@example.com,865 Tracey Views,New Mathew,Louisiana,Jordan,74354-4982,3400 Tyree Keys,New Mathew,Louisiana,Jordan,-56.0307,169.8444 +Bahringer - Stark,true,Enhanced reciprocal matrix,,"green,red,zone8",http://example.name,71115129,,,530.785.2024 x019,418-629-2283 x34011,347.447.3645,,,Alysa.Mertz@example.org,0511 Deckow Stream,Lake Cotyhaven,Nevada,Comoros,41680,2438 Sibyl Neck,Lake Cotyhaven,Nevada,Comoros,-51.7065,-22.0443 +"Beer, Armstrong and Wiegand",true,Compatible clear-thinking concept,,"zone3,zone9",https://example.org,95120864,,,986-530-1111,1-695-482-1199 x321,653-466-6627 x117,,,Elisa41@example.org,15461 Deangelo Tunnel,Kuvalisland,Florida,Sri Lanka,15255-8577,4695 Sandra Tunnel,Kuvalisland,Florida,Sri Lanka,-19.4391,-112.5888 +``` diff --git a/docs/8.0/ayanova/docs/adm-import.md b/docs/8.0/ayanova/docs/adm-import.md index 580fdd7b..e83feefe 100644 --- a/docs/8.0/ayanova/docs/adm-import.md +++ b/docs/8.0/ayanova/docs/adm-import.md @@ -165,6 +165,7 @@ Types not listed may be added in future; for needs beyond what is provided with Each object type listed below links to a page showing the specific format required for the import file and special notes about importing that specific object: - [Customers](adm-import-customer.md) +- [Head offices](adm-import-headoffice.md) ## Import form diff --git a/server/AyaNova/biz/HeadOfficeBiz.cs b/server/AyaNova/biz/HeadOfficeBiz.cs index 2ac974eb..5a3f51e7 100644 --- a/server/AyaNova/biz/HeadOfficeBiz.cs +++ b/server/AyaNova/biz/HeadOfficeBiz.cs @@ -343,32 +343,106 @@ namespace AyaNova.Biz - public async Task> ImportData(AyImportData importData) +public async Task> ImportData(AyImportData importData) { List ImportResult = new List(); - string ImportTag = $"imported-{FileUtil.GetSafeDateFileName()}"; + string ImportTag = ImportUtil.GetImportTag(); + //ignore these fields var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new AyaNova.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + foreach (JObject j in importData.Data) { - 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) + long existingId = await ct.HeadOffice.AsNoTracking().Where(z => z.Name == (string)j["Name"]).Select(x => x.Id).FirstOrDefaultAsync(); + + if (existingId == 0) { - ImportResult.Add($"* {w.Name} - {this.GetErrorsAsString()}"); - this.ClearErrors(); + if (importData.DoImport) + { + //import this record + var o = j.ToObject(jsset); + o.Tags.Add(ImportTag); + + + if (!JsonUtil.JTokenIsNullOrEmpty(j["ContractViz"])) + { + o.ContractId = await ct.Contract.AsNoTracking().Where(z => z.Name == (string)j["ContractViz"]).Select(x => x.Id).FirstOrDefaultAsync(); + if (o.ContractId == 0) + AddError(ApiErrorCode.NOT_FOUND, "ContractViz", $"'{(string)j["ContractViz"]}'"); + + if (JsonUtil.JTokenIsNullOrEmpty(j["ContractExpires"])) + o.ContractExpires = DateTime.UtcNow.Subtract(new TimeSpan(0, 1, 0));//expired one minute ago to be safe, can't guess what the contract should be + else + o.ContractExpires = (DateTime)j["ContractExpires"]; + + } + + var res = await CreateAsync(o); + if (res == null) + { + ImportResult.Add($"❌ {o.Name}\r\n{this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"✔️ {o.Name}"); + } + } } else { - ImportResult.Add($"{w.Name} - ok"); + 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}"); + } + } } } return ImportResult; } + // public async Task> ImportData(AyImportData importData) + // { + // 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 importData.Data) + // { + // 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