261 lines
9.2 KiB
C#
261 lines
9.2 KiB
C#
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<GroupOption> accountingBooks, IReadOnlyList<GroupOption> documentTypes)
|
||
{
|
||
AccountingBooks = accountingBooks ?? Array.Empty<GroupOption>();
|
||
DocumentTypes = documentTypes ?? Array.Empty<GroupOption>();
|
||
}
|
||
|
||
public IReadOnlyList<GroupOption> AccountingBooks { get; private set; }
|
||
|
||
public IReadOnlyList<GroupOption> 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<GroupOption> LoadAccountingBooks()
|
||
{
|
||
return LoadDirectory().AccountingBooks;
|
||
}
|
||
|
||
public void SaveAccountingBooks(IReadOnlyList<GroupOption> 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<GroupOption> NormalizeEntries(IEnumerable<DocumentNumberDirectoryEntry> entries, string sectionName)
|
||
{
|
||
var items = new List<GroupOption>();
|
||
var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
|
||
foreach (var entry in entries ?? Array.Empty<DocumentNumberDirectoryEntry>())
|
||
{
|
||
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<GroupOption> NormalizeWritableEntries(IEnumerable<GroupOption> entries, string sectionName)
|
||
{
|
||
var items = new List<GroupOption>();
|
||
var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
var seenTitles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
|
||
foreach (var entry in entries ?? Array.Empty<GroupOption>())
|
||
{
|
||
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<DocumentNumberDirectoryConfiguration>(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<DocumentNumberDirectoryEntry> ToConfigurationEntries(IEnumerable<GroupOption> entries)
|
||
{
|
||
var items = new List<DocumentNumberDirectoryEntry>();
|
||
|
||
foreach (var entry in entries ?? Array.Empty<GroupOption>())
|
||
{
|
||
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<DocumentNumberDirectoryEntry> AccountingBooks { get; set; }
|
||
|
||
public List<DocumentNumberDirectoryEntry> DocumentTypes { get; set; }
|
||
|
||
[JsonExtensionData]
|
||
public Dictionary<string, JsonElement> ExtensionData { get; set; }
|
||
}
|
||
|
||
internal sealed class DocumentNumberDirectoryEntry
|
||
{
|
||
public string Key { get; set; }
|
||
|
||
public string Title { get; set; }
|
||
}
|
||
}
|