< Summary

Information
Class: Services.OzonWebhookServices.OzonPostingCancellationService
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/OzonWebhookServices/OzonPostingCancellationService.cs
Line coverage
47%
Covered lines: 32
Uncovered lines: 35
Coverable lines: 67
Total lines: 112
Line coverage: 47.7%
Branch coverage
27%
Covered branches: 10
Total branches: 36
Branch coverage: 27.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
RecordAsync()66.66%1212100%
LinkOrdersAsync()0%2040%
LinkImportedOrdersAsync()0%620%
PopulateLatestForOrdersAsync()0%2040%
AddLinksAsync()100%22100%
CreateFingerprint(...)50%22100%
Trim(...)50%22100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/OzonWebhookServices/OzonPostingCancellationService.cs

#LineLine coverage
 1using System.Security.Cryptography;
 2using System.Text;
 3using Microsoft.EntityFrameworkCore;
 4using OzonDomains.Models;
 5using OzonRepositories.Context;
 6
 7namespace Services.OzonWebhookServices;
 8
 9public interface IOzonPostingCancellationService
 10{
 11    Task<OzonPostingCancellation> RecordAsync(int ozonClientId, string postingNumber, long? reasonId,
 12        string? reasonTitle, string? reasonMessage, string? reasonTypeId, DateTime occurredAt, string source,
 13        IReadOnlyCollection<int>? orderIds = null, CancellationToken cancellationToken = default);
 14    Task LinkOrdersAsync(IReadOnlyCollection<Order> orders, CancellationToken cancellationToken = default);
 15    Task LinkImportedOrdersAsync(IReadOnlyCollection<Order> orders, CancellationToken cancellationToken = default);
 16    Task PopulateLatestForOrdersAsync(IReadOnlyCollection<Order> orders, CancellationToken cancellationToken = default);
 17}
 18
 119public sealed class OzonPostingCancellationService(ApplicationDbContext db) : IOzonPostingCancellationService
 20{
 21    public async Task<OzonPostingCancellation> RecordAsync(int ozonClientId, string postingNumber, long? reasonId,
 22        string? reasonTitle, string? reasonMessage, string? reasonTypeId, DateTime occurredAt, string source,
 23        IReadOnlyCollection<int>? orderIds = null, CancellationToken cancellationToken = default)
 24    {
 225        var normalizedPosting = postingNumber.Trim();
 226        var fingerprint = CreateFingerprint(ozonClientId, normalizedPosting, reasonId, reasonMessage, occurredAt, source
 227        var cancellation = await db.OzonPostingCancellations
 228            .Include(x => x.OrderLinks)
 229            .SingleOrDefaultAsync(x => x.EventFingerprint == fingerprint, cancellationToken);
 230        if (cancellation == null)
 31        {
 132            cancellation = new OzonPostingCancellation
 133            {
 134                OzonClientId = ozonClientId, PostingNumber = normalizedPosting, ReasonId = reasonId,
 135                ReasonTitle = Trim(reasonTitle, 500), ReasonMessage = reasonMessage?.Trim(),
 136                ReasonTypeId = Trim(reasonTypeId, 32), OccurredAt = occurredAt, Source = Trim(source, 20) ?? "Webhook",
 137                EventFingerprint = fingerprint, CreatedAt = DateTime.UtcNow
 138            };
 139            db.OzonPostingCancellations.Add(cancellation);
 140            await db.SaveChangesAsync(cancellationToken);
 41        }
 42
 643        var ids = orderIds?.Where(x => x > 0).Distinct().ToArray() ?? [];
 244        if (ids.Length > 0)
 245            await AddLinksAsync(cancellation, ids, cancellationToken);
 246        return cancellation;
 247    }
 48
 49    public async Task LinkOrdersAsync(IReadOnlyCollection<Order> orders, CancellationToken cancellationToken = default)
 50    {
 051        var candidates = orders.Where(x => x.Id > 0 && x.OzonClientId.HasValue && !string.IsNullOrWhiteSpace(x.ShipmentN
 052        if (candidates.Count == 0) return;
 053        var clientIds = candidates.Select(x => x.OzonClientId!.Value).Distinct().ToArray();
 054        var postings = candidates.Select(x => x.ShipmentNumber!.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArr
 055        var cancellations = await db.OzonPostingCancellations
 056            .Where(x => clientIds.Contains(x.OzonClientId) && postings.Contains(x.PostingNumber))
 057            .ToListAsync(cancellationToken);
 058        foreach (var cancellation in cancellations)
 59        {
 060            var ids = candidates.Where(x => x.OzonClientId == cancellation.OzonClientId &&
 061                                             string.Equals(x.ShipmentNumber!.Trim(), cancellation.PostingNumber, StringC
 062                .Select(x => x.Id).ToArray();
 063            await AddLinksAsync(cancellation, ids, cancellationToken);
 64        }
 065    }
 66
 67    public async Task LinkImportedOrdersAsync(IReadOnlyCollection<Order> orders, CancellationToken cancellationToken = d
 68    {
 069        var candidates = orders.Where(x => (x.OzonClientId.HasValue || x.OzonClient != null) && !string.IsNullOrWhiteSpa
 070            .Select(x => new { ClientId = x.OzonClientId ?? x.OzonClient!.Id, Posting = x.ShipmentNumber!.Trim() }).Dist
 071        if (candidates.Count == 0) return;
 072        var clientIds = candidates.Select(x => x.ClientId).Distinct().ToArray();
 073        var postings = candidates.Select(x => x.Posting).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
 074        var persisted = await db.Orders.Where(x => x.OzonClientId.HasValue && clientIds.Contains(x.OzonClientId.Value) &
 075                                                    x.ShipmentNumber != null && postings.Contains(x.ShipmentNumber))
 076            .ToListAsync(cancellationToken);
 077        await LinkOrdersAsync(persisted, cancellationToken);
 078    }
 79
 80    public async Task PopulateLatestForOrdersAsync(IReadOnlyCollection<Order> orders, CancellationToken cancellationToke
 81    {
 082        var ids = orders.Select(x => x.Id).Where(x => x > 0).Distinct().ToArray();
 083        if (ids.Length == 0) return;
 084        var latest = await db.OzonPostingCancellationOrders.AsNoTracking()
 085            .Where(x => ids.Contains(x.OrderId))
 086            .Select(x => new { x.OrderId, x.Cancellation })
 087            .GroupBy(x => x.OrderId)
 088            .Select(g => g.OrderByDescending(x => x.Cancellation.OccurredAt).ThenByDescending(x => x.Cancellation.Id).Fi
 089            .ToListAsync(cancellationToken);
 090        var byOrder = latest.ToDictionary(x => x.OrderId, x => x.Cancellation);
 091        foreach (var order in orders)
 092            order.LatestOzonPostingCancellation = byOrder.GetValueOrDefault(order.Id);
 093    }
 94
 95    private async Task AddLinksAsync(OzonPostingCancellation cancellation, IReadOnlyCollection<int> orderIds, Cancellati
 96    {
 297        var existing = await db.OzonPostingCancellationOrders
 298            .Where(x => x.CancellationId == cancellation.Id && orderIds.Contains(x.OrderId))
 299            .Select(x => x.OrderId).ToListAsync(ct);
 4100        var links = orderIds.Except(existing).Select(id => new OzonPostingCancellationOrder { CancellationId = cancellat
 3101        if (links.Count == 0) return;
 1102        db.OzonPostingCancellationOrders.AddRange(links);
 1103        await db.SaveChangesAsync(ct);
 2104    }
 105
 106    private static string CreateFingerprint(int clientId, string posting, long? reasonId, string? message, DateTime occu
 107    {
 2108        var input = $"{clientId}|{posting}|{reasonId}|{message?.Trim()}|{occurredAt:O}|{source}";
 2109        return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input)));
 110    }
 3111    private static string? Trim(string? value, int max) => string.IsNullOrWhiteSpace(value) ? null : value.Trim()[..Math
 112}