< Summary

Class:WinSolutions.Sveta.Server.Data.DataLoading.Loaders.GoodsLoader`1
Assembly:WinSolutions.Sveta.Server
File(s):/opt/dev/sveta_api_build/WinSolutions.Sveta.Server/Data/DataLoading/Loaders/GoodsLoader.cs
Covered lines:0
Uncovered lines:297
Coverable lines:297
Total lines:421
Line coverage:0% (0 of 297)
Covered branches:0
Total branches:132
Branch coverage:0% (0 of 132)

Metrics

MethodLine coverage Branch coverage
.ctor(...)0%100%
get_GetPhotoSizeAction()0%100%
get_GoodService()0%100%
get_BarcodeService()0%100%
get_CountryService()0%100%
AfterLoad()0%0%
Load(...)0%0%
GetBrand(...)0%0%
GetCountry(...)0%100%
GetBarCode(...)0%0%
GetCategory(...)0%0%
PreFill(...)0%0%
GetTemplateDescription()0%100%

File(s)

/opt/dev/sveta_api_build/WinSolutions.Sveta.Server/Data/DataLoading/Loaders/GoodsLoader.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using WinSolutions.Sveta.Server.Data.DataModel.Contexts;
 4using WinSolutions.Sveta.Server.Data.DataModel.Entities;
 5using WinSolutions.Sveta.Server.Data.DataLoading.Parsers;
 6using System.Linq;
 7using Microsoft.EntityFrameworkCore;
 8using WinSolutions.Sveta.Server.Utils;
 9using System.ComponentModel.DataAnnotations;
 10using WinSolutions.Sveta.Server.Data.DataModel.Kinds;
 11using WinSolutions.Sveta.Server.Data.DataLoading.Records;
 12using WinSolutions.Sveta.Server.Services.Interfaces;
 13using WinSolutions.Sveta.Common.Extensions;
 14using WinSolutions.Sveta.Server.Data.DataModel.Extensions;
 15
 16namespace WinSolutions.Sveta.Server.Data.DataLoading.Loaders
 17{
 18    public class GoodsLoader<TParser>: BaseRecordsLoader<TParser,ExternalGood> where TParser: IParser, new()
 19    {
 020        public GoodsLoader(long currentUserId) : base(currentUserId)
 021        {
 022        }
 23
 024        public Action<Photo> GetPhotoSizeAction { get; set; }
 25
 026        public IGoodService GoodService { get; set; }
 27
 028        public IBarcodeService BarcodeService { get; set; }
 29
 030        public ICountryService CountryService { get; set; }
 31
 32        long lastCategoryId;
 33
 034        List<Good> addedGoods = new List<Good>();
 35
 36        protected override void AfterLoad()
 037        {
 038            foreach(var good in addedGoods)
 039            {
 040                good.UpdateUniqueCode();
 041            }
 042        }
 43
 44        protected override IEnumerable<BaseRecord> Load(SvetaDbContext context, IEnumerable<BaseRecord> list)
 045        {
 046            var goods = list.Cast<ExternalGood>().ToList();
 47
 48            /* словари, кэширующие справочники */
 049            var brandDic = new Dictionary<string, Brand>();
 50
 051            var catDic = context.Categories.Where(x => !x.IsDeleted).ToList();
 052            lastCategoryId = catDic.Any() ? catDic.Max(x => x.Id) : 0;
 053            var unitDic = new Dictionary<string, UnitsKind>
 054            (
 055                context.refUnitsKind.Select(x => new KeyValuePair<string, UnitsKind>(x.Name.ToLower(), x))
 056            );
 057            var vats = context.refVatsKind.ToList();
 058            var countryDic = new Dictionary<string, Country>
 059            (
 060                context.refCountries.Select(x => new KeyValuePair<string, Country>(x.Name.ToLower(), x))
 061            );
 062            var barcodeDic = new Dictionary<string, BarCode>
 063            (
 064                BarcodeService.FindBarcodesByCodes(goods.Select(x => x.BarCode.ToLower()))
 065                    .Result.Select(x => new KeyValuePair<string, BarCode>(x.Code.NormalizeName(), x))
 066            );
 67
 68
 069            var goodNames = new List<string>();
 070            var goodUniqueCodes = new List<string>();
 071            foreach (var g in goods)
 072            {
 073                g.Name = g.Name.NormalizeName();
 074                g.ParentCategoryName = g.ParentCategoryName.NormalizeName();
 075                g.CategoryCode = g.CategoryCode.NormalizeName();
 076                g.CategoryName = g.CategoryName.NormalizeName();
 077                g.UnitName = g.UnitName.NormalizeName();
 078                g.BrandName = g.BrandName.NormalizeName();
 079                g.SubbrandName = g.SubbrandName.NormalizeName();
 080                g.BarCode = g.BarCode.NormalizeName();
 081                g.CountryName = g.CountryName.NormalizeName();
 082                g.AdditionalBarCodes = g.AdditionalBarCodes.NormalizeName();
 083                g.VatName = g.VatName.NormalizeName();
 084                g.UniqueCode = g.UniqueCode.NormalizeName();
 85
 086                if (string.IsNullOrEmpty(g.CategoryCode) && string.IsNullOrEmpty(g.CategoryName))
 087                {
 088                    g.Exception = new ArgumentException("Не задана категория");
 089                }
 90                else
 091                {
 092                    if (!string.IsNullOrEmpty(g.Name))
 093                    {
 094                        goodNames.Add(g.Name.ToLower());
 095                    }
 096                    if (!string.IsNullOrEmpty(g.UniqueCode))
 097                    {
 098                        goodUniqueCodes.Add(g.UniqueCode.ToLower());
 099                    }
 0100                }
 0101            }
 0102            var findGoodsByNames = GoodService.FindGoodsByNames(goodNames).Result;
 0103            var findGoodsByUniqueCodes = GoodService.FindGoodsByUniqueCodes(goodUniqueCodes).Result;
 104
 0105            var namesFromFile = new List<string>();
 0106            var codesFromFile = new List<string>();
 107
 0108            var result = new List<BaseRecord>();
 0109            foreach (var extGood in goods.Where(x => x.Exception == null))
 0110            {
 0111                if (string.IsNullOrEmpty(extGood.Name) && string.IsNullOrEmpty(extGood.UniqueCode))
 0112                {
 0113                    extGood.Exception = new ArgumentException("Должен быть задан UniqueCode или Name");
 0114                    continue;
 115                }
 116
 0117                if (extGood.ExpirationDays <= 0)
 0118                {
 0119                    extGood.Exception = new ArgumentException($"Срок годности должен быть больше нуля");
 0120                    continue;
 121                }
 122
 0123                var goodUniqueCode = extGood.UniqueCode.ToLower();
 0124                var goodName = extGood.Name.ToLower();
 125
 0126                if (string.IsNullOrEmpty(extGood.BrandName))
 0127                {
 0128                    extGood.Exception = new ArgumentException($"Не задан BrandName");
 0129                    continue;
 130                }
 131
 0132                if (!string.IsNullOrEmpty(extGood.SubbrandName) && extGood.BrandName.ToLower() == extGood.SubbrandName.T
 0133                {
 0134                    extGood.Exception = new ArgumentException($"BrandName совпадает с SubbrandName");
 0135                    continue;
 136                }
 137
 0138                if (extGood.Exception != null)
 0139                {
 0140                    result.Add(extGood);
 0141                    continue;
 142                }
 143
 0144                bool isUpdate = extGood.Update == "1";
 145
 146                // сначала ищем товар по уникальному коду
 0147                bool foundByUniqueCode = false;
 0148                Good newGood = string.IsNullOrEmpty(goodUniqueCode) ? null : findGoodsByUniqueCodes.FirstOrDefault(x => 
 0149                if(newGood == null)
 0150                {
 151                    // если не находим, то ищем по имени
 0152                    newGood = findGoodsByNames.FirstOrDefault(x => x.Name.ToLower() == goodName);
 0153                }
 154                else
 0155                {
 0156                    foundByUniqueCode = true;
 157
 158                    // если нашли по артикулу, то ищем и по имени
 0159                    var newGood1 = findGoodsByNames.Where(x => x.Name.ToLower() == goodName);
 160
 161                    // если у найденного по имени не совпадает уникальный код с найденным по коду, значит дубликат имени
 0162                    if(newGood1.Any(x => x.UniqueCode.ToLower() != goodUniqueCode))
 0163                    {
 0164                        if(namesFromFile.Contains(extGood.Name) || codesFromFile.Contains(extGood.UniqueCode))
 0165                        {
 0166                            extGood.Exception = new ArgumentException($"Товар '{extGood.Name}' уже существует в файле им
 0167                            continue;
 168                        }
 169                        else
 0170                        {
 0171                            extGood.Exception = new ArgumentException($"Товар '{extGood.Name}' уже существует в базе дан
 0172                            continue;
 173                        }
 174                    }
 0175                }
 176
 177                // если товар уже есть в базе и не выставлен флаг обновления, то это ошибка
 0178                if (newGood != null && !isUpdate)
 0179                {
 0180                    if(foundByUniqueCode)
 0181                    {
 0182                        extGood.Exception = new ArgumentException($"Товар с уникальным кодом '{extGood.UniqueCode}' уже 
 0183                    }
 184                    else
 0185                    {
 0186                        extGood.Exception = new ArgumentException($"Товар '{extGood.Name}' уже существует в базе данных"
 0187                    }
 0188                    continue;
 189                }
 190
 191                // если товара нет в базе и выставлен флаг обновления, то это ошибка
 0192                if (newGood == null && isUpdate)
 0193                {
 0194                    extGood.Exception = new ArgumentException($"Товар '{extGood.Name}' не найден для обновления");
 0195                    continue;
 196                }
 197
 198                // если не в режиме обновления, то создаем новый товар и прописываем ему поля, которые были подхвачены и
 0199                if (!isUpdate)
 0200                {
 0201                    newGood = new Good();
 0202                    DataUtils.CopyTo(extGood, newGood);
 203
 204                    // ниже идущие поля прописываются только при создании товара, не при обновлении
 205
 0206                    if (!unitDic.TryGetValue(extGood.UnitName, out UnitsKind uk))
 0207                    {
 0208                        extGood.Exception = new ArgumentException($"Не найдено соответствие для единицы измерения {extGo
 0209                        continue;
 210                    }
 0211                    newGood.UnitsKind = uk;
 212
 0213                    if(string.IsNullOrEmpty(extGood.VatName))
 0214                    {
 0215                        newGood.VatsKind = vats.Single(x => x.Code == "0");
 0216                    }
 217                    else
 0218                    {
 0219                        newGood.VatsKind = vats.FirstOrDefault(x => x.Name == extGood.VatName + "%");
 0220                    }
 0221                    if (newGood.VatsKind == null)
 0222                    {
 0223                        extGood.Exception = new ArgumentException($"Не найдено соответствие НДС {extGood.VatName}");
 0224                        continue;
 225                    }
 226
 227
 0228                    newGood.GoodBarcodes = new List<GoodBarcode>();
 0229                    if (!string.IsNullOrEmpty(extGood.BarCode))
 0230                    {
 0231                        newGood.GoodBarcodes.Add(new GoodBarcode { BarCode = GetBarCode(context, barcodeDic, extGood.Bar
 0232                    }
 0233                    if(!string.IsNullOrEmpty(extGood.AdditionalBarCodes))
 0234                    {
 0235                        var codes = extGood.AdditionalBarCodes.Split(',', StringSplitOptions.RemoveEmptyEntries)
 0236                            .ToList().Distinct();
 0237                        foreach(var bc in codes)
 0238                        {
 0239                            if (!barcodeDic.TryGetValue(bc.NormalizeName(), out BarCode barcode))
 0240                            {
 0241                                barcode = new BarCode { Code = bc.NormalizeName() };
 0242                                barcodeDic.Add(barcode.Code, barcode);
 0243                                AddNew(context, barcode);
 0244                            }
 0245                            newGood.GoodBarcodes.Add(new GoodBarcode { BarCode = barcode, IsPrimary = false});
 0246                        }
 0247                    }
 0248                }
 249
 250                // ниже идущие поля прописываются и создании товара, и при обновлении
 0251                newGood.Category = GetCategory(context, catDic, extGood.ParentCategoryName, extGood.CategoryCode, extGoo
 0252                if(newGood.Category == null)
 0253                {
 0254                    extGood.Exception = new ArgumentException($"Категория не найдена");
 0255                    continue;
 256                }
 257
 258
 0259                newGood.Brand = GetBrand(context, brandDic,extGood.BrandName);
 0260                newGood.SubBrand = GetBrand(context, brandDic, extGood.SubbrandName);
 0261                if (newGood.SubBrand != null)
 0262                {
 0263                    if(newGood.SubBrand.Id == 0)
 0264                    {
 0265                        newGood.SubBrand.Parent = newGood.Brand;
 0266                    }
 267                    else
 0268                    {
 0269                        if((newGood.SubBrand.Parent != null && newGood.Brand.Id != newGood.SubBrand.Parent.Id)
 0270                            || (newGood.SubBrand.Parent == null))
 0271                        {
 0272                            extGood.Exception = new ArgumentException($"Иерархия брендов '{newGood.Brand.Name}' -> '{new
 0273                            continue;
 274                        }
 0275                    }
 0276                }
 277
 0278                newGood.Country = GetCountry(countryDic, extGood.CountryName.ToLower());
 0279                if (newGood.Country == null)
 0280                {
 0281                    extGood.Exception = new ArgumentException($"Страна {extGood.CountryName} не найдена");
 0282                    continue;
 283                }
 284
 0285                if(!string.IsNullOrEmpty(extGood.LargeImageFileName))
 0286                {
 0287                    if (string.IsNullOrEmpty(extGood.SmallImageFileName))
 0288                    {
 0289                        extGood.SmallImageFileName = extGood.LargeImageFileName;
 0290                    }
 291
 0292                    Photo photo = new Photo
 0293                    {
 0294                        FullSizeUrl = extGood.LargeImageFileName,
 0295                        PreviewUrl = extGood.SmallImageFileName
 0296                    };
 297                    try
 0298                    {
 0299                        GetPhotoSizeAction?.Invoke(photo);
 0300                    }
 0301                    catch(Exception ex)
 0302                    {
 0303                        extGood.Exception = new ArgumentException(ex.Message);
 0304                    }
 305
 0306                    newGood.Photos = new List<Photo>();
 0307                    AddNew(context, photo);
 0308                    newGood.Photos.Add(photo);
 0309                }
 310
 0311                if (isUpdate)
 0312                {
 313                    // в режиме обновления ничего не делаем
 0314                    BeforeAddRecordAction?.Invoke(newGood);
 0315                }
 316                else
 0317                {
 318                    // иначе добавляем новый товар
 0319                    AddNew(context, newGood);
 0320                    findGoodsByNames.Add(newGood);
 0321                    findGoodsByUniqueCodes.Add(newGood);
 322
 0323                    namesFromFile.Add(newGood.Name);
 0324                    codesFromFile.Add(newGood.UniqueCode);
 325
 0326                    addedGoods.Add(newGood);
 0327                }
 328
 0329                result.Add(newGood);
 0330            }
 331
 0332            return result;
 0333        }
 334
 335        private Brand GetBrand(SvetaDbContext context, Dictionary<string, Brand> dic, string name)
 0336        {
 0337            if(String.IsNullOrEmpty(name))
 0338                return null;
 0339            if(!dic.TryGetValue(name.ToLower(), out Brand brand))
 0340            {
 0341                brand = context.Brands.Where(x => x.Name.ToLower() == name.ToLower()).FirstOrDefault();
 0342                if(brand == null)
 0343                {
 0344                    brand = new Brand
 0345                    {
 0346                        Name = name
 0347                    };
 348
 0349                    AddNew(context,brand);
 0350                    dic[brand.Name.ToLower()] = brand;
 0351                }
 0352            }
 353
 0354            return brand;
 0355        }
 356
 357        private Country GetCountry(Dictionary<string, Country> dic, string countryName)
 0358        {
 0359            dic.TryGetValue(countryName, out Country result);
 0360            return result;
 0361        }
 362
 363        private BarCode GetBarCode(SvetaDbContext context, Dictionary<string, BarCode> dic, string code)
 0364        {
 0365            if (string.IsNullOrEmpty(code)) return null;
 0366            if(!dic.TryGetValue(code, out BarCode result))
 0367            {
 0368                result = new BarCode { Code = code };
 0369                dic[result.Code] = result;
 0370                AddNew(context, result);
 0371            }
 0372            return result;
 0373        }
 374
 375        private Category GetCategory(SvetaDbContext context, List<Category> dic, string parentName, string code, string 
 0376        {
 377            // сначала ищем непосредственную категорию. если находим, то сразу возвращаем игнорируя родительскую из файл
 0378            var cat = string.IsNullOrEmpty(code) ? (Category)null : dic.FirstOrDefault(x => x.Code?.ToLower() == code.To
 0379            if(cat != null) return cat;
 380
 0381            if (string.IsNullOrEmpty(name)) return null;
 0382            cat = dic.FirstOrDefault(x => x.Name?.ToLower() == name.ToLower());
 0383            if (cat != null) return cat;
 384
 385            // если не находим непосредственную, ищем родительскую. если не находим, то возвращаем null для дальнейшей о
 0386            var parentCat = dic.FirstOrDefault(x => x.Name?.ToLower() == parentName.ToLower());
 0387            if (parentCat == null) return null;
 388
 0389            cat = new Category
 0390            {
 0391                Parent = parentCat,
 0392                Name = name,
 0393                Code = !string.IsNullOrEmpty(code) ? code : "UPLOADED-" + (++lastCategoryId).ToString()
 0394            };
 0395            AddNew(context, cat);
 0396            dic.Add(cat);
 397
 0398            return cat;
 0399        }
 400
 401        const string notDefinedString = "не заполнено";
 402        void PreFill(BaseRecord rec)
 0403        {
 0404            foreach (var pi in rec.GetType().GetProperties())
 0405            {
 0406                if(pi.GetCustomAttributes(typeof(RequiredAttribute),false).Any())
 0407                {
 0408                    if(pi.PropertyType == typeof(string))
 0409                    {
 0410                        pi.SetValue(rec,notDefinedString);
 0411                    }
 0412                }
 0413            }
 0414        }
 415
 416        public override string GetTemplateDescription()
 0417        {
 0418            return "Шаблон загрузки товаров (* - поле обязательно для заполнения, ** - одно из полей обязательно для зап
 0419        }
 420    }
 421}