diff --git a/docs/8.0/ayanova/docs/adm-import-part.md b/docs/8.0/ayanova/docs/adm-import-part.md new file mode 100644 index 00000000..c0dc35e9 --- /dev/null +++ b/docs/8.0/ayanova/docs/adm-import-part.md @@ -0,0 +1,143 @@ +# Part 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 +- Description +- Notes +- Wiki +- Tags +- ManufacturerNumber +- WholeSalerNumber +- AlternativeWholeSalerNumber +- Cost +- Retail +- UnitOfMeasure +- UPC +- PartSerialsViz + +## Linked object fields + +The following linked objects are importable / updateable: + +- Manufacturer via "ManufacturerViz" field which must contain the name of an existing Vendor +- Wholesaler via "WholeSalerViz" field which must contain the name of an existing Vendor +- Alternative wholesaler via "AlternativeWholeSalerViz" field which must contain the name of an existing Vendor + +## JSON file format + +The .json file must contain an array of Part objects, so even a single object must be within [] square brackets in the import file. + +The first Part record here illustrates importing a Part with links to existing Manufacturer, Wholesaler and Alternative Wholesaler and also how to include serial numbers. + +```JSON +[ + { + "Name": "048902", + "Active": true, + "Description": "test description", + "Notes": "Alias nihil beatae dolores quam quisquam molestiae repudiandae magni consequatur dolores inventore distinctio.", + "Wiki": "# Headings\n# Heading 1st level\n## Heading 2nd level\n### Heading 3rd level\n#### Heading 4th level\n##### Heading 5th level\n###### Heading 6th level", + "Tags": [ + "orange", + "zebra", + "zone2", + "zone4", + "zone5" + ], + "ManufacturerViz": "Yellow Cedar Consulting", + "ManufacturerNumber": "man-048902", + "WholeSalerViz": "Ruecker - Jakubowski", + "WholeSalerNumber": "ws-048902", + "AlternativeWholeSalerViz": "Considine Group", + "AlternativeWholeSalerNumber": "aws-048902", + "Cost": 15.200000000000000000, + "Retail": 18.240000000000000000, + "UnitOfMeasure": "each", + "UPC": "4058773855392", + "PartSerialsViz": "387992910, 387992911, 387992912" + }, + { + "Name": "097091", + "Active": true, + "Description": null, + "Notes": "Vel sit id quia provident cum et quaerat.", + "Wiki": null, + "Tags": [ + "black", + "purple", + "silver", + "violet", + "zebra" + ], + "ManufacturerViz": "Littel Group", + "ManufacturerNumber": "man-097091", + "WholeSalerViz": "Ruecker - Jakubowski", + "WholeSalerNumber": "ws-097091", + "AlternativeWholeSalerViz": "Jakubowski, Hoeger and Hauck", + "AlternativeWholeSalerNumber": "aws-097091", + "Cost": 18.060000000000000000, + "Retail": 21.670000000000000000, + "UnitOfMeasure": "each", + "UPC": "2820615657752", + "PartSerialsViz": "" + }, + { + "Name": "198660", + "Active": true, + "Description": null, + "Notes": "Fuga quaerat neque quasi ut inventore explicabo velit explicabo delectus.", + "Wiki": null, + "Tags": [ + "jade", + "zone5", + "zone8", + "zone9" + ], + "ManufacturerViz": "Yellow Cedar Consulting", + "ManufacturerNumber": "man-198660", + "WholeSalerViz": "Weimann Group", + "WholeSalerNumber": "ws-198660", + "AlternativeWholeSalerViz": "Considine Group", + "AlternativeWholeSalerNumber": "aws-198660", + "Cost": 13.740000000000000000, + "Retail": 16.490000000000000000, + "UnitOfMeasure": "each", + "UPC": "5708648426804", + "PartSerialsViz": "" + } + +] +``` + +## CSV file format + +The first row of the .csv file must contain column headers that match the field names listed above. + +The first Part record here illustrates importing a Part with links to existing Manufacturer, Wholesaler and Alternative Wholesaler and also how to include serial numbers. + +``` +Name,Active,Description,Notes,Wiki,Tags,ManufacturerViz,ManufacturerNumber,WholeSalerViz,WholeSalerNumber,AlternativeWholeSalerViz,AlternativeWholeSalerNumber,Cost,Retail,UnitOfMeasure,UPC,PartSerialsViz +048902,true,test description,Alias nihil beatae dolores quam quisquam molestiae repudiandae magni consequatur dolores inventore distinctio.,"# Headings +# Heading 1st level +## Heading 2nd level +### Heading 3rd level +#### Heading 4th level +##### Heading 5th level +###### Heading 6th level","orange,zebra,zone2,zone4,zone5",Yellow Cedar Consulting,man-048902,Ruecker - Jakubowski,ws-048902,Considine Group,aws-048902,15.2,18.24,each,4058773855392,"387992910, 387992911, 387992912" +097091,true,,Vel sit id quia provident cum et quaerat.,,"black,purple,silver,violet,zebra",Littel Group,man-097091,Ruecker - Jakubowski,ws-097091,"Jakubowski, Hoeger and Hauck",aws-097091,18.06,21.67,each,2820615657752, +198660,true,,Fuga quaerat neque quasi ut inventore explicabo velit explicabo delectus.,,"jade,zone5,zone8,zone9",Yellow Cedar Consulting,man-198660,Weimann Group,ws-198660,Considine Group,aws-198660,13.74,16.49,each,5708648426804, +``` diff --git a/server/AyaNova/biz/CustomerBiz.cs b/server/AyaNova/biz/CustomerBiz.cs index 2ef95281..759835df 100644 --- a/server/AyaNova/biz/CustomerBiz.cs +++ b/server/AyaNova/biz/CustomerBiz.cs @@ -442,44 +442,44 @@ namespace AyaNova.Biz if (importData.DoImport) { //import this record - var o = j.ToObject(jsset); - o.Tags.Add(ImportTag); + var Target = j.ToObject(jsset); + Target.Tags.Add(ImportTag); //Set linked objects - if (o.BillHeadOffice) + if (Target.BillHeadOffice) { if (JsonUtil.JTokenIsNullOrEmpty(j["HeadOfficeViz"])) AddError(ApiErrorCode.VALIDATION_REQUIRED, "HeadOfficeViz"); else { //if it's not found, it will be zero which will fail validation with a clean error - o.HeadOfficeId = await ct.HeadOffice.AsNoTracking().Where(z => z.Name == (string)j["HeadOfficeViz"]).Select(x => x.Id).FirstOrDefaultAsync(); - if (o.HeadOfficeId == 0) + Target.HeadOfficeId = await ct.HeadOffice.AsNoTracking().Where(z => z.Name == (string)j["HeadOfficeViz"]).Select(x => x.Id).FirstOrDefaultAsync(); + if (Target.HeadOfficeId == 0) AddError(ApiErrorCode.NOT_FOUND, "HeadOfficeViz", $"'{(string)j["HeadOfficeViz"]}'"); } } 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) + Target.ContractId = await ct.Contract.AsNoTracking().Where(z => z.Name == (string)j["ContractViz"]).Select(x => x.Id).FirstOrDefaultAsync(); + if (Target.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 + Target.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"]; + Target.ContractExpires = (DateTime)j["ContractExpires"]; } - var res = await CreateAsync(o); + var res = await CreateAsync(Target); if (res == null) { - ImportResult.Add($"❌ {o.Name}\r\n{this.GetErrorsAsString()}"); + ImportResult.Add($"❌ {Target.Name}\r\n{this.GetErrorsAsString()}"); this.ClearErrors(); } else { - ImportResult.Add($"✔️ {o.Name}"); + ImportResult.Add($"✔️ {Target.Name}"); } } } @@ -489,21 +489,21 @@ namespace AyaNova.Biz { //update this record with any data provided //load existing record - var target = await GetAsync((long)existingId); - var source = j.ToObject(jsset); + 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); + ImportUtil.Update(Source, Target, propertiesToUpdate); + var res = await PutAsync(Target); if (res == null) { - ImportResult.Add($"❌ {target.Name} - {this.GetErrorsAsString()}"); + ImportResult.Add($"❌ {Target.Name} - {this.GetErrorsAsString()}"); this.ClearErrors(); } else { - ImportResult.Add($"✔️ {target.Name}"); + ImportResult.Add($"✔️ {Target.Name}"); } } } diff --git a/server/AyaNova/biz/HeadOfficeBiz.cs b/server/AyaNova/biz/HeadOfficeBiz.cs index 5a3f51e7..1b1a319c 100644 --- a/server/AyaNova/biz/HeadOfficeBiz.cs +++ b/server/AyaNova/biz/HeadOfficeBiz.cs @@ -313,7 +313,7 @@ namespace AyaNova.Biz vc.Clear(); return ReportData; } - + private VizCache vc = new VizCache(); //populate viz fields from provided object @@ -343,7 +343,7 @@ namespace AyaNova.Biz -public async Task> ImportData(AyImportData importData) + public async Task> ImportData(AyImportData importData) { List ImportResult = new List(); string ImportTag = ImportUtil.GetImportTag(); @@ -363,7 +363,7 @@ public async Task> ImportData(AyImportData importData) 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(); diff --git a/server/AyaNova/biz/PartBiz.cs b/server/AyaNova/biz/PartBiz.cs index 4e0c3fbe..a68c17b7 100644 --- a/server/AyaNova/biz/PartBiz.cs +++ b/server/AyaNova/biz/PartBiz.cs @@ -447,7 +447,7 @@ namespace AyaNova.Biz } } - + //Any form customizations to validate? var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.Part.ToString()); @@ -531,7 +531,7 @@ namespace AyaNova.Biz vc.Add(await ct.Vendor.AsNoTracking().Where(x => x.Id == o.ManufacturerId).Select(x => x.Name).FirstOrDefaultAsync(), "vendorname", o.ManufacturerId); o.ManufacturerViz = vc.Get("vendorname", o.ManufacturerId); } - + if (o.AlternativeWholeSalerId != null) { if (!vc.Has("vendorname", o.AlternativeWholeSalerId)) @@ -554,33 +554,152 @@ namespace AyaNova.Biz return await GetReportData(dataListSelectedRequest, jobId); } + + 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) + + //Compile linked objects if specified + long? ImportManufacturerId = -1;//default meaning not included / don't set + if (j["ManufacturerViz"] != null) { - ImportResult.Add($"* {w.Name} - {this.GetErrorsAsString()}"); - this.ClearErrors(); + //something was specified, may be deliberate attempt to null it out so default to that + ImportManufacturerId = null; + if (!JsonUtil.JTokenIsNullOrEmpty(j["ManufacturerViz"])) + { + //a name was specified so attempt to find it + ImportManufacturerId = await ct.Vendor.AsNoTracking().Where(z => z.Name == (string)j["ManufacturerViz"]).Select(x => x.Id).FirstOrDefaultAsync(); + if (ImportManufacturerId == 0) + AddError(ApiErrorCode.NOT_FOUND, "ManufacturerViz", $"'{(string)j["ManufacturerViz"]}'"); + } + } + + long? ImportWholeSalerId = -1; + if (j["WholeSalerViz"] != null) + { + ImportWholeSalerId = null; + if (!JsonUtil.JTokenIsNullOrEmpty(j["WholeSalerViz"])) + { + ImportWholeSalerId = await ct.Vendor.AsNoTracking().Where(z => z.Name == (string)j["WholeSalerViz"]).Select(x => x.Id).FirstOrDefaultAsync(); + if (ImportWholeSalerId == 0) + AddError(ApiErrorCode.NOT_FOUND, "WholeSalerViz", $"'{(string)j["WholeSalerViz"]}'"); + } + } + + long? ImportAlternativeWholeSalerId = -1; + if (j["AlternativeWholeSalerViz"] != null) + { + ImportAlternativeWholeSalerId = null; + if (!JsonUtil.JTokenIsNullOrEmpty(j["AlternativeWholeSalerViz"])) + { + ImportAlternativeWholeSalerId = await ct.Vendor.AsNoTracking().Where(z => z.Name == (string)j["AlternativeWholeSalerViz"]).Select(x => x.Id).FirstOrDefaultAsync(); + if (ImportAlternativeWholeSalerId == 0) + AddError(ApiErrorCode.NOT_FOUND, "AlternativeWholeSalerViz", $"'{(string)j["AlternativeWholeSalerViz"]}'"); + } + } + + long existingId = await ct.Part.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); + + if (ImportManufacturerId != -1) + Target.ManufacturerId = ImportManufacturerId; + + if (ImportWholeSalerId != -1) + Target.WholeSalerId = ImportWholeSalerId; + + if (ImportAlternativeWholeSalerId != -1) + Target.AlternativeWholeSalerId = ImportAlternativeWholeSalerId; + + var res = await CreateAsync(Target); + if (res == null) + { + ImportResult.Add($"❌ {Target.Name}\r\n{this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"✔️ {Target.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); + if (ImportManufacturerId != -1) + Target.ManufacturerId = ImportManufacturerId; + if (ImportWholeSalerId != -1) + Target.WholeSalerId = ImportWholeSalerId; + if (ImportAlternativeWholeSalerId != -1) + Target.AlternativeWholeSalerId = ImportAlternativeWholeSalerId; + + 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; + // } + + ////////////////////////////////////////////////////////////////////////////////////////////////