| | | 1 | | using System.Data.Common; |
| | | 2 | | using Microsoft.EntityFrameworkCore; |
| | | 3 | | using OzonRepositories.Context; |
| | | 4 | | |
| | | 5 | | namespace Services.BitrixStockServices; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// Данные грида отчёта синхронизации (/Studio2/BitrixStock?tab=sync&report=…): блоки строк с серверной |
| | | 9 | | /// сортировкой/фильтрами и счётчики значений для справочных фильтров. |
| | | 10 | | /// </summary> |
| | | 11 | | public interface IBitrixStockSyncReportGridService |
| | | 12 | | { |
| | | 13 | | Task<BitrixSyncGridLinesResult> GetLinesAsync(string reportId, BitrixSyncGridLinesQuery query, CancellationToken can |
| | | 14 | | |
| | | 15 | | /// <summary>Значения колонки с количеством строк при текущих фильтрах (без фильтра самой колонки).</summary> |
| | | 16 | | Task<List<BitrixSyncGridFacetValue>> GetFacetsAsync(string reportId, string colId, BitrixSyncGridLinesQuery query, C |
| | | 17 | | } |
| | | 18 | | |
| | | 19 | | public sealed class BitrixStockSyncReportGridService : IBitrixStockSyncReportGridService |
| | | 20 | | { |
| | | 21 | | private const int CommandTimeoutSeconds = 60; |
| | | 22 | | private const int MaxFacetValues = 500; |
| | | 23 | | |
| | | 24 | | private readonly IDbContextFactory<ApplicationDbContext> _dbFactory; |
| | | 25 | | |
| | 0 | 26 | | public BitrixStockSyncReportGridService(IDbContextFactory<ApplicationDbContext> dbFactory) |
| | | 27 | | { |
| | 0 | 28 | | _dbFactory = dbFactory; |
| | 0 | 29 | | } |
| | | 30 | | |
| | | 31 | | public async Task<BitrixSyncGridLinesResult> GetLinesAsync( |
| | | 32 | | string reportId, |
| | | 33 | | BitrixSyncGridLinesQuery query, |
| | | 34 | | CancellationToken cancellationToken = default) |
| | | 35 | | { |
| | 0 | 36 | | var result = new BitrixSyncGridLinesResult(); |
| | 0 | 37 | | if (string.IsNullOrWhiteSpace(reportId)) return result; |
| | | 38 | | |
| | 0 | 39 | | var start = Math.Max(0, query.StartRow); |
| | 0 | 40 | | var take = Math.Clamp(query.EndRow - start, 1, BitrixStockSyncReportGridSql.MaxBlockSize); |
| | 0 | 41 | | var built = BitrixStockSyncReportGridSql.Build(reportId.Trim(), query); |
| | | 42 | | |
| | 0 | 43 | | await using var db = await _dbFactory.CreateDbContextAsync(cancellationToken); |
| | 0 | 44 | | await db.Database.OpenConnectionAsync(cancellationToken); |
| | | 45 | | try |
| | | 46 | | { |
| | 0 | 47 | | var connection = db.Database.GetDbConnection(); |
| | | 48 | | |
| | 0 | 49 | | await using (var countCmd = CreateCommand(connection, built)) |
| | | 50 | | { |
| | 0 | 51 | | countCmd.CommandText = "SELECT COUNT(*) FROM BitrixStockSyncReportLines l" |
| | 0 | 52 | | + (built.NeedsPriceJoin ? BitrixStockSyncReportGridSql.PriceJoinSql : string.Empt |
| | 0 | 53 | | + built.Where; |
| | 0 | 54 | | result.TotalCount = Convert.ToInt32(await countCmd.ExecuteScalarAsync(cancellationToken) ?? 0); |
| | | 55 | | } |
| | | 56 | | |
| | 0 | 57 | | if (result.TotalCount == 0 || start >= result.TotalCount) return result; |
| | | 58 | | |
| | 0 | 59 | | await using var rowsCmd = CreateCommand(connection, built); |
| | 0 | 60 | | rowsCmd.CommandText = "SELECT " + BitrixStockSyncReportGridSql.SelectColumns |
| | 0 | 61 | | + " FROM BitrixStockSyncReportLines l" + BitrixStockSyncReportGridSql.PriceJoinSql |
| | 0 | 62 | | + built.Where + built.OrderBy + " LIMIT @take OFFSET @skip"; |
| | 0 | 63 | | AddParam(rowsCmd, "@take", take); |
| | 0 | 64 | | AddParam(rowsCmd, "@skip", start); |
| | | 65 | | |
| | 0 | 66 | | await using var reader = await rowsCmd.ExecuteReaderAsync(cancellationToken); |
| | 0 | 67 | | while (await reader.ReadAsync(cancellationToken)) |
| | | 68 | | { |
| | 0 | 69 | | result.Rows.Add(ReadLine(reader)); |
| | | 70 | | } |
| | 0 | 71 | | } |
| | | 72 | | finally |
| | | 73 | | { |
| | 0 | 74 | | await db.Database.CloseConnectionAsync(); |
| | | 75 | | } |
| | | 76 | | |
| | 0 | 77 | | return result; |
| | 0 | 78 | | } |
| | | 79 | | |
| | | 80 | | public async Task<List<BitrixSyncGridFacetValue>> GetFacetsAsync( |
| | | 81 | | string reportId, |
| | | 82 | | string colId, |
| | | 83 | | BitrixSyncGridLinesQuery query, |
| | | 84 | | CancellationToken cancellationToken = default) |
| | | 85 | | { |
| | 0 | 86 | | var result = new List<BitrixSyncGridFacetValue>(); |
| | 0 | 87 | | if (string.IsNullOrWhiteSpace(reportId)) return result; |
| | 0 | 88 | | if (!BitrixStockSyncReportGridSql.TryGetFacetExpression(colId, out var expression, out var needsPrice)) return r |
| | | 89 | | |
| | 0 | 90 | | var built = BitrixStockSyncReportGridSql.Build(reportId.Trim(), query, excludeColId: colId); |
| | | 91 | | |
| | 0 | 92 | | await using var db = await _dbFactory.CreateDbContextAsync(cancellationToken); |
| | 0 | 93 | | await db.Database.OpenConnectionAsync(cancellationToken); |
| | | 94 | | try |
| | | 95 | | { |
| | 0 | 96 | | await using var cmd = CreateCommand(db.Database.GetDbConnection(), built); |
| | 0 | 97 | | cmd.CommandText = "SELECT COALESCE(" + expression + ", '') AS v, COUNT(*) FROM BitrixStockSyncReportLines l" |
| | 0 | 98 | | + (needsPrice || built.NeedsPriceJoin ? BitrixStockSyncReportGridSql.PriceJoinSql : string |
| | 0 | 99 | | + built.Where + " GROUP BY v ORDER BY COUNT(*) DESC LIMIT " + MaxFacetValues; |
| | 0 | 100 | | await using var reader = await cmd.ExecuteReaderAsync(cancellationToken); |
| | 0 | 101 | | while (await reader.ReadAsync(cancellationToken)) |
| | | 102 | | { |
| | 0 | 103 | | result.Add(new BitrixSyncGridFacetValue |
| | 0 | 104 | | { |
| | 0 | 105 | | Value = reader.IsDBNull(0) ? string.Empty : Convert.ToString(reader.GetValue(0)) ?? string.Empty, |
| | 0 | 106 | | Count = Convert.ToInt32(reader.GetValue(1)) |
| | 0 | 107 | | }); |
| | | 108 | | } |
| | 0 | 109 | | } |
| | | 110 | | finally |
| | | 111 | | { |
| | 0 | 112 | | await db.Database.CloseConnectionAsync(); |
| | | 113 | | } |
| | | 114 | | |
| | 0 | 115 | | return result; |
| | 0 | 116 | | } |
| | | 117 | | |
| | | 118 | | private static DbCommand CreateCommand(DbConnection connection, BitrixStockSyncReportGridSql.Built built) |
| | | 119 | | { |
| | 0 | 120 | | var cmd = connection.CreateCommand(); |
| | 0 | 121 | | cmd.CommandTimeout = CommandTimeoutSeconds; |
| | 0 | 122 | | foreach (var (name, value) in built.Parameters) |
| | | 123 | | { |
| | 0 | 124 | | AddParam(cmd, name, value); |
| | | 125 | | } |
| | | 126 | | |
| | 0 | 127 | | return cmd; |
| | | 128 | | } |
| | | 129 | | |
| | | 130 | | private static void AddParam(DbCommand cmd, string name, object? value) |
| | | 131 | | { |
| | 0 | 132 | | var p = cmd.CreateParameter(); |
| | 0 | 133 | | p.ParameterName = name; |
| | 0 | 134 | | p.Value = value ?? DBNull.Value; |
| | 0 | 135 | | cmd.Parameters.Add(p); |
| | 0 | 136 | | } |
| | | 137 | | |
| | | 138 | | private static BitrixSyncGridLineDto ReadLine(DbDataReader r) |
| | | 139 | | { |
| | 0 | 140 | | string? S(int i) => r.IsDBNull(i) ? null : Convert.ToString(r.GetValue(i)); |
| | 0 | 141 | | int? I(int i) => r.IsDBNull(i) ? null : Convert.ToInt32(r.GetValue(i)); |
| | 0 | 142 | | long? L(int i) => r.IsDBNull(i) ? null : Convert.ToInt64(r.GetValue(i)); |
| | 0 | 143 | | decimal? D(int i) => r.IsDBNull(i) ? null : Convert.ToDecimal(r.GetValue(i)); |
| | 0 | 144 | | DateTime? T(int i) => r.IsDBNull(i) ? null : r.GetDateTime(i); |
| | 0 | 145 | | bool B(int i) => !r.IsDBNull(i) && Convert.ToInt32(r.GetValue(i)) != 0; |
| | | 146 | | |
| | 0 | 147 | | return new BitrixSyncGridLineDto |
| | 0 | 148 | | { |
| | 0 | 149 | | LineId = r.GetInt64(0), |
| | 0 | 150 | | ProductId = I(1) ?? 0, |
| | 0 | 151 | | Article = S(2) ?? string.Empty, |
| | 0 | 152 | | OfferId = S(3), |
| | 0 | 153 | | OneCStore = S(4) ?? string.Empty, |
| | 0 | 154 | | OzonWarehouseName = S(5) ?? string.Empty, |
| | 0 | 155 | | OzonWarehouseId = L(6) ?? 0, |
| | 0 | 156 | | MappingDescription = S(7) ?? string.Empty, |
| | 0 | 157 | | OneCQty = I(8) ?? 0, |
| | 0 | 158 | | OzonQty = I(9) ?? 0, |
| | 0 | 159 | | Delta = I(10) ?? 0, |
| | 0 | 160 | | ReservedQty = I(11) ?? 0, |
| | 0 | 161 | | OzonQtyAfter = I(12), |
| | 0 | 162 | | Action = S(13) ?? string.Empty, |
| | 0 | 163 | | ActionLabel = S(14) ?? string.Empty, |
| | 0 | 164 | | ActionIcon = S(15) ?? string.Empty, |
| | 0 | 165 | | RowClass = S(16) ?? string.Empty, |
| | 0 | 166 | | Note = S(17), |
| | 0 | 167 | | OzonProductId = L(18), |
| | 0 | 168 | | OzonApplyStatus = S(19), |
| | 0 | 169 | | OzonAppliedUtc = T(20), |
| | 0 | 170 | | OzonApplyError = S(21), |
| | 0 | 171 | | OzonCleanupStatus = S(22), |
| | 0 | 172 | | OzonCleanupAtUtc = T(23), |
| | 0 | 173 | | OzonCleanupError = S(24), |
| | 0 | 174 | | OzonClientId = I(25) ?? 0, |
| | 0 | 175 | | OzonClientName = S(26) ?? string.Empty, |
| | 0 | 176 | | OneCPrice = D(27), |
| | 0 | 177 | | PrevOneCPrice = D(28), |
| | 0 | 178 | | IsArchived = B(29), |
| | 0 | 179 | | IsAutoArchived = B(30), |
| | 0 | 180 | | PromoName = S(31), |
| | 0 | 181 | | IsOzonOrphan = B(32), |
| | 0 | 182 | | OzonOrphanStatus = S(33), |
| | 0 | 183 | | BatchMultiplier = I(34) ?? 1, |
| | 0 | 184 | | BatchBaseArticle = S(35), |
| | 0 | 185 | | BatchPushedPrice = D(36), |
| | 0 | 186 | | ManufacturerName = S(37), |
| | 0 | 187 | | DeltaC0 = S(38) ?? "none", |
| | 0 | 188 | | TargetQty = I(39), |
| | 0 | 189 | | InPromo = S(40) == "yes", |
| | 0 | 190 | | TruePrice = D(41), |
| | 0 | 191 | | SellingPrice = D(42), |
| | 0 | 192 | | ColorIndex = S(43) ?? "NONE", |
| | 0 | 193 | | PricePushedOld = D(44), |
| | 0 | 194 | | PricePushedNew = D(45), |
| | 0 | 195 | | PricePushStatus = S(46), |
| | 0 | 196 | | PricePushError = S(47) |
| | 0 | 197 | | }; |
| | | 198 | | } |
| | | 199 | | } |