< Summary

Information
Class: ExcelProcessor.Controllers.ExcelController
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/OzonCards/Controllers/ExcelController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 232
Coverable lines: 232
Total lines: 464
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 90
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
Index()0%620%
UploadFiles()0%2040%
ProcessExcel()0%272160%
GetColumnsInfo()0%7280%
SaveFile()0%4260%
DeleteTempFile(...)0%2040%
GetColumnExamples(...)0%4260%
GetFileInfo()0%2040%
ProcessExcelFiles()0%110100%
GetColumnIndexMapping(...)0%4260%
GetHeaders(...)0%2040%
CopyData(...)0%110100%
CleanOldTempFiles()0%4260%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/OzonCards/Controllers/ExcelController.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Mvc;
 2using OfficeOpenXml;
 3using System.ComponentModel.DataAnnotations;
 4using Microsoft.AspNetCore.Authorization;
 5using OzonOrdersWeb.Areas.OzonCards.ViewModels;
 6using Services.DataServices.ExcelMapping;
 7
 8namespace ExcelProcessor.Controllers
 9{
 10    [Area("OzonCards")]
 11    [Authorize(Roles = "User,Admin")]
 12    public class ExcelController : Controller
 13    {
 14        private readonly IWebHostEnvironment _environment;
 15        private readonly IExcelMappingService _mappingService;
 016        public ExcelController(IWebHostEnvironment environment,
 017                               IExcelMappingService mappingService)
 18        {
 019            _environment = environment;
 020            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
 021            _mappingService = mappingService;
 022        }
 23
 24        [HttpGet]
 25        public async Task<IActionResult> Index(int? mappingId)
 26        {
 027            if(mappingId == null)
 028                return View();
 29
 030            var mapping = await _mappingService.GetMappingAsync(mappingId.Value);
 031            return View(mapping);
 032        }
 33
 34
 35
 36        [HttpPost]
 37        public async Task<IActionResult> UploadFiles(IFormFile sourceFile, IFormFile destinationFile)
 38        {
 39            try
 40            {
 041                if (sourceFile == null || destinationFile == null)
 42                {
 043                    return Json(new { success = false, message = "Оба файла должны быть загружены" });
 44                }
 45
 46                // Сохраняем файлы и получаем их имена
 047                var sourceFileName = await SaveFile(sourceFile);
 048                var destFileName = await SaveFile(destinationFile);
 49
 050                var fileInfo = await GetFileInfo(sourceFile, destinationFile);
 51
 052                return Json(new
 053                {
 054                    success = true,
 055                    sourceFileName,
 056                    destFileName,
 057                    sourceSheets = fileInfo.SourceSheets,
 058                    destinationSheets = fileInfo.DestinationSheets,
 059                    sourceColumns = fileInfo.SourceColumns,
 060                    destinationColumns = fileInfo.DestinationColumns
 061                });
 62            }
 063            catch (Exception ex)
 64            {
 065                return Json(new { success = false, message = ex.Message });
 66            }
 067        }
 68
 69        [HttpPost]
 70        public async Task<IActionResult> ProcessExcel([FromForm] ExcelMappingRequest request, string? MappingName = null
 71        {
 72            try
 73            {
 074                if (string.IsNullOrEmpty(request.SourceFileName) || string.IsNullOrEmpty(request.DestinationFileName))
 75                {
 076                    return BadRequest("Файлы не были загружены");
 77                }
 78
 079                if (request.ColumnMappings == null || !request.ColumnMappings.Any())
 80                {
 081                    return BadRequest("Не указаны сопоставления столбцов");
 82                }
 83
 084                var result = await ProcessExcelFiles(request);
 85
 86
 087                DeleteTempFile(request.SourceFileName);
 088                DeleteTempFile(request.DestinationFileName);
 89
 090                if (result.Success)
 91                {
 092                    if (MappingName != null)
 93                    {
 094                        var id = await _mappingService.SaveMappingAsync(request, MappingName, User.Identity?.Name ?? str
 95                    }
 96
 097                    return File(result.ResultFile,
 098                        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 099                        result.FileName);
 100                }
 101                else
 102                {
 0103                    return BadRequest(result.Message);
 104                }
 105            }
 0106            catch (Exception ex)
 107            {
 0108                DeleteTempFile(request.SourceFileName);
 0109                DeleteTempFile(request.DestinationFileName);
 0110                return StatusCode(500, $"Ошибка обработки: {ex.Message}");
 111            }
 0112        }
 113
 114        [HttpPost]
 115        public async Task<IActionResult> GetColumnsInfo(
 116            IFormFile sourceFile,
 117            IFormFile destinationFile,
 118            string sourceSheet,
 119            string destinationSheet,
 120            int sourceHeaderRow = 1,
 121            int destinationHeaderRow = 1)
 122        {
 123            try
 124            {
 0125                if (sourceFile == null || destinationFile == null)
 126                {
 0127                    return Json(new { success = false, message = "Файлы не были загружены" });
 128                }
 129
 0130                using (var sourceStream = new MemoryStream())
 0131                using (var destStream = new MemoryStream())
 132                {
 0133                    await sourceFile.CopyToAsync(sourceStream);
 0134                    await destinationFile.CopyToAsync(destStream);
 135
 0136                    sourceStream.Position = 0;
 0137                    destStream.Position = 0;
 138
 0139                    using (var sourcePackage = new ExcelPackage(sourceStream))
 0140                    using (var destPackage = new ExcelPackage(destStream))
 141                    {
 0142                        var sourceWorksheet = sourcePackage.Workbook.Worksheets
 0143                            .FirstOrDefault(ws => ws.Name.Equals(sourceSheet, StringComparison.OrdinalIgnoreCase));
 144
 0145                        var destWorksheet = destPackage.Workbook.Worksheets
 0146                            .FirstOrDefault(ws => ws.Name.Equals(destinationSheet, StringComparison.OrdinalIgnoreCase));
 147
 0148                        if (sourceWorksheet == null || destWorksheet == null)
 149                        {
 0150                            return Json(new { success = false, message = "Указанные листы не найдены" });
 151                        }
 152
 0153                        var sourceHeaders = GetHeaders(sourceWorksheet, sourceHeaderRow);
 0154                        var destHeaders = GetHeaders(destWorksheet, destinationHeaderRow);
 155
 0156                        var sourceExamples = GetColumnExamples(sourceWorksheet, sourceHeaderRow + 1, sourceHeaders);
 0157                        var destExamples = GetColumnExamples(destWorksheet, destinationHeaderRow + 1, destHeaders);
 158
 0159                        return Json(new
 0160                        {
 0161                            success = true,
 0162                            sourceColumns = sourceHeaders.Keys.Select(k => new
 0163                            {
 0164                                name = k,
 0165                                example = sourceExamples.ContainsKey(k) ? sourceExamples[k] : ""
 0166                            }),
 0167                            destinationColumns = destHeaders.Keys.Select(k => new
 0168                            {
 0169                                name = k,
 0170                                example = destExamples.ContainsKey(k) ? destExamples[k] : ""
 0171                            })
 0172                        });
 173                    }
 174                }
 175            }
 0176            catch (Exception ex)
 177            {
 0178                return Json(new { success = false, message = ex.Message });
 179            }
 0180        }
 181
 182        private async Task<string?> SaveFile(IFormFile file)
 183        {
 0184            if (file == null || file.Length == 0)
 0185                return null;
 186
 0187            var uploadsPath = Path.Combine(_environment.WebRootPath, "uploads");
 0188            if (!Directory.Exists(uploadsPath))
 0189                Directory.CreateDirectory(uploadsPath);
 190
 0191            var fileName = Guid.NewGuid().ToString() + Path.GetExtension(file.FileName);
 0192            var filePath = Path.Combine(uploadsPath, fileName);
 193
 0194            using (var stream = new FileStream(filePath, FileMode.Create))
 195            {
 0196                await file.CopyToAsync(stream);
 0197            }
 198
 0199            return fileName;
 0200        }
 201
 202        private void DeleteTempFile(string fileName)
 203        {
 0204            if (!string.IsNullOrEmpty(fileName))
 205            {
 0206                var filePath = Path.Combine(_environment.WebRootPath, "uploads", fileName);
 0207                if (System.IO.File.Exists(filePath))
 208                {
 209                    try
 210                    {
 0211                        System.IO.File.Delete(filePath);
 0212                    }
 0213                    catch
 214                    {
 215                        // Игнорируем ошибки удаления
 0216                    }
 217                }
 218            }
 0219        }
 220
 221        private Dictionary<string, string> GetColumnExamples(ExcelWorksheet worksheet, int dataRow, Dictionary<string, i
 222        {
 0223            var examples = new Dictionary<string, string>();
 224
 0225            foreach (var header in headers)
 226            {
 0227                var value = worksheet.Cells[dataRow, header.Value].Value?.ToString();
 0228                examples[header.Key] = value ?? "нет данных";
 229            }
 230
 0231            return examples;
 232        }
 233
 234        private async Task<FileInfoResponse> GetFileInfo(IFormFile sourceFile, IFormFile destinationFile)
 235        {
 0236            var response = new FileInfoResponse();
 237
 0238            using (var sourceStream = new MemoryStream())
 0239            using (var destStream = new MemoryStream())
 240            {
 0241                await sourceFile.CopyToAsync(sourceStream);
 0242                await destinationFile.CopyToAsync(destStream);
 243
 0244                sourceStream.Position = 0;
 0245                destStream.Position = 0;
 246
 0247                using (var sourcePackage = new ExcelPackage(sourceStream))
 0248                using (var destPackage = new ExcelPackage(destStream))
 249                {
 0250                    response.SourceSheets = sourcePackage.Workbook.Worksheets
 0251                        .Select(ws => ws.Name)
 0252                        .ToList();
 253
 0254                    response.DestinationSheets = destPackage.Workbook.Worksheets
 0255                        .Select(ws => ws.Name)
 0256                        .ToList();
 257
 0258                    if (response.SourceSheets.Any())
 259                    {
 0260                        var firstSourceSheet = sourcePackage.Workbook.Worksheets[response.SourceSheets[0]];
 0261                        response.SourceColumns = GetHeaders(firstSourceSheet, 1).Keys.ToList();
 262                    }
 263
 0264                    if (response.DestinationSheets.Any())
 265                    {
 0266                        var firstDestSheet = destPackage.Workbook.Worksheets[response.DestinationSheets[0]];
 0267                        response.DestinationColumns = GetHeaders(firstDestSheet, 1).Keys.ToList();
 268                    }
 0269                }
 0270            }
 271
 0272            return response;
 0273        }
 274
 275        private async Task<ExcelProcessingResult> ProcessExcelFiles(ExcelMappingRequest request)
 276        {
 0277            await Task.CompletedTask;
 0278            var sourceFilePath = Path.Combine(_environment.WebRootPath, "uploads", request.SourceFileName);
 0279            var destFilePath = Path.Combine(_environment.WebRootPath, "uploads", request.DestinationFileName);
 280
 0281            if (!System.IO.File.Exists(sourceFilePath) || !System.IO.File.Exists(destFilePath))
 282            {
 0283                return new ExcelProcessingResult
 0284                {
 0285                    Success = false,
 0286                    Message = "Файлы не найдены на сервере"
 0287                };
 288            }
 289
 0290            using (var sourceStream = new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read))
 0291            using (var destStream = new FileStream(destFilePath, FileMode.Open, FileAccess.ReadWrite))
 292            {
 0293                using (var sourcePackage = new ExcelPackage(sourceStream))
 0294                using (var destPackage = new ExcelPackage(destStream))
 295                {
 0296                    var sourceWorksheet = sourcePackage.Workbook.Worksheets
 0297                        .FirstOrDefault(ws => ws.Name.Equals(request.SourceSheet, StringComparison.OrdinalIgnoreCase));
 298
 0299                    var destWorksheet = destPackage.Workbook.Worksheets
 0300                        .FirstOrDefault(ws => ws.Name.Equals(request.DestinationSheet, StringComparison.OrdinalIgnoreCas
 301
 0302                    if (sourceWorksheet == null)
 303                    {
 0304                        return new ExcelProcessingResult
 0305                        {
 0306                            Success = false,
 0307                            Message = $"Лист '{request.SourceSheet}' не найден в исходном файле"
 0308                        };
 309                    }
 310
 0311                    if (destWorksheet == null)
 312                    {
 0313                        return new ExcelProcessingResult
 0314                        {
 0315                            Success = false,
 0316                            Message = $"Лист '{request.DestinationSheet}' не найден в целевом файле"
 0317                        };
 318                    }
 319
 0320                    var columnIndexMap = GetColumnIndexMapping(sourceWorksheet, destWorksheet, request);
 321
 0322                    if (columnIndexMap == null)
 323                    {
 0324                        return new ExcelProcessingResult
 0325                        {
 0326                            Success = false,
 0327                            Message = "Ошибка сопоставления столбцов"
 0328                        };
 329                    }
 330
 0331                    int processedRows = CopyData(sourceWorksheet, destWorksheet, request, columnIndexMap);
 332
 0333                    using (var resultStream = new MemoryStream())
 334                    {
 0335                        destPackage.SaveAs(resultStream);
 0336                        resultStream.Position = 0;
 337
 0338                        return new ExcelProcessingResult
 0339                        {
 0340                            Success = true,
 0341                            Message = $"Успешно обработано {processedRows} строк",
 0342                            ProcessedRows = processedRows,
 0343                            ResultFile = resultStream.ToArray(),
 0344                            FileName = $"processed_{Path.GetFileNameWithoutExtension(request.DestinationFileName)}.xlsx"
 0345                        };
 346                    }
 347                }
 348            }
 0349        }
 350
 351        private Dictionary<int, int> GetColumnIndexMapping(
 352            ExcelWorksheet sourceWorksheet,
 353            ExcelWorksheet destWorksheet,
 354            ExcelMappingRequest request)
 355        {
 0356            var mapping = new Dictionary<int, int>();
 357
 0358            var sourceHeaders = GetHeaders(sourceWorksheet, request.SourceHeaderRow);
 0359            var destHeaders = GetHeaders(destWorksheet, request.DestinationHeaderRow);
 360
 0361            foreach (var columnMapping in request.ColumnMappings)
 362            {
 0363                if (!sourceHeaders.ContainsKey(columnMapping.SourceColumn))
 364                {
 0365                    throw new ValidationException($"Столбец '{columnMapping.SourceColumn}' не найден в исходном файле");
 366                }
 367
 0368                if (!destHeaders.ContainsKey(columnMapping.DestinationColumn))
 369                {
 0370                    throw new ValidationException($"Столбец '{columnMapping.DestinationColumn}' не найден в целевом файл
 371                }
 372
 0373                int sourceColIndex = sourceHeaders[columnMapping.SourceColumn];
 0374                int destColIndex = destHeaders[columnMapping.DestinationColumn];
 375
 0376                mapping.Add(sourceColIndex, destColIndex);
 377            }
 378
 0379            return mapping;
 380        }
 381
 382        private Dictionary<string, int> GetHeaders(ExcelWorksheet worksheet, int headerRow)
 383        {
 0384            var headers = new Dictionary<string, int>();
 0385            int col = 1;
 386
 0387            while (true)
 388            {
 0389                var headerValue = worksheet.Cells[headerRow, col].Value?.ToString();
 0390                if (string.IsNullOrEmpty(headerValue))
 391                    break;
 392
 0393                headers[headerValue.Trim()] = col;
 0394                col++;
 395            }
 396
 0397            return headers;
 398        }
 399
 400        private int CopyData(
 401            ExcelWorksheet sourceWorksheet,
 402            ExcelWorksheet destWorksheet,
 403            ExcelMappingRequest request,
 404            Dictionary<int, int> columnIndexMap)
 405        {
 0406            int processedRows = 0;
 0407            int sourceRow = request.SourceDataStartRow;
 0408            int destRow = request.DestinationDataStartRow;
 409
 0410            while (true)
 411            {
 0412                bool hasData = false;
 0413                foreach (var mapping in columnIndexMap)
 414                {
 0415                    var sourceValue = sourceWorksheet.Cells[sourceRow, mapping.Key].Value;
 0416                    if (sourceValue != null && !string.IsNullOrEmpty(sourceValue.ToString()))
 417                    {
 0418                        hasData = true;
 0419                        break;
 420                    }
 421                }
 422
 0423                if (!hasData)
 424                    break;
 425
 0426                foreach (var mapping in columnIndexMap)
 427                {
 0428                    var sourceValue = sourceWorksheet.Cells[sourceRow, mapping.Key].Value;
 0429                    destWorksheet.Cells[destRow, mapping.Value].Value = sourceValue;
 430                }
 431
 0432                sourceRow++;
 0433                destRow++;
 0434                processedRows++;
 435            }
 436
 0437            return processedRows;
 438        }
 439
 440        // Периодическая очистка старых файлов
 441        private void CleanOldTempFiles()
 442        {
 443            try
 444            {
 0445                var uploadsPath = Path.Combine(_environment.WebRootPath, "uploads");
 0446                if (Directory.Exists(uploadsPath))
 447                {
 0448                    foreach (var file in Directory.GetFiles(uploadsPath))
 449                    {
 0450                        var fileInfo = new FileInfo(file);
 0451                        if (fileInfo.LastWriteTime < DateTime.Now.AddHours(-1))
 452                        {
 0453                            fileInfo.Delete();
 454                        }
 455                    }
 456                }
 0457            }
 0458            catch
 459            {
 460                // Игнорируем ошибки очистки
 0461            }
 0462        }
 463    }
 464}