using Npgsql; namespace TravelAgencyWinForms.Utils; public static class ErrorHelper { private static readonly Dictionary TableNames = new(StringComparer.OrdinalIgnoreCase) { ["clients"] = "клиентах", ["employees"] = "сотрудниках", ["positions"] = "должностях", ["cities"] = "городах", ["tours"] = "турах", ["tour_cities"] = "маршрутах туров", ["tour_list"] = "расписании туров", ["bookings"] = "бронированиях", ["app_users"] = "пользователях", ["audit_log"] = "журнале операций" }; public static string ToUserMessage(Exception exception) { if (exception is PostgresException postgres) return TranslatePostgres(postgres); var message = exception.Message; if (message.Contains("23503") || message.Contains("referenced from table", StringComparison.OrdinalIgnoreCase)) return "Запись нельзя удалить, потому что она используется в связанных данных."; if (message.Contains("duplicate key", StringComparison.OrdinalIgnoreCase)) return "Запись с такими уникальными данными уже существует."; return message; } private static string TranslatePostgres(PostgresException error) { var constraint = error.ConstraintName ?? string.Empty; var detail = error.Detail ?? string.Empty; if (error.SqlState == "23503") return ForeignKeyMessage(error, detail); if (error.SqlState == "23505") return DuplicateMessage(constraint); if (error.SqlState == "23502") return "Заполните обязательные поля."; if (error.SqlState == "23514") return CheckMessage(constraint); return string.IsNullOrWhiteSpace(error.MessageText) ? "Не удалось выполнить операцию. Проверьте данные и попробуйте ещё раз." : error.MessageText; } private static string ForeignKeyMessage(PostgresException error, string detail) { var relatedTable = ExtractRelatedTable(detail); if (!string.IsNullOrWhiteSpace(relatedTable) && TableNames.TryGetValue(relatedTable, out var tableLabel)) return $"Запись нельзя удалить, потому что она используется в {tableLabel}."; if (!string.IsNullOrWhiteSpace(error.TableName) && TableNames.TryGetValue(error.TableName, out var currentTableLabel)) return $"Операцию нельзя выполнить: запись связана с данными в разделе «{currentTableLabel}»."; return "Запись нельзя удалить, потому что она используется в связанных данных."; } private static string ExtractRelatedTable(string detail) { var marker = "referenced from table"; var index = detail.IndexOf(marker, StringComparison.OrdinalIgnoreCase); if (index < 0) return string.Empty; var tail = detail[(index + marker.Length)..].Trim(); if (tail.StartsWith('"')) { var end = tail.IndexOf('"', 1); if (end > 1) return tail[1..end]; } return tail.Split(' ', '.', ',', ';').FirstOrDefault() ?? string.Empty; } private static string DuplicateMessage(string constraint) { if (constraint.Contains("passport", StringComparison.OrdinalIgnoreCase)) return "Запись с такими паспортными данными уже существует."; if (constraint.Contains("booking_number", StringComparison.OrdinalIgnoreCase)) return "Бронирование с таким номером уже существует."; if (constraint.Contains("login", StringComparison.OrdinalIgnoreCase)) return "Пользователь с таким логином уже существует."; if (constraint.Contains("city", StringComparison.OrdinalIgnoreCase)) return "Такой город уже есть в справочнике."; if (constraint.Contains("position", StringComparison.OrdinalIgnoreCase)) return "Такая должность уже есть в справочнике."; return "Запись с такими уникальными данными уже существует."; } private static string CheckMessage(string constraint) { if (constraint.Contains("phone", StringComparison.OrdinalIgnoreCase)) return "Телефон должен содержать 10–12 цифр и может начинаться с +."; if (constraint.Contains("email", StringComparison.OrdinalIgnoreCase)) return "Введите корректный адрес электронной почты."; if (constraint.Contains("passport", StringComparison.OrdinalIgnoreCase)) return "Паспортные данные должны быть в формате: 0000 000000."; if (constraint.Contains("salary", StringComparison.OrdinalIgnoreCase)) return "Зарплата не может быть отрицательной."; if (constraint.Contains("price", StringComparison.OrdinalIgnoreCase)) return "Стоимость тура не может быть отрицательной."; if (constraint.Contains("birth", StringComparison.OrdinalIgnoreCase)) return "Дата рождения указана некорректно."; if (constraint.Contains("tour_list_dates", StringComparison.OrdinalIgnoreCase)) return "Дата окончания тура не может быть раньше даты начала."; if (constraint.Contains("status", StringComparison.OrdinalIgnoreCase)) return "Выбран недопустимый статус бронирования."; return "Проверьте корректность заполненных данных."; } }