edit
This commit is contained in:
118
XLAB2/AccountingBookDirectoryWindow.xaml
Normal file
118
XLAB2/AccountingBookDirectoryWindow.xaml
Normal file
@@ -0,0 +1,118 @@
|
||||
<Window x:Class="XLAB2.AccountingBookDirectoryWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Книги учета"
|
||||
Height="680"
|
||||
Width="980"
|
||||
MinHeight="520"
|
||||
MinWidth="820"
|
||||
Loaded="Window_Loaded"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel Grid.Row="0"
|
||||
Margin="0,0,0,8">
|
||||
<StackPanel DockPanel.Dock="Left"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock Width="210"
|
||||
VerticalAlignment="Center"
|
||||
Text="Поиск по ключу или книге учета" />
|
||||
<TextBox Width="320"
|
||||
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel DockPanel.Dock="Right"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Width="110"
|
||||
Margin="8,0,0,0"
|
||||
Command="{Binding RefreshCommand}"
|
||||
Content="Обновить" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
|
||||
<DataGrid Grid.Row="1"
|
||||
ItemsSource="{Binding ItemsView}"
|
||||
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="Column">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<MenuItem Header="Добавить"
|
||||
Command="{Binding AddCommand}">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="14" Height="14">
|
||||
<Grid Width="16" Height="16">
|
||||
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
|
||||
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
|
||||
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Изменить"
|
||||
Command="{Binding EditCommand}">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="14" Height="14">
|
||||
<Canvas Width="16" Height="16">
|
||||
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
|
||||
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Удалить"
|
||||
Command="{Binding DeleteCommand}">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="14" Height="14">
|
||||
<Canvas Width="16" Height="16">
|
||||
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
|
||||
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
|
||||
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
|
||||
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
|
||||
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<EventSetter Event="PreviewMouseRightButtonDown"
|
||||
Handler="DataGridRow_PreviewMouseRightButtonDown" />
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Ключ"
|
||||
Width="240"
|
||||
Binding="{Binding Key}" />
|
||||
<DataGridTextColumn Header="Номер книги учета"
|
||||
Width="*"
|
||||
Binding="{Binding Title}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<TextBlock Grid.Row="2"
|
||||
Margin="0,8,0,0"
|
||||
Foreground="DimGray"
|
||||
Text="{Binding StatusText}" />
|
||||
|
||||
<StackPanel Grid.Row="3"
|
||||
Margin="0,12,0,0"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Width="90"
|
||||
IsCancel="True"
|
||||
Content="Закрыть" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
35
XLAB2/AccountingBookDirectoryWindow.xaml.cs
Normal file
35
XLAB2/AccountingBookDirectoryWindow.xaml.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace XLAB2
|
||||
{
|
||||
public partial class AccountingBookDirectoryWindow : Window
|
||||
{
|
||||
private readonly AccountingBookDirectoryWindowViewModel _viewModel;
|
||||
|
||||
internal AccountingBookDirectoryWindow(DocumentNumberDirectoryService documentNumberDirectoryService)
|
||||
{
|
||||
InitializeComponent();
|
||||
_viewModel = new AccountingBookDirectoryWindowViewModel(
|
||||
documentNumberDirectoryService,
|
||||
new DialogService(this, documentNumberDirectoryService));
|
||||
DataContext = _viewModel;
|
||||
}
|
||||
|
||||
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var row = sender as DataGridRow;
|
||||
if (row != null)
|
||||
{
|
||||
row.IsSelected = true;
|
||||
row.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_viewModel.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
266
XLAB2/AccountingBookDirectoryWindowViewModel.cs
Normal file
266
XLAB2/AccountingBookDirectoryWindowViewModel.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace XLAB2
|
||||
{
|
||||
internal sealed class AccountingBookDirectoryWindowViewModel : ObservableObject
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly DocumentNumberDirectoryService _service;
|
||||
private string _searchText;
|
||||
private GroupOption _selectedItem;
|
||||
private string _statusText;
|
||||
|
||||
public AccountingBookDirectoryWindowViewModel(DocumentNumberDirectoryService service, IDialogService dialogService)
|
||||
{
|
||||
_service = service ?? throw new ArgumentNullException("service");
|
||||
_dialogService = dialogService ?? throw new ArgumentNullException("dialogService");
|
||||
|
||||
Items = new ObservableCollection<GroupOption>();
|
||||
ItemsView = CollectionViewSource.GetDefaultView(Items);
|
||||
ItemsView.Filter = FilterItems;
|
||||
|
||||
AddCommand = new RelayCommand(delegate { AddItem(); });
|
||||
DeleteCommand = new RelayCommand(delegate { DeleteSelectedItem(); }, delegate { return SelectedItem != null; });
|
||||
EditCommand = new RelayCommand(delegate { EditSelectedItem(); }, delegate { return SelectedItem != null; });
|
||||
RefreshCommand = new RelayCommand(delegate { RefreshItems(null); });
|
||||
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
public ICommand AddCommand { get; private set; }
|
||||
|
||||
public ICommand DeleteCommand { get; private set; }
|
||||
|
||||
public ICommand EditCommand { get; private set; }
|
||||
|
||||
public ObservableCollection<GroupOption> Items { get; private set; }
|
||||
|
||||
public ICollectionView ItemsView { get; private set; }
|
||||
|
||||
public ICommand RefreshCommand { get; private set; }
|
||||
|
||||
public string SearchText
|
||||
{
|
||||
get { return _searchText; }
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _searchText, value))
|
||||
{
|
||||
ItemsView.Refresh();
|
||||
UpdateStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GroupOption SelectedItem
|
||||
{
|
||||
get { return _selectedItem; }
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedItem, value))
|
||||
{
|
||||
RaiseCommandStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string StatusText
|
||||
{
|
||||
get { return _statusText; }
|
||||
private set { SetProperty(ref _statusText, value); }
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
RunOperation(delegate { RefreshItems(null); }, false);
|
||||
}
|
||||
|
||||
private void AddItem()
|
||||
{
|
||||
var result = _dialogService.ShowAccountingBookEditDialog(new GroupOption(), true, Items.ToList());
|
||||
if (result == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RunOperation(delegate
|
||||
{
|
||||
var items = CloneItems(Items);
|
||||
items.Add(CloneItem(result));
|
||||
_service.SaveAccountingBooks(items);
|
||||
RefreshItems(result.Key);
|
||||
_dialogService.ShowInfo("Запись справочника добавлена.");
|
||||
}, true);
|
||||
}
|
||||
|
||||
private bool Contains(string source, string searchText)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(source)
|
||||
&& source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
private void DeleteSelectedItem()
|
||||
{
|
||||
if (SelectedItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedItem = SelectedItem;
|
||||
if (!_dialogService.Confirm(string.Format("Удалить книгу учета \"{0}\"?", selectedItem.Title)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RunOperation(delegate
|
||||
{
|
||||
var items = CloneItems(Items);
|
||||
items.RemoveAll(delegate(GroupOption item)
|
||||
{
|
||||
return string.Equals(item.Key, selectedItem.Key, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
_service.SaveAccountingBooks(items);
|
||||
RefreshItems(null);
|
||||
_dialogService.ShowInfo("Запись справочника удалена.");
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void EditSelectedItem()
|
||||
{
|
||||
if (SelectedItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var seed = CloneItem(SelectedItem);
|
||||
var result = _dialogService.ShowAccountingBookEditDialog(seed, false, Items.ToList());
|
||||
if (result == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RunOperation(delegate
|
||||
{
|
||||
var items = CloneItems(Items);
|
||||
var index = items.FindIndex(delegate(GroupOption item)
|
||||
{
|
||||
return string.Equals(item.Key, seed.Key, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Не удалось найти книгу учета для обновления.");
|
||||
}
|
||||
|
||||
items[index] = CloneItem(result);
|
||||
_service.SaveAccountingBooks(items);
|
||||
RefreshItems(result.Key);
|
||||
_dialogService.ShowInfo("Запись справочника обновлена.");
|
||||
}, true);
|
||||
}
|
||||
|
||||
private bool FilterItems(object item)
|
||||
{
|
||||
var directoryItem = item as GroupOption;
|
||||
if (directoryItem == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(SearchText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Contains(directoryItem.Key, SearchText)
|
||||
|| Contains(directoryItem.Title, SearchText);
|
||||
}
|
||||
|
||||
private void RaiseCommandStates()
|
||||
{
|
||||
((RelayCommand)AddCommand).RaiseCanExecuteChanged();
|
||||
((RelayCommand)DeleteCommand).RaiseCanExecuteChanged();
|
||||
((RelayCommand)EditCommand).RaiseCanExecuteChanged();
|
||||
((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
private void RefreshItems(string keyToSelect)
|
||||
{
|
||||
var items = _service.LoadAccountingBooks();
|
||||
var currentKey = string.IsNullOrWhiteSpace(keyToSelect)
|
||||
? (SelectedItem == null ? null : SelectedItem.Key)
|
||||
: keyToSelect;
|
||||
|
||||
Items.Clear();
|
||||
foreach (var item in items)
|
||||
{
|
||||
Items.Add(CloneItem(item));
|
||||
}
|
||||
|
||||
ItemsView.Refresh();
|
||||
SelectedItem = string.IsNullOrWhiteSpace(currentKey)
|
||||
? Items.FirstOrDefault()
|
||||
: Items.FirstOrDefault(delegate(GroupOption item)
|
||||
{
|
||||
return string.Equals(item.Key, currentKey, StringComparison.OrdinalIgnoreCase);
|
||||
}) ?? Items.FirstOrDefault();
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
private void RunOperation(Action action, bool showWarningForInvalidOperation)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
if (showWarningForInvalidOperation)
|
||||
{
|
||||
_dialogService.ShowWarning(ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
_dialogService.ShowError(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dialogService.ShowError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static GroupOption CloneItem(GroupOption item)
|
||||
{
|
||||
return new GroupOption
|
||||
{
|
||||
Key = item == null ? string.Empty : item.Key,
|
||||
Title = item == null ? string.Empty : item.Title
|
||||
};
|
||||
}
|
||||
|
||||
private static List<GroupOption> CloneItems(IEnumerable<GroupOption> items)
|
||||
{
|
||||
var result = new List<GroupOption>();
|
||||
|
||||
foreach (var item in items ?? Array.Empty<GroupOption>())
|
||||
{
|
||||
result.Add(CloneItem(item));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void UpdateStatus()
|
||||
{
|
||||
var visibleCount = ItemsView.Cast<object>().Count();
|
||||
StatusText = string.Format("Всего записей: {0}. По фильтру: {1}.", Items.Count, visibleCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
XLAB2/AccountingBookEditWindow.xaml
Normal file
64
XLAB2/AccountingBookEditWindow.xaml
Normal file
@@ -0,0 +1,64 @@
|
||||
<Window x:Class="XLAB2.AccountingBookEditWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="{Binding Title}"
|
||||
Height="250"
|
||||
Width="560"
|
||||
MinHeight="240"
|
||||
MinWidth="520"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,12,8"
|
||||
VerticalAlignment="Center"
|
||||
Text="Номер книги учета" />
|
||||
<TextBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,8"
|
||||
Text="{Binding BookNumber, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,12,8"
|
||||
VerticalAlignment="Center"
|
||||
Text="Ключ" />
|
||||
<TextBox Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,8"
|
||||
Text="{Binding Key, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<TextBlock Grid.Row="2"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,8,0,0"
|
||||
Foreground="Firebrick"
|
||||
Text="{Binding ValidationMessage}" />
|
||||
|
||||
<StackPanel Grid.Row="3"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,12,0,0"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Width="100"
|
||||
Margin="0,0,8,0"
|
||||
IsDefault="True"
|
||||
Command="{Binding ConfirmCommand}"
|
||||
Content="Сохранить" />
|
||||
<Button Width="90"
|
||||
Command="{Binding CancelCommand}"
|
||||
Content="Отмена" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
20
XLAB2/AccountingBookEditWindow.xaml.cs
Normal file
20
XLAB2/AccountingBookEditWindow.xaml.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace XLAB2
|
||||
{
|
||||
public partial class AccountingBookEditWindow : Window
|
||||
{
|
||||
internal AccountingBookEditWindow(AccountingBookEditWindowViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
viewModel.CloseRequested += ViewModelOnCloseRequested;
|
||||
}
|
||||
|
||||
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
|
||||
{
|
||||
DialogResult = dialogResult;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
138
XLAB2/AccountingBookEditWindowViewModel.cs
Normal file
138
XLAB2/AccountingBookEditWindowViewModel.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace XLAB2
|
||||
{
|
||||
internal sealed class AccountingBookEditWindowViewModel : ObservableObject
|
||||
{
|
||||
private readonly IReadOnlyList<GroupOption> _existingItems;
|
||||
private readonly string _originalKey;
|
||||
private string _bookNumber;
|
||||
private string _key;
|
||||
private string _validationMessage;
|
||||
|
||||
public AccountingBookEditWindowViewModel(GroupOption seed, bool isNew, IReadOnlyList<GroupOption> existingItems)
|
||||
{
|
||||
var source = seed ?? new GroupOption();
|
||||
_existingItems = existingItems ?? Array.Empty<GroupOption>();
|
||||
_originalKey = Normalize(source.Key);
|
||||
IsNew = isNew;
|
||||
BookNumber = source.Title ?? string.Empty;
|
||||
Key = source.Key ?? string.Empty;
|
||||
|
||||
ConfirmCommand = new RelayCommand(Confirm);
|
||||
CancelCommand = new RelayCommand(Cancel);
|
||||
}
|
||||
|
||||
public event EventHandler<bool?> CloseRequested;
|
||||
|
||||
public string BookNumber
|
||||
{
|
||||
get { return _bookNumber; }
|
||||
set { SetProperty(ref _bookNumber, value); }
|
||||
}
|
||||
|
||||
public ICommand CancelCommand { get; private set; }
|
||||
|
||||
public ICommand ConfirmCommand { get; private set; }
|
||||
|
||||
public bool IsNew { get; private set; }
|
||||
|
||||
public string Key
|
||||
{
|
||||
get { return _key; }
|
||||
set { SetProperty(ref _key, value); }
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get { return IsNew ? "Новая книга учета" : "Редактирование книги учета"; }
|
||||
}
|
||||
|
||||
public string ValidationMessage
|
||||
{
|
||||
get { return _validationMessage; }
|
||||
private set { SetProperty(ref _validationMessage, value); }
|
||||
}
|
||||
|
||||
public GroupOption ToResult()
|
||||
{
|
||||
return new GroupOption
|
||||
{
|
||||
Key = Normalize(Key) ?? string.Empty,
|
||||
Title = Normalize(BookNumber) ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private void Cancel(object parameter)
|
||||
{
|
||||
RaiseCloseRequested(false);
|
||||
}
|
||||
|
||||
private void Confirm(object parameter)
|
||||
{
|
||||
var normalizedKey = Normalize(Key);
|
||||
if (normalizedKey == null)
|
||||
{
|
||||
ValidationMessage = "Укажите ключ книги учета.";
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedBookNumber = Normalize(BookNumber);
|
||||
if (normalizedBookNumber == null)
|
||||
{
|
||||
ValidationMessage = "Укажите номер книги учета.";
|
||||
return;
|
||||
}
|
||||
|
||||
var duplicateKey = _existingItems.FirstOrDefault(delegate(GroupOption item)
|
||||
{
|
||||
return item != null
|
||||
&& !IsCurrentItem(item)
|
||||
&& string.Equals(Normalize(item.Key), normalizedKey, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
if (duplicateKey != null)
|
||||
{
|
||||
ValidationMessage = string.Format("Ключ книги учета \"{0}\" уже существует.", normalizedKey);
|
||||
return;
|
||||
}
|
||||
|
||||
var duplicateBookNumber = _existingItems.FirstOrDefault(delegate(GroupOption item)
|
||||
{
|
||||
return item != null
|
||||
&& !IsCurrentItem(item)
|
||||
&& string.Equals(Normalize(item.Title), normalizedBookNumber, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
if (duplicateBookNumber != null)
|
||||
{
|
||||
ValidationMessage = string.Format("Книга учета \"{0}\" уже существует.", normalizedBookNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
ValidationMessage = string.Empty;
|
||||
RaiseCloseRequested(true);
|
||||
}
|
||||
|
||||
private bool IsCurrentItem(GroupOption item)
|
||||
{
|
||||
return !IsNew
|
||||
&& string.Equals(Normalize(item == null ? null : item.Key), _originalKey, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string Normalize(string value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
}
|
||||
|
||||
private void RaiseCloseRequested(bool? dialogResult)
|
||||
{
|
||||
var handler = CloseRequested;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, dialogResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,11 @@ namespace XLAB2
|
||||
.ConfigureServices((_, services) =>
|
||||
{
|
||||
services.AddSingleton<IDatabaseConnectionFactory>(_ => SqlServerConnectionFactory.Current);
|
||||
services.AddSingleton(new DocumentNumberDirectoryService(System.IO.Path.Combine(AppContext.BaseDirectory, "Assets", "document-number-directory.json")));
|
||||
services.AddTransient<PsvDataService>();
|
||||
services.AddTransient<MainWindow>(provider => new MainWindow(provider.GetRequiredService<PsvDataService>()));
|
||||
services.AddTransient<MainWindow>(provider => new MainWindow(
|
||||
provider.GetRequiredService<PsvDataService>(),
|
||||
provider.GetRequiredService<DocumentNumberDirectoryService>()));
|
||||
})
|
||||
.UseDefaultServiceProvider((_, options) =>
|
||||
{
|
||||
|
||||
18
XLAB2/Assets/document-number-directory.json
Normal file
18
XLAB2/Assets/document-number-directory.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"documentTypes": [
|
||||
{
|
||||
"key": "PSV",
|
||||
"title": "ПСВ"
|
||||
},
|
||||
{
|
||||
"key": "ACT_REFERENCE",
|
||||
"title": "Акт-справка"
|
||||
}
|
||||
],
|
||||
"accountingBooks": [
|
||||
{
|
||||
"key": "219/С5/15",
|
||||
"title": "219/С5/15"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<Window x:Class="XLAB2.CreateDocumentWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Новый ПСВ"
|
||||
Height="240"
|
||||
Width="430"
|
||||
Title="Новый документ"
|
||||
Height="360"
|
||||
Width="520"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid Margin="16">
|
||||
@@ -13,9 +13,12 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150" />
|
||||
<ColumnDefinition Width="170" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
@@ -23,36 +26,72 @@
|
||||
Grid.Column="0"
|
||||
Margin="0,0,12,8"
|
||||
VerticalAlignment="Center"
|
||||
Text="Номер ПСВ" />
|
||||
<TextBox Grid.Row="0"
|
||||
Text="Тип документа" />
|
||||
<ComboBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
MinWidth="180"
|
||||
Margin="0,0,0,8"
|
||||
Text="{Binding DocumentNumber, UpdateSourceTrigger=PropertyChanged}" />
|
||||
DisplayMemberPath="Title"
|
||||
ItemsSource="{Binding DocumentTypes}"
|
||||
SelectedItem="{Binding SelectedDocumentType, Mode=TwoWay}" />
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,12,8"
|
||||
VerticalAlignment="Center"
|
||||
Text="Дата приемки" />
|
||||
<DatePicker Grid.Row="1"
|
||||
Text="Книга учета" />
|
||||
<ComboBox Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
SelectedDate="{Binding AcceptedOn, Mode=TwoWay}"
|
||||
SelectedDateFormat="Short"
|
||||
Margin="0,0,0,8" />
|
||||
Margin="0,0,0,8"
|
||||
DisplayMemberPath="Title"
|
||||
ItemsSource="{Binding AccountingBooks}"
|
||||
SelectedItem="{Binding SelectedAccountingBook, Mode=TwoWay}" />
|
||||
|
||||
<TextBlock Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,12,8"
|
||||
VerticalAlignment="Center"
|
||||
Text="Номер документа" />
|
||||
<TextBox Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="180"
|
||||
Margin="0,0,0,8"
|
||||
Text="{Binding DocumentSequenceNumber, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<TextBlock Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,12,8"
|
||||
VerticalAlignment="Center"
|
||||
Text="Итоговый номер" />
|
||||
<TextBox Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,8"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding DocumentNumber, Mode=OneWay}" />
|
||||
|
||||
<TextBlock Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,12,8"
|
||||
VerticalAlignment="Center"
|
||||
Text="Дата приемки" />
|
||||
<DatePicker Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,8"
|
||||
SelectedDate="{Binding AcceptedOn, Mode=TwoWay}"
|
||||
SelectedDateFormat="Short" />
|
||||
|
||||
<TextBlock Grid.Row="5"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,8,0,4"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="DimGray"
|
||||
Text="ПСВ без строк EKZMK не хранится в базе. Команда «Добавить» создаёт только черновик текущего сеанса." />
|
||||
<TextBlock Grid.Row="3"
|
||||
TextWrapping="Wrap"
|
||||
Text="Документ без строк EKZMK не хранится в базе. Команда «Создать» создаёт только черновик текущего сеанса." />
|
||||
|
||||
<TextBlock Grid.Row="6"
|
||||
Grid.ColumnSpan="2"
|
||||
Foreground="Firebrick"
|
||||
Text="{Binding ValidationMessage}" />
|
||||
|
||||
<StackPanel Grid.Row="4"
|
||||
<StackPanel Grid.Row="7"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace XLAB2
|
||||
@@ -6,13 +8,25 @@ namespace XLAB2
|
||||
internal sealed class CreateDocumentWindowViewModel : ObservableObject
|
||||
{
|
||||
private DateTime? _acceptedOn;
|
||||
private string _documentNumber;
|
||||
private string _documentSequenceNumber;
|
||||
private GroupOption _selectedAccountingBook;
|
||||
private GroupOption _selectedDocumentType;
|
||||
private string _validationMessage;
|
||||
|
||||
public CreateDocumentWindowViewModel(DocumentEditorResult seed)
|
||||
public CreateDocumentWindowViewModel(DocumentEditorResult seed, DocumentNumberDirectory directory)
|
||||
{
|
||||
if (directory == null)
|
||||
{
|
||||
throw new ArgumentNullException("directory");
|
||||
}
|
||||
|
||||
AccountingBooks = new ObservableCollection<GroupOption>(directory.AccountingBooks ?? Array.Empty<GroupOption>());
|
||||
DocumentTypes = new ObservableCollection<GroupOption>(directory.DocumentTypes ?? Array.Empty<GroupOption>());
|
||||
|
||||
_acceptedOn = seed != null ? seed.AcceptedOn : DateTime.Today;
|
||||
_documentNumber = seed != null ? seed.DocumentNumber : string.Empty;
|
||||
_documentSequenceNumber = seed == null ? string.Empty : seed.DocumentSequenceNumber ?? string.Empty;
|
||||
_selectedAccountingBook = ResolveSelectedOption(AccountingBooks, seed == null ? null : seed.AccountingBookKey);
|
||||
_selectedDocumentType = ResolveSelectedOption(DocumentTypes, seed == null ? null : seed.DocumentTypeKey);
|
||||
|
||||
ConfirmCommand = new RelayCommand(Confirm);
|
||||
CancelCommand = new RelayCommand(Cancel);
|
||||
@@ -23,8 +37,16 @@ namespace XLAB2
|
||||
public DateTime? AcceptedOn
|
||||
{
|
||||
get { return _acceptedOn; }
|
||||
set { SetProperty(ref _acceptedOn, value); }
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _acceptedOn, value))
|
||||
{
|
||||
ClearValidationMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<GroupOption> AccountingBooks { get; private set; }
|
||||
|
||||
public ICommand CancelCommand { get; private set; }
|
||||
|
||||
@@ -32,8 +54,54 @@ namespace XLAB2
|
||||
|
||||
public string DocumentNumber
|
||||
{
|
||||
get { return _documentNumber; }
|
||||
set { SetProperty(ref _documentNumber, value); }
|
||||
get
|
||||
{
|
||||
return DocumentNumberFormatter.Build(
|
||||
SelectedDocumentType == null ? null : SelectedDocumentType.Title,
|
||||
SelectedAccountingBook == null ? null : SelectedAccountingBook.Title,
|
||||
DocumentSequenceNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public string DocumentSequenceNumber
|
||||
{
|
||||
get { return _documentSequenceNumber; }
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _documentSequenceNumber, value))
|
||||
{
|
||||
ClearValidationMessage();
|
||||
OnPropertyChanged("DocumentNumber");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<GroupOption> DocumentTypes { get; private set; }
|
||||
|
||||
public GroupOption SelectedAccountingBook
|
||||
{
|
||||
get { return _selectedAccountingBook; }
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedAccountingBook, value))
|
||||
{
|
||||
ClearValidationMessage();
|
||||
OnPropertyChanged("DocumentNumber");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GroupOption SelectedDocumentType
|
||||
{
|
||||
get { return _selectedDocumentType; }
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedDocumentType, value))
|
||||
{
|
||||
ClearValidationMessage();
|
||||
OnPropertyChanged("DocumentNumber");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ValidationMessage
|
||||
@@ -46,7 +114,12 @@ namespace XLAB2
|
||||
{
|
||||
return new DocumentEditorResult
|
||||
{
|
||||
DocumentNumber = DocumentNumber == null ? string.Empty : DocumentNumber.Trim(),
|
||||
AccountingBookKey = SelectedAccountingBook == null ? string.Empty : SelectedAccountingBook.Key,
|
||||
AccountingBookTitle = SelectedAccountingBook == null ? string.Empty : SelectedAccountingBook.Title,
|
||||
DocumentNumber = DocumentNumber,
|
||||
DocumentSequenceNumber = DocumentSequenceNumber == null ? string.Empty : DocumentSequenceNumber.Trim(),
|
||||
DocumentTypeKey = SelectedDocumentType == null ? string.Empty : SelectedDocumentType.Key,
|
||||
DocumentTypeTitle = SelectedDocumentType == null ? string.Empty : SelectedDocumentType.Title,
|
||||
AcceptedOn = AcceptedOn.HasValue ? AcceptedOn.Value : DateTime.Today,
|
||||
IssuedOn = null
|
||||
};
|
||||
@@ -57,11 +130,31 @@ namespace XLAB2
|
||||
RaiseCloseRequested(false);
|
||||
}
|
||||
|
||||
private void ClearValidationMessage()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ValidationMessage))
|
||||
{
|
||||
ValidationMessage = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private void Confirm(object parameter)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(DocumentNumber))
|
||||
if (SelectedDocumentType == null)
|
||||
{
|
||||
ValidationMessage = "Введите номер ПСВ.";
|
||||
ValidationMessage = "Выберите тип документа.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedAccountingBook == null)
|
||||
{
|
||||
ValidationMessage = "Выберите книгу учета.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(DocumentSequenceNumber))
|
||||
{
|
||||
ValidationMessage = "Введите номер документа.";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -71,10 +164,42 @@ namespace XLAB2
|
||||
return;
|
||||
}
|
||||
|
||||
if (DocumentNumber.Length > DocumentNumberFormatter.MaxLength)
|
||||
{
|
||||
ValidationMessage = string.Format(
|
||||
"Итоговый номер документа не должен превышать {0} символов.",
|
||||
DocumentNumberFormatter.MaxLength);
|
||||
return;
|
||||
}
|
||||
|
||||
ValidationMessage = string.Empty;
|
||||
RaiseCloseRequested(true);
|
||||
}
|
||||
|
||||
private static GroupOption ResolveSelectedOption(ObservableCollection<GroupOption> items, string preferredKey)
|
||||
{
|
||||
if (items == null || items.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(preferredKey))
|
||||
{
|
||||
var matchingItem = items.FirstOrDefault(delegate(GroupOption item)
|
||||
{
|
||||
return item != null
|
||||
&& string.Equals(item.Key, preferredKey.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
if (matchingItem != null)
|
||||
{
|
||||
return matchingItem;
|
||||
}
|
||||
}
|
||||
|
||||
return items[0];
|
||||
}
|
||||
|
||||
private void RaiseCloseRequested(bool? dialogResult)
|
||||
{
|
||||
var handler = CloseRequested;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
@@ -13,6 +14,8 @@ namespace XLAB2
|
||||
|
||||
IReadOnlyList<string> ShowCloneVerificationDialog(CloneVerificationSeed seed);
|
||||
|
||||
GroupOption ShowAccountingBookEditDialog(GroupOption seed, bool isNew, IReadOnlyList<GroupOption> existingItems);
|
||||
|
||||
SpoiDirectoryItem ShowSpoiEditDialog(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems);
|
||||
|
||||
SpnmtpDirectoryItem ShowSpnmtpEditDialog(SpnmtpDirectoryItem seed, bool isNew, IReadOnlyList<SpnmtpDirectoryItem> existingItems);
|
||||
@@ -33,6 +36,8 @@ namespace XLAB2
|
||||
|
||||
internal sealed class DialogService : IDialogService
|
||||
{
|
||||
private readonly DocumentNumberDirectoryService _documentNumberDirectoryService;
|
||||
|
||||
public DialogService()
|
||||
{
|
||||
}
|
||||
@@ -42,11 +47,18 @@ namespace XLAB2
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public DialogService(Window owner, DocumentNumberDirectoryService documentNumberDirectoryService)
|
||||
{
|
||||
Owner = owner;
|
||||
_documentNumberDirectoryService = documentNumberDirectoryService;
|
||||
}
|
||||
|
||||
public Window Owner { get; set; }
|
||||
|
||||
public DocumentEditorResult ShowCreateDocumentDialog(DocumentEditorResult seed)
|
||||
{
|
||||
var viewModel = new CreateDocumentWindowViewModel(seed);
|
||||
var directory = (_documentNumberDirectoryService ?? CreateDefaultDocumentNumberDirectoryService()).LoadDirectory();
|
||||
var viewModel = new CreateDocumentWindowViewModel(seed, directory);
|
||||
var window = new CreateDocumentWindow(viewModel);
|
||||
AttachOwner(window);
|
||||
|
||||
@@ -84,6 +96,16 @@ namespace XLAB2
|
||||
return result.HasValue && result.Value ? viewModel.GetSerialNumbers() : null;
|
||||
}
|
||||
|
||||
public GroupOption ShowAccountingBookEditDialog(GroupOption seed, bool isNew, IReadOnlyList<GroupOption> existingItems)
|
||||
{
|
||||
var viewModel = new AccountingBookEditWindowViewModel(seed, isNew, existingItems);
|
||||
var window = new AccountingBookEditWindow(viewModel);
|
||||
AttachOwner(window);
|
||||
|
||||
var result = window.ShowDialog();
|
||||
return result.HasValue && result.Value ? viewModel.ToResult() : null;
|
||||
}
|
||||
|
||||
public SpoiDirectoryItem ShowSpoiEditDialog(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems)
|
||||
{
|
||||
var viewModel = new SpoiEditWindowViewModel(seed, isNew, existingItems);
|
||||
@@ -157,5 +179,10 @@ namespace XLAB2
|
||||
|
||||
MessageBox.Show(Owner, message, "ПСВ", MessageBoxButton.OK, image);
|
||||
}
|
||||
|
||||
private static DocumentNumberDirectoryService CreateDefaultDocumentNumberDirectoryService()
|
||||
{
|
||||
return new DocumentNumberDirectoryService(System.IO.Path.Combine(AppContext.BaseDirectory, "Assets", "document-number-directory.json"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
260
XLAB2/DocumentNumberDirectoryService.cs
Normal file
260
XLAB2/DocumentNumberDirectoryService.cs
Normal file
@@ -0,0 +1,260 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,8 @@
|
||||
Click="SpoiDirectoryMenuItem_Click" />
|
||||
<MenuItem Header="Наименования типов СИ"
|
||||
Click="SpnmtpDirectoryMenuItem_Click" />
|
||||
<MenuItem Header="Книги учета"
|
||||
Click="AccountingBookDirectoryMenuItem_Click" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="Подразделения"
|
||||
Click="FrpdDirectoryMenuItem_Click" />
|
||||
|
||||
@@ -6,12 +6,14 @@ namespace XLAB2
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly DocumentNumberDirectoryService _documentNumberDirectoryService;
|
||||
private readonly MainWindowViewModel _viewModel;
|
||||
|
||||
internal MainWindow(PsvDataService service)
|
||||
internal MainWindow(PsvDataService service, DocumentNumberDirectoryService documentNumberDirectoryService)
|
||||
{
|
||||
InitializeComponent();
|
||||
_viewModel = new MainWindowViewModel(service, new DialogService(this));
|
||||
_documentNumberDirectoryService = documentNumberDirectoryService;
|
||||
_viewModel = new MainWindowViewModel(service, new DialogService(this, documentNumberDirectoryService));
|
||||
DataContext = _viewModel;
|
||||
}
|
||||
|
||||
@@ -79,6 +81,13 @@ namespace XLAB2
|
||||
window.ShowDialog();
|
||||
}
|
||||
|
||||
private void AccountingBookDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = new AccountingBookDirectoryWindow(_documentNumberDirectoryService);
|
||||
window.Owner = this;
|
||||
window.ShowDialog();
|
||||
}
|
||||
|
||||
private void TypeSizeDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = new TypeSizeDirectoryWindow();
|
||||
|
||||
@@ -517,8 +517,18 @@ namespace XLAB2
|
||||
|
||||
public sealed class DocumentEditorResult
|
||||
{
|
||||
public string AccountingBookKey { get; set; }
|
||||
|
||||
public string AccountingBookTitle { get; set; }
|
||||
|
||||
public string DocumentNumber { get; set; }
|
||||
|
||||
public string DocumentSequenceNumber { get; set; }
|
||||
|
||||
public string DocumentTypeKey { get; set; }
|
||||
|
||||
public string DocumentTypeTitle { get; set; }
|
||||
|
||||
public DateTime AcceptedOn { get; set; }
|
||||
|
||||
public DateTime? IssuedOn { get; set; }
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
<Content Include="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Assets\document-number-directory.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="ClosePsv.docx">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
Reference in New Issue
Block a user