using System; using System.Collections.Generic; using System.IO; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; namespace XLAB2 { internal sealed class DocumentNumberDirectory { public DocumentNumberDirectory(IReadOnlyList accountingBooks, IReadOnlyList documentTypes) { AccountingBooks = accountingBooks ?? Array.Empty(); DocumentTypes = documentTypes ?? Array.Empty(); } public IReadOnlyList AccountingBooks { get; private set; } public IReadOnlyList DocumentTypes { get; private set; } } internal sealed class DocumentNumberDirectoryService { private readonly string _filePath; public DocumentNumberDirectoryService(string filePath) { _filePath = filePath; } public DocumentNumberDirectory LoadDirectory() { var configuration = LoadConfiguration(); return new DocumentNumberDirectory( NormalizeEntries(configuration.AccountingBooks, "книг учета"), NormalizeEntries(configuration.DocumentTypes, "типов документов")); } public IReadOnlyList LoadAccountingBooks() { return LoadDirectory().AccountingBooks; } public void SaveAccountingBooks(IReadOnlyList accountingBooks) { var configuration = LoadConfiguration(); var normalizedAccountingBooks = NormalizeWritableEntries(accountingBooks, "книг учета"); var normalizedDocumentTypes = NormalizeEntries(configuration.DocumentTypes, "типов документов"); configuration.AccountingBooks = ToConfigurationEntries(normalizedAccountingBooks); configuration.DocumentTypes = ToConfigurationEntries(normalizedDocumentTypes); SaveConfiguration(configuration); } private static IReadOnlyList NormalizeEntries(IEnumerable entries, string sectionName) { var items = new List(); var seenKeys = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var entry in entries ?? Array.Empty()) { if (entry == null) { continue; } var key = DocumentNumberFormatter.NormalizePart(entry.Key); var title = DocumentNumberFormatter.NormalizePart(entry.Title); if (key == null || title == null || !seenKeys.Add(key)) { continue; } items.Add(new GroupOption { Key = key, Title = title }); } if (items.Count == 0) { throw new InvalidOperationException(string.Format("Справочник номеров документов не содержит {0}.", sectionName)); } return items; } private static IReadOnlyList NormalizeWritableEntries(IEnumerable entries, string sectionName) { var items = new List(); var seenKeys = new HashSet(StringComparer.OrdinalIgnoreCase); var seenTitles = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var entry in entries ?? Array.Empty()) { if (entry == null) { continue; } var key = DocumentNumberFormatter.NormalizePart(entry.Key); if (key == null) { throw new InvalidOperationException("Не задан ключ книги учета."); } var title = DocumentNumberFormatter.NormalizePart(entry.Title); if (title == null) { throw new InvalidOperationException("Не задан номер книги учета."); } if (!seenKeys.Add(key)) { throw new InvalidOperationException(string.Format("Ключ книги учета \"{0}\" повторяется.", key)); } if (!seenTitles.Add(title)) { throw new InvalidOperationException(string.Format("Номер книги учета \"{0}\" повторяется.", title)); } items.Add(new GroupOption { Key = key, Title = title }); } if (items.Count == 0) { throw new InvalidOperationException(string.Format("Справочник номеров документов не содержит {0}.", sectionName)); } return items; } private DocumentNumberDirectoryConfiguration LoadConfiguration() { if (string.IsNullOrWhiteSpace(_filePath)) { throw new InvalidOperationException("Не задан путь к справочнику номеров документов."); } if (!File.Exists(_filePath)) { throw new InvalidOperationException(string.Format("Не найден справочник номеров документов: {0}.", _filePath)); } using (var stream = File.OpenRead(_filePath)) { var configuration = JsonSerializer.Deserialize(stream, CreateReadSerializerOptions()); if (configuration == null) { throw new InvalidOperationException("Справочник номеров документов пуст или поврежден."); } return configuration; } } private void SaveConfiguration(DocumentNumberDirectoryConfiguration configuration) { var directoryPath = Path.GetDirectoryName(_filePath); if (!string.IsNullOrWhiteSpace(directoryPath)) { Directory.CreateDirectory(directoryPath); } var json = JsonSerializer.Serialize(configuration, CreateWriteSerializerOptions()); File.WriteAllText(_filePath, json); } private static List ToConfigurationEntries(IEnumerable entries) { var items = new List(); foreach (var entry in entries ?? Array.Empty()) { if (entry == null) { continue; } items.Add(new DocumentNumberDirectoryEntry { Key = entry.Key, Title = entry.Title }); } return items; } private static JsonSerializerOptions CreateReadSerializerOptions() { return new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; } private static JsonSerializerOptions CreateWriteSerializerOptions() { return new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }; } } internal static class DocumentNumberFormatter { public const int MaxLength = 60; public static string Build(string documentTypeTitle, string accountingBookTitle, string documentSequenceNumber) { var normalizedDocumentTypeTitle = NormalizePart(documentTypeTitle); var normalizedAccountingBookTitle = NormalizePart(accountingBookTitle); var normalizedDocumentSequenceNumber = NormalizePart(documentSequenceNumber); if (normalizedDocumentTypeTitle == null || normalizedAccountingBookTitle == null || normalizedDocumentSequenceNumber == null) { return string.Empty; } return string.Format("{0} № {1}-{2}", normalizedDocumentTypeTitle, normalizedAccountingBookTitle, normalizedDocumentSequenceNumber); } public static string NormalizePart(string value) { return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } } internal sealed class DocumentNumberDirectoryConfiguration { public List AccountingBooks { get; set; } public List DocumentTypes { get; set; } [JsonExtensionData] public Dictionary ExtensionData { get; set; } } internal sealed class DocumentNumberDirectoryEntry { public string Key { get; set; } public string Title { get; set; } } }