< Summary

Information
Class: OzonOrdersWeb.Areas.OneCFresh.Controllers.DocumentController
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/OneCFresh/Controllers/DocumentController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 285
Coverable lines: 285
Total lines: 456
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 162
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%
Receipts()0%4260%
Transfers()0%4260%
Payments()0%4260%
Detail()0%6162780%
PostDocument()0%2040%
UnpostDocument()0%2040%
UpdateComment()0%620%
BuildPostingsMapAsync()0%156120%
BuildNomenclatureMapAsync()0%4260%
ParseLines(...)0%420200%
BuildDateFilter(...)100%210%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/OneCFresh/Controllers/DocumentController.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Authorization;
 2using Microsoft.AspNetCore.Mvc;
 3using Microsoft.EntityFrameworkCore;
 4using Newtonsoft.Json.Linq;
 5using OzonOrdersWeb.Areas.OneCFresh.ViewModels;
 6using OzonRepositories.Context;
 7using Services.ApiServices._1CApi.OData;
 8using Services.ApiServices._1CApi.OData.Models;
 9
 10namespace OzonOrdersWeb.Areas.OneCFresh.Controllers;
 11
 12[Authorize(Roles = "User,Admin")]
 13[Area("OneCFresh")]
 14[Route("OneCFresh/Document/[action]")]
 15public class DocumentController : Controller
 16{
 17    private readonly IOneCODataReferenceService _refService;
 18    private readonly IOneCODataClient _client;
 19    private readonly ApplicationDbContext _db;
 20
 021    public DocumentController(
 022        IOneCODataReferenceService refService,
 023        IOneCODataClient client,
 024        ApplicationDbContext db)
 25    {
 026        _refService = refService;
 027        _client = client;
 028        _db = db;
 029    }
 30
 31    [HttpGet]
 32    public async Task<IActionResult> Receipts(
 33        string? search = null,
 34        DateTime? dateFrom = null,
 35        DateTime? dateTo = null,
 36        int page = 1,
 37        int pageSize = 100)
 38    {
 039        ViewData["Title"] = "Приходные накладные";
 040        dateFrom ??= DateTime.Today.AddMonths(-1);
 041        dateTo ??= DateTime.Today.AddDays(1);
 42
 43        try
 44        {
 045            var filter = BuildDateFilter("Date", dateFrom.Value, dateTo.Value);
 046            var docs = await _client.GetPageAsync<ODataReceiptListItem>(
 047                "Document_ПриходнаяНакладная",
 048                top: pageSize,
 049                skip: (page - 1) * pageSize,
 050                queryParams: new Dictionary<string, string>
 051                {
 052                    ["$select"] = "Ref_Key,Number,Date,Posted,DeletionMark,Контрагент_Key,НомерВходящегоДокумента,ДатаВх
 053                    ["$filter"] = filter,
 054                    ["$orderby"] = "Date desc"
 055                });
 56
 057            if (!string.IsNullOrWhiteSpace(search))
 58            {
 059                var s = search.Trim();
 060                docs = docs.Where(d =>
 061                    (d.Number?.Contains(s, StringComparison.OrdinalIgnoreCase) ?? false) ||
 062                    (d.IncomingDocNumber?.Contains(s, StringComparison.OrdinalIgnoreCase) ?? false)).ToList();
 63            }
 64
 065            var total = await _client.GetCountAsync("Document_ПриходнаяНакладная", filter);
 66
 067            var counterparties = await _refService.GetCounterpartiesAsync();
 068            var warehouses = await _refService.GetWarehousesAsync();
 069            var ctNames = counterparties.DistinctBy(c => c.RefKey).ToDictionary(c => c.RefKey, c => c.Description);
 070            var whNames = warehouses.DistinctBy(w => w.RefKey).ToDictionary(w => w.RefKey, w => w.Description);
 71
 072            var guids = docs.Select(d => d.RefKey.ToString()).ToHashSet(StringComparer.OrdinalIgnoreCase);
 73
 074            var linked = await _db.BackgroundTransactionResults
 075                .AsNoTracking()
 076                .Where(r => r.OneCDocumentId != null && guids.Contains(r.OneCDocumentId))
 077                .Select(r => new { r.OneCDocumentId, r.TransactionId })
 078                .ToListAsync();
 079            var linkedMap = linked
 080                .Where(r => !string.IsNullOrEmpty(r.OneCDocumentId))
 081                .ToDictionary(r => r.OneCDocumentId!, r => r.TransactionId);
 82
 083            var postingsByGuid = await BuildPostingsMapAsync(guids);
 84
 085            var rows = docs.Select(d => new ReceiptRow
 086            {
 087                Doc = d,
 088                LinkedTransactionId = linkedMap.TryGetValue(d.RefKey.ToString(), out var tid) ? tid : null,
 089                LinkedTransactionType = "receipt",
 090                LinkedOrderId = postingsByGuid.TryGetValue(d.RefKey.ToString(), out var p) ? (int?)p.orderId : null,
 091                LinkedOrderSuccess = postingsByGuid.TryGetValue(d.RefKey.ToString(), out var p2) && p2.success
 092            }).ToList();
 93
 094            return View(new ReceiptsViewModel
 095            {
 096                Items = rows,
 097                DateFrom = dateFrom,
 098                DateTo = dateTo,
 099                Search = search,
 0100                TotalCount = total,
 0101                Page = page,
 0102                PageSize = pageSize,
 0103                CounterpartyNames = ctNames,
 0104                WarehouseNames = whNames
 0105            });
 106        }
 0107        catch (Exception ex)
 108        {
 0109            TempData["Error"] = $"Ошибка загрузки приходных накладных: {ex.Message}";
 0110            return View(new ReceiptsViewModel { DateFrom = dateFrom, DateTo = dateTo, Search = search });
 111        }
 0112    }
 113
 114    [HttpGet]
 115    public async Task<IActionResult> Transfers(
 116        string? search = null,
 117        DateTime? dateFrom = null,
 118        DateTime? dateTo = null,
 119        int page = 1,
 120        int pageSize = 100)
 121    {
 0122        ViewData["Title"] = "Перемещения запасов";
 0123        dateFrom ??= DateTime.Today.AddMonths(-1);
 0124        dateTo ??= DateTime.Today.AddDays(1);
 125
 126        try
 127        {
 0128            var filter = BuildDateFilter("Date", dateFrom.Value, dateTo.Value);
 0129            var docs = await _client.GetPageAsync<ODataTransferListItem>(
 0130                "Document_ПеремещениеЗапасов",
 0131                top: pageSize,
 0132                skip: (page - 1) * pageSize,
 0133                queryParams: new Dictionary<string, string>
 0134                {
 0135                    ["$select"] = "Ref_Key,Number,Date,Posted,DeletionMark,СтруктурнаяЕдиница_Key,СтруктурнаяЕдиницаПолу
 0136                    ["$filter"] = filter,
 0137                    ["$orderby"] = "Date desc"
 0138                });
 139
 0140            if (!string.IsNullOrWhiteSpace(search))
 141            {
 0142                var s = search.Trim();
 0143                docs = docs.Where(d =>
 0144                    (d.Number?.Contains(s, StringComparison.OrdinalIgnoreCase) ?? false) ||
 0145                    (d.Comment?.Contains(s, StringComparison.OrdinalIgnoreCase) ?? false)).ToList();
 146            }
 147
 0148            var total = await _client.GetCountAsync("Document_ПеремещениеЗапасов", filter);
 149
 0150            var warehouses = await _refService.GetWarehousesAsync();
 0151            var whNames = warehouses.DistinctBy(w => w.RefKey).ToDictionary(w => w.RefKey, w => w.Description);
 152
 0153            var guids = docs.Select(d => d.RefKey.ToString()).ToHashSet(StringComparer.OrdinalIgnoreCase);
 154
 0155            var linked = await _db.BackgroundTransactionResults
 0156                .AsNoTracking()
 0157                .Where(r => r.OneCDocumentId != null && guids.Contains(r.OneCDocumentId))
 0158                .Select(r => new { r.OneCDocumentId, r.TransactionId })
 0159                .ToListAsync();
 0160            var linkedMap = linked
 0161                .Where(r => !string.IsNullOrEmpty(r.OneCDocumentId))
 0162                .ToDictionary(r => r.OneCDocumentId!, r => r.TransactionId);
 163
 0164            var postingsByGuid = await BuildPostingsMapAsync(guids);
 165
 0166            var rows = docs.Select(d => new TransferRow
 0167            {
 0168                Doc = d,
 0169                LinkedTransactionId = linkedMap.TryGetValue(d.RefKey.ToString(), out var tid) ? tid : null,
 0170                LinkedTransactionType = "transfer",
 0171                LinkedOrderId = postingsByGuid.TryGetValue(d.RefKey.ToString(), out var p) ? (int?)p.orderId : null,
 0172                LinkedOrderSuccess = postingsByGuid.TryGetValue(d.RefKey.ToString(), out var p2) && p2.success
 0173            }).ToList();
 174
 0175            return View(new TransfersViewModel
 0176            {
 0177                Items = rows,
 0178                DateFrom = dateFrom,
 0179                DateTo = dateTo,
 0180                Search = search,
 0181                TotalCount = total,
 0182                Page = page,
 0183                PageSize = pageSize,
 0184                WarehouseNames = whNames
 0185            });
 186        }
 0187        catch (Exception ex)
 188        {
 0189            TempData["Error"] = $"Ошибка загрузки перемещений: {ex.Message}";
 0190            return View(new TransfersViewModel { DateFrom = dateFrom, DateTo = dateTo, Search = search });
 191        }
 0192    }
 193
 194    [HttpGet]
 195    public async Task<IActionResult> Payments(
 196        string? search = null,
 197        DateTime? dateFrom = null,
 198        DateTime? dateTo = null,
 199        int page = 1,
 200        int pageSize = 100)
 201    {
 0202        ViewData["Title"] = "Платёжные поручения";
 0203        dateFrom ??= DateTime.Today.AddMonths(-1);
 0204        dateTo ??= DateTime.Today.AddDays(1);
 205
 206        try
 207        {
 0208            var filter = BuildDateFilter("Date", dateFrom.Value, dateTo.Value);
 0209            var docs = await _client.GetPageAsync<ODataPaymentListItem>(
 0210                "Document_ПлатежноеПоручение",
 0211                top: pageSize,
 0212                skip: (page - 1) * pageSize,
 0213                queryParams: new Dictionary<string, string>
 0214                {
 0215                    ["$select"] = "Ref_Key,Number,Date,Posted,DeletionMark,Контрагент_Key,СуммаДокумента,НазначениеПлате
 0216                    ["$filter"] = filter,
 0217                    ["$orderby"] = "Date desc"
 0218                });
 219
 0220            if (!string.IsNullOrWhiteSpace(search))
 221            {
 0222                var s = search.Trim();
 0223                docs = docs.Where(d =>
 0224                    (d.Number?.Contains(s, StringComparison.OrdinalIgnoreCase) ?? false) ||
 0225                    (d.Purpose?.Contains(s, StringComparison.OrdinalIgnoreCase) ?? false)).ToList();
 226            }
 227
 0228            var total = await _client.GetCountAsync("Document_ПлатежноеПоручение", filter);
 229
 0230            var counterparties = await _refService.GetCounterpartiesAsync();
 0231            var ctNames = counterparties.DistinctBy(c => c.RefKey).ToDictionary(c => c.RefKey, c => c.Description);
 232
 0233            return View(new PaymentsViewModel
 0234            {
 0235                Items = docs,
 0236                DateFrom = dateFrom,
 0237                DateTo = dateTo,
 0238                Search = search,
 0239                TotalCount = total,
 0240                Page = page,
 0241                PageSize = pageSize,
 0242                CounterpartyNames = ctNames
 0243            });
 244        }
 0245        catch (Exception ex)
 246        {
 0247            TempData["Error"] = $"Ошибка загрузки платёжных поручений: {ex.Message}";
 0248            return View(new PaymentsViewModel { DateFrom = dateFrom, DateTo = dateTo, Search = search });
 249        }
 0250    }
 251
 252    [HttpGet]
 253    public async Task<IActionResult> Detail(string docType, Guid refKey)
 254    {
 255        try
 256        {
 0257            var endpoint = docType == "receipt"
 0258                ? "Document_ПриходнаяНакладная"
 0259                : "Document_ПеремещениеЗапасов";
 260
 0261            var rawJson = await _client.GetRawJsonAsync(endpoint, refKey);
 0262            var jobj = JObject.Parse(rawJson);
 0263            var linesArr = jobj["Запасы"] as JArray;
 264
 0265            var nomMap = await BuildNomenclatureMapAsync(linesArr);
 0266            var lines = ParseLines(linesArr, nomMap);
 267
 0268            if (docType == "receipt")
 269            {
 0270                var vm = new ReceiptDetailViewModel
 0271                {
 0272                    RefKey = refKey,
 0273                    Number = jobj["Number"]?.ToString() ?? "",
 0274                    Date = jobj["Date"]?.ToString() ?? "",
 0275                    Posted = jobj["Posted"]?.Value<bool>() ?? false,
 0276                    DeletionMark = jobj["DeletionMark"]?.Value<bool>() ?? false,
 0277                    IncomingDocNumber = jobj["НомерВходящегоДокумента"]?.ToString() ?? "",
 0278                    IncomingDocDate = jobj["ДатаВходящегоДокумента"]?.ToString() ?? "",
 0279                    Comment = jobj["Комментарий"]?.ToString() ?? "",
 0280                    Lines = lines
 0281                };
 282
 0283                if (Guid.TryParse(jobj["Контрагент_Key"]?.ToString(), out var ctKey))
 284                {
 0285                    var cps = await _refService.GetCounterpartiesAsync();
 0286                    vm.CounterpartyName = cps.FirstOrDefault(c => c.RefKey == ctKey)?.Description ?? ctKey.ToString();
 287                }
 0288                if (Guid.TryParse(jobj["СтруктурнаяЕдиница_Key"]?.ToString(), out var whKey))
 289                {
 0290                    var whs = await _refService.GetWarehousesAsync();
 0291                    vm.WarehouseName = whs.FirstOrDefault(w => w.RefKey == whKey)?.Description ?? whKey.ToString();
 292                }
 293
 0294                return PartialView("_DetailModal", vm);
 295            }
 296            else
 297            {
 0298                var vm = new TransferDetailViewModel
 0299                {
 0300                    RefKey = refKey,
 0301                    Number = jobj["Number"]?.ToString() ?? "",
 0302                    Date = jobj["Date"]?.ToString() ?? "",
 0303                    Posted = jobj["Posted"]?.Value<bool>() ?? false,
 0304                    DeletionMark = jobj["DeletionMark"]?.Value<bool>() ?? false,
 0305                    Comment = jobj["Комментарий"]?.ToString() ?? "",
 0306                    Lines = lines
 0307                };
 308
 0309                var whs = await _refService.GetWarehousesAsync();
 0310                if (Guid.TryParse(jobj["СтруктурнаяЕдиница_Key"]?.ToString(), out var whFrom))
 0311                    vm.WarehouseSenderName = whs.FirstOrDefault(w => w.RefKey == whFrom)?.Description ?? whFrom.ToString
 0312                if (Guid.TryParse(jobj["СтруктурнаяЕдиницаПолучатель_Key"]?.ToString(), out var whTo))
 0313                    vm.WarehouseRecipientName = whs.FirstOrDefault(w => w.RefKey == whTo)?.Description ?? whTo.ToString(
 314
 0315                return PartialView("_DetailModal", vm);
 316            }
 317        }
 0318        catch (Exception ex)
 319        {
 0320            if (docType == "receipt")
 0321                return PartialView("_DetailModal", new ReceiptDetailViewModel { Error = ex.Message });
 0322            return PartialView("_DetailModal", new TransferDetailViewModel { Error = ex.Message });
 323        }
 0324    }
 325
 326    [HttpPost]
 327    [Authorize(Roles = "Admin")]
 328    [ValidateAntiForgeryToken]
 329    public async Task<IActionResult> PostDocument(string docType, Guid refKey, string returnUrl)
 330    {
 0331        var endpoint = docType == "receipt"
 0332            ? "Document_ПриходнаяНакладная"
 0333            : "Document_ПеремещениеЗапасов";
 334        try
 335        {
 0336            await _client.SetPostedAsync(endpoint, refKey, true);
 0337            TempData["Success"] = "Документ проведён.";
 0338        }
 0339        catch (Exception ex)
 340        {
 0341            TempData["Error"] = $"Ошибка проведения: {ex.Message}";
 0342        }
 0343        return Redirect(string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl);
 0344    }
 345
 346    [HttpPost]
 347    [Authorize(Roles = "Admin")]
 348    [ValidateAntiForgeryToken]
 349    public async Task<IActionResult> UnpostDocument(string docType, Guid refKey, string returnUrl)
 350    {
 0351        var endpoint = docType == "receipt"
 0352            ? "Document_ПриходнаяНакладная"
 0353            : "Document_ПеремещениеЗапасов";
 354        try
 355        {
 0356            await _client.SetPostedAsync(endpoint, refKey, false);
 0357            TempData["Success"] = "Проведение документа отменено.";
 0358        }
 0359        catch (Exception ex)
 360        {
 0361            TempData["Error"] = $"Ошибка отмены проведения: {ex.Message}";
 0362        }
 0363        return Redirect(string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl);
 0364    }
 365
 366    [HttpPost]
 367    [Authorize(Roles = "Admin")]
 368    [ValidateAntiForgeryToken]
 369    public async Task<IActionResult> UpdateComment(Guid refKey, string comment, string returnUrl)
 370    {
 371        try
 372        {
 0373            await _client.PatchAsync<ODataDocumentWithComment>("Document_ПеремещениеЗапасов", refKey, new { Комментарий 
 0374        }
 0375        catch (Exception ex)
 376        {
 0377            TempData["Error"] = $"Ошибка сохранения комментария: {ex.Message}";
 0378        }
 0379        return Redirect(string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl);
 0380    }
 381
 382    private async Task<Dictionary<string, (int orderId, int? txId, bool success, string kind)>> BuildPostingsMapAsync(Ha
 383    {
 0384        var postings = await _db.OrderHistoryOneCPostings
 0385            .AsNoTracking()
 0386            .Where(p => p.DocumentGuids != null)
 0387            .Select(p => new { p.DocumentGuids, p.OrderId, p.TransactionId, p.Success, p.OperationKind })
 0388            .ToListAsync();
 389
 0390        var result = new Dictionary<string, (int, int?, bool, string)>(StringComparer.OrdinalIgnoreCase);
 0391        foreach (var posting in postings)
 392        {
 0393            foreach (var guid in (posting.DocumentGuids ?? "").Split(',', StringSplitOptions.RemoveEmptyEntries | String
 394            {
 0395                if (guids.Contains(guid) && !result.ContainsKey(guid))
 0396                    result[guid] = (posting.OrderId, posting.TransactionId, posting.Success, posting.OperationKind ?? ""
 397            }
 398        }
 0399        return result;
 0400    }
 401
 402    private async Task<Dictionary<Guid, ODataNomenclature>> BuildNomenclatureMapAsync(JArray? linesArr)
 403    {
 0404        if (linesArr == null || linesArr.Count == 0)
 0405            return new Dictionary<Guid, ODataNomenclature>();
 406
 0407        var keys = linesArr
 0408            .Select(i => i["Номенклатура_Key"]?.ToString())
 0409            .Where(k => !string.IsNullOrEmpty(k) && Guid.TryParse(k, out _))
 0410            .Select(k => Guid.Parse(k!))
 0411            .Distinct()
 0412            .ToList();
 413
 0414        if (keys.Count == 0)
 0415            return new Dictionary<Guid, ODataNomenclature>();
 416
 0417        var filter = string.Join(" or ", keys.Select(k => $"Ref_Key eq guid'{k}'"));
 0418        var items = await _client.GetSinglePageAsync<ODataNomenclature>(
 0419            "Catalog_Номенклатура",
 0420            top: keys.Count + 10,
 0421            queryParams: new Dictionary<string, string>
 0422            {
 0423                ["$select"] = "Ref_Key,Description,Артикул",
 0424                ["$filter"] = filter
 0425            });
 426
 0427        return items.DistinctBy(n => n.RefKey).ToDictionary(n => n.RefKey);
 0428    }
 429
 430    private static List<DocumentLineItem> ParseLines(JArray? arr, Dictionary<Guid, ODataNomenclature> nomMap)
 431    {
 0432        var result = new List<DocumentLineItem>();
 0433        if (arr == null) return result;
 434
 0435        foreach (var item in arr)
 436        {
 0437            var line = new DocumentLineItem
 0438            {
 0439                Quantity = item["Количество"]?.Value<decimal>() ?? 0,
 0440                Price = item["Цена"]?.Value<decimal>() ?? 0,
 0441                Amount = item["Сумма"]?.Value<decimal>() ?? 0
 0442            };
 0443            if (Guid.TryParse(item["Номенклатура_Key"]?.ToString(), out var nomKey)
 0444                && nomMap.TryGetValue(nomKey, out var nom))
 445            {
 0446                line.NomenclatureName = nom.Description ?? string.Empty;
 0447                line.Article = nom.Article ?? string.Empty;
 448            }
 0449            result.Add(line);
 450        }
 0451        return result;
 452    }
 453
 454    private static string BuildDateFilter(string field, DateTime from, DateTime to)
 0455        => $"{field} ge datetime'{from:yyyy-MM-ddT00:00:00}' and {field} le datetime'{to:yyyy-MM-ddT23:59:59}'";
 456}