< Summary

Line coverage
100%
Covered lines: 85
Uncovered lines: 0
Coverable lines: 85
Total lines: 2533
Line coverage: 100%
Branch coverage
96%
Covered branches: 106
Total branches: 110
Branch coverage: 96.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/AiAssistant/AiWriteIntentParser.cs

#LineLine coverage
 1using System.Text.RegularExpressions;
 2using OzonDomains;
 3
 4namespace Services.AiAssistant;
 5
 6/// <summary>
 7/// Определяет write-интенты (редактирование заказа, транзакции) из пользовательских сообщений.
 8/// </summary>
 9public static partial class AiWriteIntentParser
 10{
 11    // ── Общие детекторы ────────────────────────────────────────────────────────
 12
 13    public static bool IsEditOrderIntent(string lower) =>
 1614        (lower.Contains("измен") || lower.Contains("поставь") || lower.Contains("установи") ||
 1615         lower.Contains("смени") || lower.Contains("обнови") || lower.Contains("поменяй") ||
 1616         lower.Contains("задай") || lower.Contains("назначь") || lower.Contains("укажи")) &&
 1617        (lower.Contains("поставщик") || lower.Contains("склад") || lower.Contains("статус") ||
 1618         lower.Contains("цен") || lower.Contains("количеств") || lower.Contains("номер заказ"));
 19
 20    public static bool IsTransactionIntent(string lower)
 21    {
 2522        if (AiOrderFilterParser.IsReadOnlyOrderSearchIntent(lower))
 323            return false;
 24
 2225        return lower.Contains("проведи") || lower.Contains("проведите") || lower.Contains("провести") ||
 2226               lower.Contains("заказан поставщику") || lower.Contains("заказан реализатору") ||
 2227               lower.Contains("принят на склад") ||
 2228               lower.Contains("отгружен поставщиком") || lower.Contains("отгрузить клиенту") ||
 2229               lower.Contains("отгрузить к клиент") ||
 2230               (lower.Contains("создай транзакци") || lower.Contains("создать транзакци")) ||
 2231               lower.Contains("к отмене") || lower.Contains("пометь к отмене") ||
 2232               (lower.Contains("отмен") && (lower.Contains("заказ") || lower.Contains("транзакц"))
 2233                && !LooksLikeStatusFilterPhrase(lower)) ||
 2234               lower.Contains("возврат покупател") || lower.Contains("вернуть поставщику") ||
 2235               lower.Contains("возврат на склад") || lower.Contains("перемести") ||
 2236               lower.Contains("изменение склада") || lower.Contains("смени склад");
 37    }
 38
 39    /// <summary>«со статусом Отменен» — фильтр поиска, не команда отменить заказ.</summary>
 40    private static bool LooksLikeStatusFilterPhrase(string lower) =>
 241        lower.Contains("со статус") || lower.Contains("статусом") || lower.Contains("в статусе")
 242        || lower.Contains("статус ");
 43
 44    // ── Тип транзакции ─────────────────────────────────────────────────────────
 45
 146    private static readonly (string[] Keywords, TransactionType Type)[] TransactionKeywordMap =
 147    [
 148        (["заказан поставщику", "закажи у поставщика", "заказать у поставщика"], TransactionType.OrderedToSupplier),
 149        (["заказан реализатору", "закажи у реализатора", "заказать у реализатора"], TransactionType.OrderedToSeller),
 150        (["отгружен поставщиком", "поставщик отгрузил", "принята от поставщика"], TransactionType.ShippedBySupplier),
 151        (["отгружен реализатором", "реализатор отгрузил"], TransactionType.ShippedToSeller),
 152        (["принят на склад", "принять на склад", "принято на склад"], TransactionType.AcceptedToWarehouse),
 153        (["отгружен клиенту", "отгрузить клиенту", "отправить клиенту"], TransactionType.ShippedToClient),
 154        (["возврат покупателя", "покупатель вернул", "возврат от покупателя"], TransactionType.CustomerReturn),
 155        (["вернуть поставщику", "возвращено поставщику", "возврат поставщику"], TransactionType.ReturnToSupplier),
 156        (["возврат на склад", "вернуть на склад", "вернуть на наш склад"], TransactionType.ReturnToWarehouse),
 157        (["перемещение между складами", "переместить между складами", "перемести товар"], TransactionType.WarehouseTrans
 158        (["изменение склада", "смени склад", "изменить склад"], TransactionType.WarehouseChange),
 159        (["к отмене", "пометь к отмене", "пометить к отмене"], TransactionType.ToCancellation),
 160        (["отменить заказ", "отмена заказа", "провести отмену", "создать транзакцию отмен"], TransactionType.Cancelled),
 161        (["отгружен комиссионеру", "отправить на склад ozon", "отгрузить ozon"], TransactionType.ShippedToCommissioner),
 162        (["утерян реализатором", "потерян реализатором"], TransactionType.LostByCommissioner),
 163        (["перемещение", "перенести заказ"], TransactionType.ReturnTransfer),
 164    ];
 65
 66    public static TransactionType? TryParseTransactionType(string lower)
 67    {
 92068        foreach (var (keywords, type) in TransactionKeywordMap)
 69        {
 43470            if (keywords.Any(lower.Contains))
 4671                return type;
 72        }
 373        return null;
 74    }
 75
 76    // ── Извлечение имён из сообщения ────────────────────────────────────────────
 77
 78    [GeneratedRegex(@"поставщик[аеу]?\s+(?:на\s+)?[«""']?([А-Яа-яёA-Za-z0-9][А-Яа-яёA-Za-z0-9\s\-]{1,40}?)[»""']?(?:\s|$
 79    private static partial Regex SupplierNameRegex();
 80
 81    [GeneratedRegex(@"(?:статус|статуса?)\s+(?:на\s+)?[«""']?([А-Яа-яё][А-Яа-яё\s]{3,40}?)[»""']?(?:\s|$|,|\.)", RegexOp
 82    private static partial Regex StatusNameRegex();
 83
 84    [GeneratedRegex(@"(?:склад[еу]?|склад отгрузки)\s+(?:на\s+)?[«""']?([А-Яа-яёA-Za-z0-9][А-Яа-яёA-Za-z0-9\s\-]{1,40}?)
 85    private static partial Regex WarehouseNameRegex();
 86
 87    // Склад-отправитель: "со склада X", "отправитель X", "с X"
 88    [GeneratedRegex(@"(?:со?\s+склад[аеу]?|склад[аеу]?\s*-\s*отправитель|откуда\s+склад)\s+[«""']?([А-Яа-яёA-Za-z0-9][А-
 89    private static partial Regex WarehouseSenderRegex();
 90
 91    // Склад-получатель: "на склад X", "получатель X", "куда X"
 92    [GeneratedRegex(@"(?:на\s+склад[еу]?|склад[аеу]?\s*-\s*получатель|куда\s+склад)\s+[«""']?([А-Яа-яёA-Za-z0-9][А-Яа-яё
 93    private static partial Regex WarehouseRecipientRegex();
 94
 95    // Матчит: "цена 100", "цену 100", "цена закупки 100", "цена на 100", "стоимость 100", "по 100 руб"
 96    [GeneratedRegex(@"(?:цен[аеуы]?\s*(?:закупки)?\s*(?:на\s+)?|стоимость\s+|по\s+)(\d[\d\s,\.]*)", RegexOptions.IgnoreC
 97    private static partial Regex PriceRegex();
 98
 99    [GeneratedRegex(@"количеств[оа]\s+(?:на\s+)?(\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
 100    private static partial Regex QuantityRegex();
 101
 102    public static string? TryExtractSupplierName(string message)
 103    {
 8104        var m = SupplierNameRegex().Match(message);
 8105        return m.Success ? m.Groups[1].Value.Trim() : null;
 106    }
 107
 108    public static string? TryExtractStatusName(string message)
 109    {
 7110        var m = StatusNameRegex().Match(message);
 7111        return m.Success ? m.Groups[1].Value.Trim() : null;
 112    }
 113
 114    public static string? TryExtractWarehouseName(string message)
 115    {
 8116        var m = WarehouseNameRegex().Match(message);
 8117        return m.Success ? m.Groups[1].Value.Trim() : null;
 118    }
 119
 120    public static string? TryExtractWarehouseSender(string message)
 121    {
 5122        var m = WarehouseSenderRegex().Match(message);
 8123        if (m.Success) return m.Groups[1].Value.Trim();
 124        // Fallback: первый склад из generic regex
 2125        return TryExtractWarehouseName(message);
 126    }
 127
 128    public static string? TryExtractWarehouseRecipient(string message)
 129    {
 4130        var m = WarehouseRecipientRegex().Match(message);
 4131        return m.Success ? m.Groups[1].Value.Trim() : null;
 132    }
 133
 134    public static decimal? TryExtractPrice(string message)
 135    {
 9136        var m = PriceRegex().Match(message);
 12137        if (!m.Success) return null;
 6138        var raw = m.Groups[1].Value.Replace(" ", "").Replace(",", ".");
 6139        return decimal.TryParse(raw, System.Globalization.NumberStyles.Number,
 6140            System.Globalization.CultureInfo.InvariantCulture, out var v) ? v : null;
 141    }
 142
 143    public static int? TryExtractQuantity(string message)
 144    {
 5145        var m = QuantityRegex().Match(message);
 5146        return m.Success && int.TryParse(m.Groups[1].Value, out var v) ? v : null;
 147    }
 148
 149    // ── Обязательные поля для транзакций ────────────────────────────────────────
 150
 1151    private static readonly HashSet<TransactionType> RequiresWarehouses = new()
 1152    {
 1153        TransactionType.CustomerReturn,
 1154        TransactionType.ReturnToSupplier,
 1155        TransactionType.ReturnToWarehouse,
 1156        TransactionType.WarehouseTransfer,
 1157        TransactionType.LostByCommissioner,
 1158    };
 159
 14160    public static bool NeedsWarehouses(TransactionType type) => RequiresWarehouses.Contains(type);
 161
 162    public static List<string> GetMissingTransactionFields(
 163        TransactionType type,
 164        string? warehouseSender,
 165        string? warehouseRecipient)
 166    {
 6167        var missing = new List<string>();
 8168        if (!NeedsWarehouses(type)) return missing;
 169
 4170        if (string.IsNullOrWhiteSpace(warehouseSender))
 2171            missing.Add("склад-отправитель");
 4172        if (string.IsNullOrWhiteSpace(warehouseRecipient) && type != TransactionType.WarehouseChange)
 2173            missing.Add("склад-получатель");
 4174        return missing;
 175    }
 176}

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/obj/Release/net8.0/System.Text.RegularExpressions.Generator/System.Text.RegularExpressions.Generator.RegexGenerator/RegexGenerator.g.cs

File '/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/obj/Release/net8.0/System.Text.RegularExpressions.Generator/System.Text.RegularExpressions.Generator.RegexGenerator/RegexGenerator.g.cs' does not exist (any more).