< Summary

Information
Class: Services.BitrixStockServices.BitrixStockExceptionService
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/BitrixStockServices/BitrixStockExceptionService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 181
Coverable lines: 181
Total lines: 279
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 80
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/BitrixStockServices/BitrixStockExceptionService.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using Microsoft.Extensions.Caching.Memory;
 3using OzonDomains.Models;
 4using OzonRepositories.Context;
 5
 6namespace Services.BitrixStockServices;
 7
 08public class BitrixStockExceptionService(ApplicationDbContext db, IMemoryCache cache) : IBitrixStockExceptionService
 9{
 10    private const string CacheKeyArticles = "bitrix-exc-articles";
 11    private const string CacheKeyPrices   = "bitrix-exc-prices";
 12    private const string CacheKeyStock    = "bitrix-exc-stock";
 013    private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(5);
 14
 15    // ──────────────────────────────────────────────────────────────────
 16    // Helpers
 17    // ──────────────────────────────────────────────────────────────────
 18
 19    private async Task<System.Data.Common.DbConnection> OpenAsync(CancellationToken ct)
 20    {
 021        var conn = db.Database.GetDbConnection();
 022        if (conn.State != System.Data.ConnectionState.Open)
 023            await conn.OpenAsync(ct);
 024        return conn;
 025    }
 26
 27    public void InvalidateCache()
 28    {
 029        cache.Remove(CacheKeyArticles + ":stock");
 030        cache.Remove(CacheKeyArticles + ":price");
 031        cache.Remove(CacheKeyPrices);
 032        cache.Remove(CacheKeyStock);
 033    }
 34
 35    // ──────────────────────────────────────────────────────────────────
 36    // ExceptionArticles
 37    // ──────────────────────────────────────────────────────────────────
 38
 39    public async Task<List<BitrixStockExceptionArticle>> GetExceptionArticlesAsync(CancellationToken ct = default)
 40    {
 041        var conn = await OpenAsync(ct);
 042        using var cmd = conn.CreateCommand();
 043        cmd.CommandText = "SELECT Id,Article,Comment,SkipStock,SkipPrice,IsActive,CreatedAt,UpdatedAt,CreatedBy FROM Bit
 044        var result = new List<BitrixStockExceptionArticle>();
 045        await using var r = await cmd.ExecuteReaderAsync(ct);
 046        while (await r.ReadAsync(ct)) result.Add(MapArticle(r));
 047        return result;
 048    }
 49
 50    public async Task<HashSet<string>> GetSkipStockArticlesSetAsync(CancellationToken ct = default)
 51    {
 052        if (cache.TryGetValue<HashSet<string>>(CacheKeyArticles + ":stock", out var cached) && cached != null)
 053            return cached;
 54
 055        var all = await GetExceptionArticlesAsync(ct);
 056        var set = new HashSet<string>(
 057            all.Where(a => a.IsActive && a.SkipStock).Select(a => a.Article.ToLowerInvariant()),
 058            StringComparer.OrdinalIgnoreCase);
 059        cache.Set(CacheKeyArticles + ":stock", set, CacheTtl);
 060        return set;
 061    }
 62
 63    public async Task<HashSet<string>> GetSkipPriceArticlesSetAsync(CancellationToken ct = default)
 64    {
 065        if (cache.TryGetValue<HashSet<string>>(CacheKeyArticles + ":price", out var cached) && cached != null)
 066            return cached;
 67
 068        var all = await GetExceptionArticlesAsync(ct);
 069        var set = new HashSet<string>(
 070            all.Where(a => a.IsActive && a.SkipPrice).Select(a => a.Article.ToLowerInvariant()),
 071            StringComparer.OrdinalIgnoreCase);
 072        cache.Set(CacheKeyArticles + ":price", set, CacheTtl);
 073        return set;
 074    }
 75
 76    public async Task<BitrixStockExceptionArticle> UpsertExceptionArticleAsync(BitrixStockExceptionArticle entity, Cance
 77    {
 078        var now = DateTime.UtcNow;
 079        if (entity.Id == 0)
 80        {
 081            entity.CreatedAt = now;
 082            entity.UpdatedAt = now;
 083            await db.Database.ExecuteSqlRawAsync(
 084                "INSERT INTO BitrixStockExceptionArticles (Article,Comment,SkipStock,SkipPrice,IsActive,CreatedAt,Update
 085                EfSqlRawParameterGuard.Materialize(entity.Article, entity.Comment, entity.SkipStock ? 1 : 0, entity.Skip
 086                ct);
 087            var conn = await OpenAsync(ct);
 088            using var idCmd = conn.CreateCommand();
 089            idCmd.CommandText = "SELECT LAST_INSERT_ID();";
 090            entity.Id = Convert.ToInt32(await idCmd.ExecuteScalarAsync(ct));
 091        }
 92        else
 93        {
 094            entity.UpdatedAt = now;
 095            await db.Database.ExecuteSqlRawAsync(
 096                "UPDATE BitrixStockExceptionArticles SET Article={0},Comment={1},SkipStock={2},SkipPrice={3},IsActive={4
 097                EfSqlRawParameterGuard.Materialize(entity.Article, entity.Comment, entity.SkipStock ? 1 : 0, entity.Skip
 098                ct);
 99        }
 0100        InvalidateCache();
 0101        return entity;
 0102    }
 103
 104    public async Task DeleteExceptionArticleAsync(int id, CancellationToken ct = default)
 105    {
 0106        await db.Database.ExecuteSqlRawAsync("DELETE FROM BitrixStockExceptionArticles WHERE Id={0};", EfSqlRawParameter
 0107        InvalidateCache();
 0108    }
 109
 110    // ──────────────────────────────────────────────────────────────────
 111    // ClientPrices
 112    // ──────────────────────────────────────────────────────────────────
 113
 114    public async Task<List<BitrixStockExceptionClientPrice>> GetClientPricesAsync(CancellationToken ct = default)
 115    {
 0116        var conn = await OpenAsync(ct);
 0117        using var cmd = conn.CreateCommand();
 0118        cmd.CommandText = "SELECT Id,Article,OzonClientId,PriceMode,FixedPrice,FixedOldPrice,Comment,IsActive,CreatedAt,
 0119        var result = new List<BitrixStockExceptionClientPrice>();
 0120        await using var r = await cmd.ExecuteReaderAsync(ct);
 0121        while (await r.ReadAsync(ct)) result.Add(MapClientPrice(r));
 0122        return result;
 0123    }
 124
 125    public async Task<Dictionary<(string article, int clientId), BitrixStockExceptionClientPrice>> GetClientPricesMapAsy
 126    {
 0127        if (cache.TryGetValue<Dictionary<(string, int), BitrixStockExceptionClientPrice>>(CacheKeyPrices, out var cached
 0128            return cached;
 129
 0130        var all = await GetClientPricesAsync(ct);
 0131        var map = all
 0132            .Where(p => p.IsActive)
 0133            .ToDictionary(p => (p.Article.ToLowerInvariant(), p.OzonClientId));
 0134        cache.Set(CacheKeyPrices, map, CacheTtl);
 0135        return map;
 0136    }
 137
 138    public async Task<BitrixStockExceptionClientPrice> UpsertClientPriceAsync(BitrixStockExceptionClientPrice entity, Ca
 139    {
 0140        var now = DateTime.UtcNow;
 0141        if (entity.Id == 0)
 142        {
 0143            entity.CreatedAt = now;
 0144            entity.UpdatedAt = now;
 0145            await db.Database.ExecuteSqlRawAsync(
 0146                "INSERT INTO BitrixStockExceptionClientPrices (Article,OzonClientId,PriceMode,FixedPrice,FixedOldPrice,C
 0147                EfSqlRawParameterGuard.Materialize(entity.Article, entity.OzonClientId, entity.PriceMode, entity.FixedPr
 0148                ct);
 0149            var conn = await OpenAsync(ct);
 0150            using var idCmd = conn.CreateCommand();
 0151            idCmd.CommandText = "SELECT LAST_INSERT_ID();";
 0152            entity.Id = Convert.ToInt32(await idCmd.ExecuteScalarAsync(ct));
 0153        }
 154        else
 155        {
 0156            entity.UpdatedAt = now;
 0157            await db.Database.ExecuteSqlRawAsync(
 0158                "UPDATE BitrixStockExceptionClientPrices SET Article={0},OzonClientId={1},PriceMode={2},FixedPrice={3},F
 0159                EfSqlRawParameterGuard.Materialize(entity.Article, entity.OzonClientId, entity.PriceMode, entity.FixedPr
 0160                ct);
 161        }
 0162        InvalidateCache();
 0163        return entity;
 0164    }
 165
 166    public async Task DeleteClientPriceAsync(int id, CancellationToken ct = default)
 167    {
 0168        await db.Database.ExecuteSqlRawAsync("DELETE FROM BitrixStockExceptionClientPrices WHERE Id={0};", EfSqlRawParam
 0169        InvalidateCache();
 0170    }
 171
 172    // ──────────────────────────────────────────────────────────────────
 173    // StockRules
 174    // ──────────────────────────────────────────────────────────────────
 175
 176    public async Task<List<BitrixStockExceptionStockRule>> GetStockRulesAsync(CancellationToken ct = default)
 177    {
 0178        var conn = await OpenAsync(ct);
 0179        using var cmd = conn.CreateCommand();
 0180        cmd.CommandText = "SELECT Id,Article,OzonClientId,StockMode,Comment,IsActive,CreatedAt,UpdatedAt,CreatedBy FROM 
 0181        var result = new List<BitrixStockExceptionStockRule>();
 0182        await using var r = await cmd.ExecuteReaderAsync(ct);
 0183        while (await r.ReadAsync(ct)) result.Add(MapStockRule(r));
 0184        return result;
 0185    }
 186
 187    public async Task<BitrixStockExceptionStockRule?> FindStockRuleAsync(string article, int clientId, CancellationToken
 188    {
 0189        if (cache.TryGetValue<List<BitrixStockExceptionStockRule>>(CacheKeyStock, out var allCached) && allCached != nul
 190        {
 0191            return allCached.FirstOrDefault(r => r.IsActive && r.Article.Equals(article, StringComparison.OrdinalIgnoreC
 0192                ?? allCached.FirstOrDefault(r => r.IsActive && r.Article.Equals(article, StringComparison.OrdinalIgnoreC
 193        }
 194
 0195        var all = await GetStockRulesAsync(ct);
 0196        cache.Set(CacheKeyStock, all, CacheTtl);
 0197        return all.FirstOrDefault(r => r.IsActive && r.Article.Equals(article, StringComparison.OrdinalIgnoreCase) && r.
 0198            ?? all.FirstOrDefault(r => r.IsActive && r.Article.Equals(article, StringComparison.OrdinalIgnoreCase) && r.
 0199    }
 200
 201    public async Task<BitrixStockExceptionStockRule> UpsertStockRuleAsync(BitrixStockExceptionStockRule entity, Cancella
 202    {
 0203        var now = DateTime.UtcNow;
 0204        if (entity.Id == 0)
 205        {
 0206            entity.CreatedAt = now;
 0207            entity.UpdatedAt = now;
 0208            await db.Database.ExecuteSqlRawAsync(
 0209                "INSERT INTO BitrixStockExceptionStockRules (Article,OzonClientId,StockMode,Comment,IsActive,CreatedAt,U
 0210                EfSqlRawParameterGuard.Materialize(entity.Article, entity.OzonClientId, entity.StockMode, entity.Comment
 0211                ct);
 0212            var conn = await OpenAsync(ct);
 0213            using var idCmd = conn.CreateCommand();
 0214            idCmd.CommandText = "SELECT LAST_INSERT_ID();";
 0215            entity.Id = Convert.ToInt32(await idCmd.ExecuteScalarAsync(ct));
 0216        }
 217        else
 218        {
 0219            entity.UpdatedAt = now;
 0220            await db.Database.ExecuteSqlRawAsync(
 0221                "UPDATE BitrixStockExceptionStockRules SET Article={0},OzonClientId={1},StockMode={2},Comment={3},IsActi
 0222                EfSqlRawParameterGuard.Materialize(entity.Article, entity.OzonClientId, entity.StockMode, entity.Comment
 0223                ct);
 224        }
 0225        InvalidateCache();
 0226        return entity;
 0227    }
 228
 229    public async Task DeleteStockRuleAsync(int id, CancellationToken ct = default)
 230    {
 0231        await db.Database.ExecuteSqlRawAsync("DELETE FROM BitrixStockExceptionStockRules WHERE Id={0};", EfSqlRawParamet
 0232        InvalidateCache();
 0233    }
 234
 235    // ──────────────────────────────────────────────────────────────────
 236    // Mappers
 237    // ──────────────────────────────────────────────────────────────────
 238
 0239    private static BitrixStockExceptionArticle MapArticle(System.Data.Common.DbDataReader r) => new()
 0240    {
 0241        Id        = r.GetInt32(0),
 0242        Article   = r.GetString(1),
 0243        Comment   = r.IsDBNull(2) ? null : r.GetString(2),
 0244        SkipStock = r.GetBoolean(3),
 0245        SkipPrice = r.GetBoolean(4),
 0246        IsActive  = r.GetBoolean(5),
 0247        CreatedAt = r.GetDateTime(6),
 0248        UpdatedAt = r.GetDateTime(7),
 0249        CreatedBy = r.IsDBNull(8) ? null : r.GetString(8),
 0250    };
 251
 0252    private static BitrixStockExceptionClientPrice MapClientPrice(System.Data.Common.DbDataReader r) => new()
 0253    {
 0254        Id            = r.GetInt32(0),
 0255        Article       = r.GetString(1),
 0256        OzonClientId  = r.GetInt32(2),
 0257        PriceMode     = r.GetString(3),
 0258        FixedPrice    = r.IsDBNull(4) ? null : r.GetDecimal(4),
 0259        FixedOldPrice = r.IsDBNull(5) ? null : r.GetDecimal(5),
 0260        Comment       = r.IsDBNull(6) ? null : r.GetString(6),
 0261        IsActive      = r.GetBoolean(7),
 0262        CreatedAt     = r.GetDateTime(8),
 0263        UpdatedAt     = r.GetDateTime(9),
 0264        CreatedBy     = r.IsDBNull(10) ? null : r.GetString(10),
 0265    };
 266
 0267    private static BitrixStockExceptionStockRule MapStockRule(System.Data.Common.DbDataReader r) => new()
 0268    {
 0269        Id           = r.GetInt32(0),
 0270        Article      = r.GetString(1),
 0271        OzonClientId = r.IsDBNull(2) ? null : r.GetInt32(2),
 0272        StockMode    = r.GetString(3),
 0273        Comment      = r.IsDBNull(4) ? null : r.GetString(4),
 0274        IsActive     = r.GetBoolean(5),
 0275        CreatedAt    = r.GetDateTime(6),
 0276        UpdatedAt    = r.GetDateTime(7),
 0277        CreatedBy    = r.IsDBNull(8) ? null : r.GetString(8),
 0278    };
 279}