< Summary

Class:WinSolutions.Sveta.Server.Data.DataLoading.LoadingResult
Assembly:WinSolutions.Sveta.Server
File(s):/opt/dev/sveta_api_build/WinSolutions.Sveta.Server/Data/DataLoading/BaseRecordsLoader.cs
Covered lines:0
Uncovered lines:28
Coverable lines:28
Total lines:427
Line coverage:0% (0 of 28)
Covered branches:0
Total branches:8
Branch coverage:0% (0 of 8)

Metrics

MethodLine coverage Branch coverage
get_UploadId()0%100%
get_TemplateHeader()0%100%
get_Errors()0%100%
get_AffectedCount()0%100%
GetResultFile()0%0%

File(s)

/opt/dev/sveta_api_build/WinSolutions.Sveta.Server/Data/DataLoading/BaseRecordsLoader.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Text;
 5using WinSolutions.Sveta.Server.Data.DataModel.Contexts;
 6using WinSolutions.Sveta.Server.Data.DataModel.Entities;
 7using WinSolutions.Sveta.Server.Data.DataLoading.Parsers;
 8using System.Linq;
 9using WinSolutions.Sveta.Server.Data.DataLoading.Records;
 10using System.Diagnostics.CodeAnalysis;
 11using System.Globalization;
 12using ClosedXML.Excel;
 13using WinSolutions.Sveta.Common;
 14
 15namespace WinSolutions.Sveta.Server.Data.DataLoading
 16{
 17    public class LoaderException:Exception
 18    {
 19        public LoaderException(string msg):base(msg)
 20        {
 21        }
 22    }
 23
 24    public class LoadingResult
 25    {
 026        public long UploadId{get;set;}
 27
 028        public List<string> TemplateHeader { get; set; }
 29
 030        public IEnumerable<ParsingError> Errors{get;set;}
 31
 032        public int AffectedCount {get;set;}
 33
 34        public byte[] GetResultFile()
 035        {
 036            var result = new List<string[]>();
 37
 038            if (TemplateHeader != null && TemplateHeader.Any())
 039            {
 040                var line = new List<string>(TemplateHeader[0].Split(';')) { "" };
 041                result.Add(line.ToArray());
 42
 043                line = new List<string>(TemplateHeader[1].Split(';')) { "Ошибка" };
 044                result.Add(line.ToArray());
 45
 046                line = new List<string>(TemplateHeader[2].Split(';')) { "Error" };
 047                result.Add(line.ToArray());
 048            }
 49
 050            if (Errors != null)
 051            {
 052                foreach (var e in Errors)
 053                {
 054                    var line = new List<string>(e.Line) { e.Exception.Message };
 055                    result.Add(line.ToArray());
 056                }
 057            }
 58
 059            using (MemoryStream ms = new MemoryStream())
 060            {
 061                CsvUtil.ToExcelStream(result).CopyTo(ms);
 062                return ms.ToArray();
 63            }
 064        }
 65    }
 66
 67    public abstract class BaseRecordsLoader<TParser,TRecord>: IDataLoader  where TParser: IParser, new() where TRecord: 
 68    {
 69        long currentUserId;
 70
 71        public BaseRecordsLoader(long currentUserId)
 72        {
 73            this.currentUserId = currentUserId;
 74        }
 75
 76        public abstract string GetTemplateDescription();
 77
 78        public List<string> GetTemplateHeader(bool csvFormat)
 79        {
 80            var result = new List<string>();
 81            result.Add(GetTemplateDescription());
 82
 83            var map = new TRecord().GetMapping().ToList();
 84
 85            var line = "";
 86            for (int i = 0; i < map.Count; i++)
 87            {
 88                line += (csvFormat ? $"\"{map[i].NameRus}\"" : map[i].NameRus);
 89                if (i < map.Count - 1)
 90                {
 91                    line += ";";
 92                }
 93            }
 94            result.Add(line);
 95
 96            line = "";
 97            for (int i = 0; i < map.Count; i++)
 98            {
 99                line += (csvFormat ? $"\"{map[i].PropertyInfo.Name}\"" : map[i].PropertyInfo.Name);
 100                if (i < map.Count - 1)
 101                {
 102                    line += ";";
 103                }
 104            }
 105            result.Add(line);
 106
 107            return result;
 108        }
 109
 110        public Action<BaseRecord> BeforeAddRecordAction {get;set;}
 111
 112        public void GetTemplate(SvetaDbContext context, Stream stream, bool csvFormat = true)
 113        {
 114            var template = GetTemplateInternal(csvFormat);
 115
 116            if (csvFormat)
 117            {
 118                using (var writer = new StreamWriter(stream, encoding: Encoding.UTF8, leaveOpen: true))
 119                {
 120                    template.ForEach(x => writer.WriteLine(string.Join(';', x)));
 121                }
 122            }
 123            else
 124            {
 125                using (var book = CsvUtil.ToExcel(template))
 126                {
 127                    book.SaveAs(stream);
 128                }
 129            }
 130        }
 131
 132        List<string[]> GetTemplateInternal(bool csvFormat)
 133        {
 134            var map = new TRecord().GetMapping().ToList();
 135            var writer = new List<string[]>();
 136
 137            GetTemplateHeader(csvFormat).ForEach(x =>
 138            {
 139                writer.Add(x.Split(';'));
 140            });
 141
 142            var nf = new NumberFormatInfo();
 143            var rnd = new Random();
 144            for (int x = 0; x < 4; x++)
 145            {
 146                var line = new List<string>();
 147                for (int i = 0; i < map.Count; i++)
 148                {
 149                    var val = String.Format(nf, map[i].SampleData, rnd.Next(0, 1000));
 150
 151                    if (csvFormat && map[i].PropertyInfo.PropertyType == typeof(string))
 152                    {
 153                        val = $"\"{val}\"";
 154                    }
 155
 156                    line.Add(val);
 157                }
 158
 159                writer.Add(line.ToArray());
 160            }
 161
 162            return writer;
 163        }
 164
 165        protected int SaveChanges(SvetaDbContext context)
 166        {
 167            var task = context.SaveChangesAsync(currentUserId);
 168            task.Wait();
 169            return task.Result;
 170        }
 171
 172        List<BaseRecord> added = null;
 173        public LoadingResult Load(SvetaDbContext context, Stream stream)
 174        {
 175            var upload = new Upload
 176            {
 177                Items = new List<UploadItem>(),
 178                SourceFileName = stream is FileStream ? (stream as FileStream).Name : null
 179            };
 180
 181            LoadingResult res = new LoadingResult();
 182
 183            try
 184            {
 185                added = new List<BaseRecord>();
 186
 187
 188
 189                var parser = new TParser();
 190                var list = parser.Parse(stream).ToList();
 191
 192                var list2 = list.Where(x =>
 193                {
 194                    if (x is IExternalRecord r)
 195                        return r.Exception == null;
 196
 197                    return true;
 198                }).ToList();
 199
 200                var res2 = Load(context, list2);
 201                var pl = res2.ToList();
 202
 203
 204
 205                if(pl.Any())
 206                    res.AffectedCount =  SaveChanges(context);
 207
 208
 209                res.Errors =  list.Where(x =>
 210                {
 211                    if(x is IExternalRecord r)
 212                        return r.Exception != null;
 213
 214                    return true;
 215                })
 216                .Cast<IExternalRecord>()
 217                .Select(x => new ParsingError
 218                {
 219                    LineNumber = x.LineNumber,
 220                    Line = x.Line,
 221                    Exception = x.Exception
 222                });
 223
 224                res.TemplateHeader = GetTemplateHeader(false);
 225
 226                upload.Items = new List<UploadItem>
 227                (
 228                    added.Select(x => new UploadItem
 229                    {
 230                        EndityId = x.Id,
 231                        EntityName = x.GetType().Name
 232                    })
 233                );
 234
 235                upload.ResultFile = res.GetResultFile();
 236                upload.UploadedRecordCount = res.AffectedCount;
 237            }
 238            catch(Exception ex)
 239            {
 240                res.Errors = new List<ParsingError>(new []{new ParsingError{Exception = ex} });
 241                var lines = new List<string[]>();
 242                lines.Add(new string[] { "Ошибка загрузки. Обратитесь к администратору системы, приложив данный файл с о
 243                lines.Add(new string[] { ex.Message });
 244                lines.Add(new string[] { ex.StackTrace });
 245                var ms = new MemoryStream();
 246                CsvUtil.ToExcelStream(lines).CopyTo(ms);
 247                upload.ResultFile = ms.ToArray();
 248            }
 249
 250            context.Uploads.Add(upload);
 251            SaveChanges(context);
 252            res.UploadId = upload.Id;
 253
 254            AfterLoad();
 255            SaveChanges(context);
 256
 257            return res;
 258        }
 259
 260        protected virtual void AfterLoad()
 261        {
 262
 263        }
 264
 265        class TypeComparer : IComparer<Type>
 266        {
 267            public int Compare([AllowNull] Type x, [AllowNull] Type y)
 268            {
 269                var px = x.GetProperties().Where(x => x.PropertyType.IsSubclassOf(typeof(BaseRecord))).ToList();
 270                var py = y.GetProperties().Where(x => x.PropertyType.IsSubclassOf(typeof(BaseRecord))).ToList();
 271
 272                if(px.Count == 0 && py.Count == 0)
 273                    return 0;
 274
 275                foreach (var p in px)
 276                {
 277                    if(p.PropertyType == y || p.PropertyType.IsSubclassOf(y))
 278                        return 1;
 279                }
 280
 281                foreach (var p in py)
 282                {
 283                    if(p.PropertyType == x || p.PropertyType.IsSubclassOf(x))
 284                        return -1;
 285                }
 286
 287                return 0;
 288            }
 289        }
 290
 291        public void Rollback(SvetaDbContext context,long uploadId)
 292        {
 293            if(!context.Uploads.Any(x => x.Id == uploadId))
 294                throw new LoaderException($"Загрузка с Id = {uploadId} не найдена.");
 295
 296
 297            var status = context.Uploads.Where(x => x.Id == uploadId)
 298                .Select(x => x.Status)
 299                .FirstOrDefault();
 300
 301            if(status == UploadStatus.Cancelled)
 302                throw new LoaderException($"Загрузка с Id = {uploadId} уже отменена.");
 303
 304            var types = new Dictionary<string,Type>();
 305
 306
 307            var added = context.UploadItems
 308                .Where(x => x.UploadId == uploadId)
 309                .OrderBy(x => x.Id)
 310                .ToList();
 311
 312            foreach (var typeName in added.GroupBy(x => x.EntityName).Select(x => x.Key).ToList())
 313            {
 314                types[typeName] = Type.GetType($"WinSolutions.Sveta.Server.Data.DataModel.Entities.{typeName}",true);
 315            }
 316
 317
 318            var typeOrder = types.OrderBy(x => x.Value,new TypeComparer()).ToList();
 319            typeOrder.Reverse();
 320
 321            foreach (var t in typeOrder)
 322            {
 323                foreach (var r in added.Where(x => x.EntityName == t.Key).ToList())
 324                {
 325
 326                    var rec = (BaseRecord)context.Find(t.Value,r.EndityId);
 327
 328                    if(rec != null)
 329                    {
 330                        context.Remove(rec);
 331                        //context.Entry(rec).State = Microsoft.EntityFrameworkCore.EntityState.Deleted;
 332                    }
 333                }
 334
 335                SaveChanges(context);
 336            }
 337
 338            var upload = new Upload
 339            {
 340                Id = uploadId,
 341                Status = UploadStatus.Cancelled
 342            };
 343
 344            context.Uploads.Attach(upload);
 345            context.Entry(upload).Property(x => x.Status).IsModified = true;
 346            SaveChanges(context);
 347        }
 348
 349
 350        public void Commit(SvetaDbContext context,long uploadId)
 351        {
 352            if(!context.Uploads.Any(x => x.Id == uploadId))
 353                throw new LoaderException($"Загрузка с Id = {uploadId} не найдена.");
 354
 355
 356            var status = context.Uploads.Where(x => x.Id == uploadId)
 357                .Select(x => x.Status)
 358                .FirstOrDefault();
 359
 360            if(status == UploadStatus.Cancelled)
 361                throw new LoaderException($"Загрузка с Id = {uploadId} уже отменена.");
 362
 363            if(status == UploadStatus.Commited)
 364                throw new LoaderException($"Загрузка с Id = {uploadId} уже подтверждена.");
 365
 366            var types = new Dictionary<string,Type>();
 367
 368
 369            var added = context.UploadItems
 370                .Where(x => x.UploadId == uploadId)
 371                .OrderBy(x => x.Id)
 372                .ToList();
 373
 374            foreach (var typeName in added.GroupBy(x => x.EntityName).Select(x => x.Key).ToList())
 375            {
 376                types[typeName] = Type.GetType($"WinSolutions.Sveta.Server.Data.DataModel.Entities.{typeName}",true);
 377            }
 378
 379
 380
 381            var activeState = context.refRecordsState.Where(x => x.Code == "Active").Single();
 382
 383            foreach (var r in added)
 384            {
 385                var type = types[r.EntityName];
 386
 387                var rec = (BaseRecord)context.Find(type,r.EndityId);
 388
 389                if(rec != null)
 390                {
 391                    rec.RecState = activeState;
 392                }
 393            }
 394
 395            SaveChanges(context);
 396
 397            var upload = new Upload
 398            {
 399                Id = uploadId,
 400                Status = UploadStatus.Commited
 401            };
 402
 403            context.Uploads.Attach(upload);
 404            context.Entry(upload).Property(x => x.Status).IsModified = true;
 405            SaveChanges(context);
 406        }
 407
 408        protected abstract IEnumerable<BaseRecord> Load(SvetaDbContext context,IEnumerable<BaseRecord> list);
 409
 410        RecordsState inactiveState = null;
 411        void SetState(SvetaDbContext context, BaseRecord rec)
 412        {
 413            if(inactiveState == null)
 414                inactiveState = context.refRecordsState.Where(x => x.Code == "Inactive").Single();
 415
 416            rec.RecState = inactiveState;
 417        }
 418
 419        protected virtual void AddNew(SvetaDbContext context,BaseRecord rec)
 420        {
 421            BeforeAddRecordAction?.Invoke(rec);
 422            SetState(context,rec);
 423            context.Add(rec);
 424            added.Add(rec);
 425        }
 426    }
 427}