diff --git a/docs/8.0/ayanova/docs/adm-import-task-group.md b/docs/8.0/ayanova/docs/adm-import-task-group.md new file mode 100644 index 00000000..018de9f7 --- /dev/null +++ b/docs/8.0/ayanova/docs/adm-import-task-group.md @@ -0,0 +1,109 @@ +# Task group import / update specifications + +Names of fields listed here are the exact case and spelling required to be recognized by AyaNova for [importing / updating](adm-import.md). + +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 +- Items + +## JSON file format + +The .json file must contain an array of one or more Task group objects. + +```JSON +[ + { + "Name": "Aerostat monitor standard refurb", + "Active": true, + "Notes": "Alias maxime deserunt corrupti voluptatem a voluptatem eligendi.", + "Items": [ + { + "Sequence": 1, + "Task": "Power down unit" + }, + { + "Sequence": 2, + "Task": "Open seals" + }, + { + "Sequence": 3, + "Task": "Replace central core" + }, + { + "Sequence": 4, + "Task": "Test point B and verify +.25" + }, + { + "Sequence": 5, + "Task": "Replace seals" + }, + { + "Sequence": 6, + "Task": "Confirm power up" + } + ] + }, + { + "Name": "Clean and inspect Class 7C", + "Active": true, + "Notes": "Facilis omnis dolor tempora dolores suscipit maxime ut et eum aperiam dolor.", + "Items": [ + { + "Sequence": 1, + "Task": "Open unit" + }, + { + "Sequence": 2, + "Task": "Test tinclavic seals" + }, + { + "Task": "Inspect triple-bonded polysium for cracks" + }, + { + "Sequence": 4, + "Task": "Verify thickness of zybanium shield" + }, + { + "Sequence": 5, + "Task": "Close unit" + }, + { + "Sequence": 6, + "Task": "Clean unit and confirm power up" + } + ] + } +] +``` + +## CSV file format + +The first row of the .csv file must contain column headers that match the field names listed above. + +CSV doesn't cleanly map on to structured elements with sub collections well so the Items column here which contains the Tasks for this Task group has a special format we've adopted: + +The Items column **must** be: + +- surrounded in double "quotes" +- contain the Tasks separated by commas +- each Task text must _not_ have a comma or a quotation mark in it or the csv will not be valid +- Sequence numbers are not importable from csv but will be automatically assigned in the order presented so the first item will be sequence 1, second sequence 2 etc + +``` +Name,Active,Notes,Items +Aerostat monitor standard refurb,true,Alias maxime deserunt corrupti voluptatem a voluptatem eligendi.,"Power down unit, Open seals, Replace central core, Test point B and verify +.25" +Clean and inspect Class 7C,true,Facilis omnis dolor tempora dolores suscipit maxime ut et eum aperiam dolor.,"Open unit, Test tinclavic seals, Inspect triple-bonded polysium for cracks, Verify thickness of zybanium shield,Close unit, Clean unit and confirm power up" +``` diff --git a/docs/8.0/ayanova/docs/adm-import.md b/docs/8.0/ayanova/docs/adm-import.md index 8875a5dd..2cf089e3 100644 --- a/docs/8.0/ayanova/docs/adm-import.md +++ b/docs/8.0/ayanova/docs/adm-import.md @@ -175,6 +175,7 @@ Each object type listed below links to a page showing the specific format requir - ##### [Part warhouses](adm-import-part-warehouse.md) - ##### [Projects](adm-import-projects.md) - ##### [Service rates](adm-import-service-rate.md) +- ##### [Task group](adm-import-task-group.md) - ##### [Travel rates](adm-import-travel-rate.md) ## Import form diff --git a/server/AyaNova/biz/TaskGroupBiz.cs b/server/AyaNova/biz/TaskGroupBiz.cs index 2cd9faec..0504eb74 100644 --- a/server/AyaNova/biz/TaskGroupBiz.cs +++ b/server/AyaNova/biz/TaskGroupBiz.cs @@ -298,7 +298,7 @@ namespace AyaNova.Biz var batchResults = await ct.TaskGroup.AsNoTracking().Include(z => z.Items).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; + batchResults = null; foreach (TaskGroup w in orderedList) { if (!ReportRenderManager.KeepGoing(jobId)) return null; @@ -307,7 +307,7 @@ namespace AyaNova.Biz jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); ReportData.Add(jo); } - orderedList=null; + orderedList = null; } return ReportData; } @@ -325,35 +325,107 @@ namespace AyaNova.Biz return await GetReportData(dataListSelectedRequest, jobId); } + + public async Task> ImportData(AyImportData importData) { - List ImportResult = new List(); - string ImportTag = $"imported-{FileUtil.GetSafeDateFileName()}"; + List ImportResult = new List(); + + + 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) + try { - ImportResult.Add($"* {w.Name} - {this.GetErrorsAsString()}"); - this.ClearErrors(); + //Compile Items collection if specified + var ImportTaskGroupItems = new List(); + if (j["Items"] != null) + { + //hydrate from collection + foreach (JToken t in j["Items"]) + { + ImportTaskGroupItems.Add(new TaskGroupItem() { TaskGroupId = 0, Sequence = (int)t["Sequence"], Task = (string)t["Task"] }); + } + j["Items"] = null;//strip it out so it doesn't get automatically converted into parsed object later + } + + long existingId = await ct.TaskGroup.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); + if (Target.Items == null) + Target.Items = new List(); + + + if (j["Items"] != null) + foreach (TaskGroupItem tgi in ImportTaskGroupItems) + Target.Items.Add(tgi); + + 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"); + propertiesToUpdate.Remove("Items"); + ImportUtil.Update(Source, Target, propertiesToUpdate); + if (j["Items"] != null) + { + Target.Items.Clear(); + foreach (TaskGroupItem tgi in ImportTaskGroupItems) + Target.Items.Add(tgi); + } + + var res = await PutAsync(Target); + + if (res == null) + { + ImportResult.Add($"❌ {Target.Name} - {this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"✔️ {Target.Name}"); + } + } + } } - else + catch (Exception ex) { - ImportResult.Add($"{w.Name} - ok"); + ImportResult.Add($"❌ Exception processing import\n record:{j.ToString()}\nError:{ex.Message}\nSource:{ex.Source}\nStack:{ex.StackTrace.ToString()}"); } } + return ImportResult; } - //////////////////////////////////////////////////////////////////////////////////////////////// //JOB / OPERATIONS //