This commit is contained in:
Курнат Андрей
2026-03-14 21:20:14 +03:00
parent c2548c377b
commit 5995d4fa72
36 changed files with 5550 additions and 0 deletions

View File

@@ -13,6 +13,8 @@ namespace XLAB
IReadOnlyList<string> ShowCloneVerificationDialog(CloneVerificationSeed seed);
SpoiDirectoryItem ShowSpoiEditDialog(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems);
SpnmtpDirectoryItem ShowSpnmtpEditDialog(SpnmtpDirectoryItem seed, bool isNew, IReadOnlyList<SpnmtpDirectoryItem> existingItems);
VerificationEditResult ShowVerificationDialog(
@@ -78,6 +80,16 @@ namespace XLAB
return result.HasValue && result.Value ? viewModel.GetSerialNumbers() : null;
}
public SpoiDirectoryItem ShowSpoiEditDialog(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems)
{
var viewModel = new SpoiEditWindowViewModel(seed, isNew, existingItems);
var window = new SpoiEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public SpnmtpDirectoryItem ShowSpnmtpEditDialog(SpnmtpDirectoryItem seed, bool isNew, IReadOnlyList<SpnmtpDirectoryItem> existingItems)
{
var viewModel = new SpnmtpEditWindowViewModel(seed, isNew, existingItems);

View File

@@ -17,6 +17,10 @@
<Menu Grid.Row="0"
Margin="0,0,0,12">
<MenuItem Header="Справочники">
<MenuItem Header="Типоразмеры СИ"
Click="TypeSizeDirectoryMenuItem_Click" />
<MenuItem Header="Области измерений"
Click="SpoiDirectoryMenuItem_Click" />
<MenuItem Header="Наименования типов СИ"
Click="SpnmtpDirectoryMenuItem_Click" />
</MenuItem>

View File

@@ -52,6 +52,20 @@ namespace XLAB
window.ShowDialog();
}
private void TypeSizeDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new TypeSizeDirectoryWindow();
window.Owner = this;
window.ShowDialog();
}
private void SpoiDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new SpoiDirectoryWindow();
window.Owner = this;
window.ShowDialog();
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await _viewModel.InitializeAsync();

View File

@@ -146,6 +146,184 @@ ORDER BY fr.NMFRD, v.IDVDODVDD;";
return forms;
}
public IReadOnlyList<SpoiDirectoryItem> LoadSpoiItems()
{
const string sql = @"
SELECT
s.IDSPOI AS Id,
s.KDOI AS Code,
s.NMOI AS Name
FROM dbo.SPOI s
ORDER BY s.NMOI, s.KDOI, s.IDSPOI;";
var items = new List<SpoiDirectoryItem>();
using (var connection = CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = 60;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new SpoiDirectoryItem
{
Id = GetInt32(reader, "Id"),
Code = GetString(reader, "Code"),
Name = GetString(reader, "Name")
});
}
}
}
return items;
}
public int AddSpoiItem(SpoiDirectoryItem item)
{
var normalizedItem = NormalizeSpoiItem(item);
const string sql = @"
INSERT INTO dbo.SPOI
(
KDOI,
NMOI
)
VALUES
(
@Code,
@Name
);
SELECT CAST(SCOPE_IDENTITY() AS int);";
using (var connection = CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureSpoiCodeIsUnique(connection, normalizedItem.Code, null);
EnsureSpoiNameIsUnique(connection, normalizedItem.Name, null);
command.CommandTimeout = 60;
command.Parameters.Add("@Code", SqlDbType.VarChar, SpoiDirectoryRules.CodeMaxLength).Value = normalizedItem.Code;
command.Parameters.Add("@Name", SqlDbType.VarChar, SpoiDirectoryRules.NameMaxLength).Value = normalizedItem.Name;
try
{
return Convert.ToInt32(command.ExecuteScalar());
}
catch (SqlException ex) when (IsSpoiDuplicateCodeViolation(ex))
{
throw CreateSpoiDuplicateCodeException(normalizedItem.Code, ex);
}
catch (SqlException ex) when (IsSpoiDuplicateNameViolation(ex))
{
throw CreateSpoiDuplicateNameException(normalizedItem.Name, ex);
}
}
}
public void UpdateSpoiItem(SpoiDirectoryItem item)
{
var normalizedItem = NormalizeSpoiItem(item);
if (normalizedItem.Id <= 0)
{
throw new InvalidOperationException("Не выбрана запись SPOI для изменения.");
}
const string sql = @"
UPDATE dbo.SPOI
SET KDOI = @Code,
NMOI = @Name
WHERE IDSPOI = @Id;
SELECT @@ROWCOUNT;";
using (var connection = CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureSpoiCodeIsUnique(connection, normalizedItem.Code, normalizedItem.Id);
EnsureSpoiNameIsUnique(connection, normalizedItem.Name, normalizedItem.Id);
command.CommandTimeout = 60;
command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
command.Parameters.Add("@Code", SqlDbType.VarChar, SpoiDirectoryRules.CodeMaxLength).Value = normalizedItem.Code;
command.Parameters.Add("@Name", SqlDbType.VarChar, SpoiDirectoryRules.NameMaxLength).Value = normalizedItem.Name;
try
{
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись SPOI для изменения не найдена.");
}
}
catch (SqlException ex) when (IsSpoiDuplicateCodeViolation(ex))
{
throw CreateSpoiDuplicateCodeException(normalizedItem.Code, ex);
}
catch (SqlException ex) when (IsSpoiDuplicateNameViolation(ex))
{
throw CreateSpoiDuplicateNameException(normalizedItem.Name, ex);
}
}
}
public SpoiDeleteResult DeleteSpoiItem(int id)
{
if (id <= 0)
{
throw new InvalidOperationException("Не выбрана запись SPOI для удаления.");
}
const string sql = @"
DELETE FROM dbo.SPOI
WHERE IDSPOI = @Id;
SELECT @@ROWCOUNT;";
try
{
using (var connection = CreateConnection())
{
connection.Open();
var blockers = LoadSpoiDeleteBlockers(connection, id);
if (blockers.Count > 0)
{
return new SpoiDeleteResult
{
IsDeleted = false,
WarningMessage = CreateSpoiDeleteBlockedMessage(blockers)
};
}
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60;
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись SPOI для удаления не найдена.");
}
}
return new SpoiDeleteResult
{
IsDeleted = true
};
}
}
catch (SqlException ex) when (ex.Number == 547)
{
return new SpoiDeleteResult
{
IsDeleted = false,
WarningMessage = CreateSpoiDeleteBlockedMessage(ex)
};
}
}
public IReadOnlyList<SpnmtpDirectoryItem> LoadSpnmtpItems()
{
const string sql = @"
@@ -2304,6 +2482,216 @@ WHERE z.IDEKZ = @InstrumentId
return value.Trim();
}
private static SpoiDirectoryItem NormalizeSpoiItem(SpoiDirectoryItem item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
var code = NormalizeRequiredSpoiValue(
item.Code,
"Код ОИ не заполнен.",
SpoiDirectoryRules.CodeMaxLength,
"Код ОИ превышает допустимую длину.");
var name = NormalizeRequiredSpoiValue(
item.Name,
"Область измерений не заполнена.",
SpoiDirectoryRules.NameMaxLength,
"Область измерений превышает допустимую длину.");
return new SpoiDirectoryItem
{
Id = item.Id,
Code = code,
Name = name
};
}
private static void EnsureSpoiCodeIsUnique(SqlConnection connection, string code, int? excludeId)
{
const string sql = @"
SELECT TOP (1) IDSPOI
FROM dbo.SPOI
WHERE KDOI = @Code
AND (@ExcludeId IS NULL OR IDSPOI <> @ExcludeId);";
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60;
command.Parameters.Add("@Code", SqlDbType.VarChar, SpoiDirectoryRules.CodeMaxLength).Value = code;
command.Parameters.Add("@ExcludeId", SqlDbType.Int).Value = (object)excludeId ?? DBNull.Value;
var existingId = command.ExecuteScalar();
if (existingId != null && existingId != DBNull.Value)
{
throw CreateSpoiDuplicateCodeException(code, null);
}
}
}
private static void EnsureSpoiNameIsUnique(SqlConnection connection, string name, int? excludeId)
{
const string sql = @"
SELECT TOP (1) IDSPOI
FROM dbo.SPOI
WHERE NMOI = @Name
AND (@ExcludeId IS NULL OR IDSPOI <> @ExcludeId);";
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60;
command.Parameters.Add("@Name", SqlDbType.VarChar, SpoiDirectoryRules.NameMaxLength).Value = name;
command.Parameters.Add("@ExcludeId", SqlDbType.Int).Value = (object)excludeId ?? DBNull.Value;
var existingId = command.ExecuteScalar();
if (existingId != null && existingId != DBNull.Value)
{
throw CreateSpoiDuplicateNameException(name, null);
}
}
}
private static InvalidOperationException CreateSpoiDuplicateCodeException(string code, Exception innerException)
{
return new InvalidOperationException(
string.Format("Код ОИ \"{0}\" уже существует в справочнике.", code),
innerException);
}
private static InvalidOperationException CreateSpoiDuplicateNameException(string name, Exception innerException)
{
return new InvalidOperationException(
string.Format("Область измерений \"{0}\" уже существует в справочнике.", name),
innerException);
}
private static List<DeleteBlockerInfo> LoadSpoiDeleteBlockers(SqlConnection connection, int id)
{
const string sql = @"
SELECT blocker.TableName, blocker.LinkCount
FROM
(
SELECT N'FRATGR' AS TableName, COUNT(*) AS LinkCount
FROM dbo.FRATGR
WHERE IDSPOI = @Id
UNION ALL
SELECT N'PRATGR', COUNT(*)
FROM dbo.PRATGR
WHERE IDSPOI = @Id
UNION ALL
SELECT N'PRSTOI', COUNT(*)
FROM dbo.PRSTOI
WHERE IDSPOI = @Id
UNION ALL
SELECT N'TIPS', COUNT(*)
FROM dbo.TIPS
WHERE IDSPOI = @Id
) blocker
WHERE blocker.LinkCount > 0
ORDER BY blocker.TableName;";
var blockers = new List<DeleteBlockerInfo>();
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60;
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
blockers.Add(new DeleteBlockerInfo
{
TableName = GetString(reader, "TableName"),
RowCount = GetInt32(reader, "LinkCount")
});
}
}
}
return blockers;
}
private static string CreateSpoiDeleteBlockedMessage(IEnumerable<DeleteBlockerInfo> blockers)
{
var blockerList = blockers == null
? new List<DeleteBlockerInfo>()
: blockers.Where(delegate(DeleteBlockerInfo blocker) { return blocker != null; }).ToList();
var details = blockerList.Count == 0
? "FRATGR, PRATGR, PRSTOI, TIPS"
: string.Join(", ", blockerList.Select(delegate(DeleteBlockerInfo blocker)
{
return string.Format("{0}: {1}", blocker.TableName, blocker.RowCount);
}));
return string.Format(
"Запись SPOI не может быть удалена, потому что на неё есть ссылки в таблицах: {0}. Подтверждённые ограничения БД: FK_FRATGR_SPOI, FK_PRATGR_SPOI, FK_PRSTOI_SPOI, FK_TIPS_SPOI.",
details);
}
private static string CreateSpoiDeleteBlockedMessage(SqlException ex)
{
if (ex != null && ex.Message.IndexOf("FK_FRATGR_SPOI", StringComparison.OrdinalIgnoreCase) >= 0)
{
return "Запись SPOI не может быть удалена, потому что на неё есть ссылки в таблице FRATGR. Ограничение БД: FK_FRATGR_SPOI (dbo.FRATGR.IDSPOI -> dbo.SPOI.IDSPOI).";
}
if (ex != null && ex.Message.IndexOf("FK_PRATGR_SPOI", StringComparison.OrdinalIgnoreCase) >= 0)
{
return "Запись SPOI не может быть удалена, потому что на неё есть ссылки в таблице PRATGR. Ограничение БД: FK_PRATGR_SPOI (dbo.PRATGR.IDSPOI -> dbo.SPOI.IDSPOI).";
}
if (ex != null && ex.Message.IndexOf("FK_PRSTOI_SPOI", StringComparison.OrdinalIgnoreCase) >= 0)
{
return "Запись SPOI не может быть удалена, потому что на неё есть ссылки в таблице PRSTOI. Ограничение БД: FK_PRSTOI_SPOI (dbo.PRSTOI.IDSPOI -> dbo.SPOI.IDSPOI).";
}
if (ex != null && ex.Message.IndexOf("FK_TIPS_SPOI", StringComparison.OrdinalIgnoreCase) >= 0)
{
return "Запись SPOI не может быть удалена, потому что на неё есть ссылки в таблице TIPS. Ограничение БД: FK_TIPS_SPOI (dbo.TIPS.IDSPOI -> dbo.SPOI.IDSPOI).";
}
return "Запись SPOI не может быть удалена из-за ограничения ссылочной целостности в БД.";
}
private static bool IsSpoiDuplicateCodeViolation(SqlException ex)
{
return ex != null
&& (ex.Number == 2601 || ex.Number == 2627)
&& ex.Message.IndexOf("XAK2SPOI", StringComparison.OrdinalIgnoreCase) >= 0;
}
private static bool IsSpoiDuplicateNameViolation(SqlException ex)
{
return ex != null
&& (ex.Number == 2601 || ex.Number == 2627)
&& ex.Message.IndexOf("XAK1SPOI", StringComparison.OrdinalIgnoreCase) >= 0;
}
private static string NormalizeRequiredSpoiValue(string value, string emptyErrorMessage, int maxLength, string tooLongErrorMessage)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new InvalidOperationException(emptyErrorMessage);
}
var normalizedValue = value.Trim();
if (normalizedValue.Length > maxLength)
{
throw new InvalidOperationException(tooLongErrorMessage);
}
return normalizedValue;
}
private static SpnmtpDirectoryItem NormalizeSpnmtpItem(SpnmtpDirectoryItem item)
{
if (item == null)

View File

@@ -360,6 +360,29 @@ namespace XLAB
public string WarningMessage { get; set; }
}
public sealed class SpoiDirectoryItem
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
internal sealed class SpoiDeleteResult
{
public bool IsDeleted { get; set; }
public string WarningMessage { get; set; }
}
internal static class SpoiDirectoryRules
{
public const int CodeMaxLength = 3;
public const int NameMaxLength = 80;
}
internal static class SpnmtpDirectoryRules
{
public const int NameMaxLength = 150;

View File

@@ -0,0 +1,86 @@
<Window x:Class="XLAB.SpoiDirectoryWindow"
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="170"
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.Columns>
<DataGridTextColumn Header="ID"
Width="90"
Binding="{Binding Id}" />
<DataGridTextColumn Header="Код ОИ"
Width="120"
Binding="{Binding Code}" />
<DataGridTextColumn Header="Область измерений"
Width="*"
Binding="{Binding Name}" />
</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="110"
Margin="0,0,8,0"
Command="{Binding AddCommand}"
Content="Добавить" />
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding EditCommand}"
Content="Изменить" />
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding DeleteCommand}"
Content="Удалить" />
<Button Width="90"
IsCancel="True"
Content="Закрыть" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,21 @@
using System.Windows;
namespace XLAB
{
public partial class SpoiDirectoryWindow : Window
{
private readonly SpoiDirectoryWindowViewModel _viewModel;
public SpoiDirectoryWindow()
{
InitializeComponent();
_viewModel = new SpoiDirectoryWindowViewModel(new PsvDataService(), new DialogService(this));
DataContext = _viewModel;
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await _viewModel.InitializeAsync();
}
}
}

View File

@@ -0,0 +1,294 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Input;
namespace XLAB
{
internal sealed class SpoiDirectoryWindowViewModel : ObservableObject
{
private readonly IDialogService _dialogService;
private readonly PsvDataService _service;
private bool _isBusy;
private string _searchText;
private SpoiDirectoryItem _selectedItem;
private string _statusText;
public SpoiDirectoryWindowViewModel(PsvDataService service, IDialogService dialogService)
{
_service = service;
_dialogService = dialogService;
Items = new ObservableCollection<SpoiDirectoryItem>();
ItemsView = CollectionViewSource.GetDefaultView(Items);
ItemsView.Filter = FilterItems;
AddCommand = new RelayCommand(delegate { AddItemAsync(); }, delegate { return !IsBusy; });
DeleteCommand = new RelayCommand(delegate { DeleteSelectedItemAsync(); }, delegate { return !IsBusy && SelectedItem != null; });
EditCommand = new RelayCommand(delegate { EditSelectedItemAsync(); }, delegate { return !IsBusy && SelectedItem != null; });
RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });
UpdateStatus();
}
public ICommand AddCommand { get; private set; }
public ICommand DeleteCommand { get; private set; }
public ICommand EditCommand { get; private set; }
public ObservableCollection<SpoiDirectoryItem> Items { get; private set; }
public ICollectionView ItemsView { get; private set; }
public bool IsBusy
{
get { return _isBusy; }
private set
{
if (SetProperty(ref _isBusy, value))
{
RaiseCommandStates();
}
}
}
public ICommand RefreshCommand { get; private set; }
public string SearchText
{
get { return _searchText; }
set
{
if (SetProperty(ref _searchText, value))
{
ItemsView.Refresh();
UpdateStatus();
}
}
}
public SpoiDirectoryItem SelectedItem
{
get { return _selectedItem; }
set
{
if (SetProperty(ref _selectedItem, value))
{
RaiseCommandStates();
}
}
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public async Task InitializeAsync()
{
await ExecuteBusyOperationAsync(delegate { return RefreshCoreAsync(null); });
}
private void AddItemAsync()
{
var result = _dialogService.ShowSpoiEditDialog(new SpoiDirectoryItem(), true, Items.ToList());
if (result == null)
{
return;
}
RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _service.AddSpoiItem(result); });
await RefreshCoreAsync(createdId);
_dialogService.ShowInfo("Запись справочника добавлена.");
});
}
private bool Contains(string source, string searchText)
{
return !string.IsNullOrWhiteSpace(source)
&& source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
}
private void DeleteSelectedItemAsync()
{
if (SelectedItem == null)
{
return;
}
var selectedItem = SelectedItem;
if (!_dialogService.Confirm(string.Format("Удалить запись \"{0}\"?", selectedItem.Name)))
{
return;
}
RunMutationOperation(async delegate
{
var deleteResult = await Task.Run(delegate { return _service.DeleteSpoiItem(selectedItem.Id); });
if (!deleteResult.IsDeleted)
{
_dialogService.ShowWarning(deleteResult.WarningMessage);
return;
}
await RefreshCoreAsync(null);
_dialogService.ShowInfo("Запись справочника удалена.");
});
}
private void EditSelectedItemAsync()
{
if (SelectedItem == null)
{
return;
}
var seed = new SpoiDirectoryItem
{
Id = SelectedItem.Id,
Code = SelectedItem.Code,
Name = SelectedItem.Name
};
var result = _dialogService.ShowSpoiEditDialog(seed, false, Items.ToList());
if (result == null)
{
return;
}
RunMutationOperation(async delegate
{
await Task.Run(delegate { _service.UpdateSpoiItem(result); });
await RefreshCoreAsync(result.Id);
_dialogService.ShowInfo("Запись справочника обновлена.");
});
}
private async Task ExecuteBusyOperationAsync(Func<Task> operation)
{
try
{
IsBusy = true;
await operation();
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
}
finally
{
IsBusy = false;
}
}
private async Task ExecuteMutationOperationAsync(Func<Task> operation)
{
try
{
IsBusy = true;
await operation();
}
catch (InvalidOperationException ex)
{
_dialogService.ShowWarning(ex.Message);
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
}
finally
{
IsBusy = false;
}
}
private bool FilterItems(object item)
{
var directoryItem = item as SpoiDirectoryItem;
if (directoryItem == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(SearchText))
{
return true;
}
return Contains(directoryItem.Code, SearchText)
|| Contains(directoryItem.Name, SearchText)
|| directoryItem.Id.ToString().IndexOf(SearchText, StringComparison.OrdinalIgnoreCase) >= 0;
}
private async Task RefreshCoreAsync(int? idToSelect)
{
var items = await Task.Run(delegate { return _service.LoadSpoiItems(); });
var currentId = idToSelect ?? (SelectedItem == null ? (int?)null : SelectedItem.Id);
Items.Clear();
foreach (var item in items)
{
Items.Add(item);
}
ItemsView.Refresh();
SelectedItem = currentId.HasValue
? Items.FirstOrDefault(delegate(SpoiDirectoryItem item) { return item.Id == currentId.Value; })
: Items.FirstOrDefault();
UpdateStatus();
}
private void RefreshAsync()
{
RunBusyOperation(delegate { return RefreshCoreAsync(null); });
}
private void RaiseCommandStates()
{
((RelayCommand)AddCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteCommand).RaiseCanExecuteChanged();
((RelayCommand)EditCommand).RaiseCanExecuteChanged();
((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
}
private async void RunBusyOperation(Func<Task> operation)
{
try
{
await ExecuteBusyOperationAsync(operation);
}
catch (Exception ex)
{
IsBusy = false;
_dialogService.ShowError(ex.Message);
}
}
private async void RunMutationOperation(Func<Task> operation)
{
try
{
await ExecuteMutationOperationAsync(operation);
}
catch (Exception ex)
{
IsBusy = false;
_dialogService.ShowError(ex.Message);
}
}
private void UpdateStatus()
{
var visibleCount = ItemsView.Cast<object>().Count();
StatusText = string.Format("Всего записей: {0}. По фильтру: {1}.", Items.Count, visibleCount);
}
}
}

65
XLAB/SpoiEditWindow.xaml Normal file
View File

@@ -0,0 +1,65 @@
<Window x:Class="XLAB.SpoiEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="230"
Width="540"
MinHeight="220"
MinWidth="500"
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"
MaxLength="3"
Text="{Binding Code, 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 Name, 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>

View File

@@ -0,0 +1,20 @@
using System.Windows;
namespace XLAB
{
public partial class SpoiEditWindow : Window
{
internal SpoiEditWindow(SpoiEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB
{
internal sealed class SpoiEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<SpoiDirectoryItem> _existingItems;
private string _code;
private string _name;
private string _validationMessage;
public SpoiEditWindowViewModel(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems)
{
var source = seed ?? new SpoiDirectoryItem();
_existingItems = existingItems ?? Array.Empty<SpoiDirectoryItem>();
Id = source.Id;
IsNew = isNew;
Code = source.Code ?? string.Empty;
Name = source.Name ?? string.Empty;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public string Code
{
get { return _code; }
set { SetProperty(ref _code, value); }
}
public ICommand ConfirmCommand { get; private set; }
public int Id { get; private set; }
public bool IsNew { get; private set; }
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
public string Title
{
get { return IsNew ? "Новая область измерений" : "Редактирование области измерений"; }
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public SpoiDirectoryItem ToResult()
{
return new SpoiDirectoryItem
{
Id = Id,
Code = string.IsNullOrWhiteSpace(Code) ? string.Empty : Code.Trim(),
Name = string.IsNullOrWhiteSpace(Name) ? string.Empty : Name.Trim()
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
var normalizedCode = string.IsNullOrWhiteSpace(Code) ? string.Empty : Code.Trim();
var normalizedName = string.IsNullOrWhiteSpace(Name) ? string.Empty : Name.Trim();
if (normalizedCode.Length == 0)
{
ValidationMessage = "Укажите код ОИ.";
return;
}
if (normalizedCode.Length > SpoiDirectoryRules.CodeMaxLength)
{
ValidationMessage = string.Format("Код ОИ не должен превышать {0} символов.", SpoiDirectoryRules.CodeMaxLength);
return;
}
if (normalizedName.Length == 0)
{
ValidationMessage = "Укажите область измерений.";
return;
}
if (normalizedName.Length > SpoiDirectoryRules.NameMaxLength)
{
ValidationMessage = string.Format("Область измерений не должна превышать {0} символов.", SpoiDirectoryRules.NameMaxLength);
return;
}
var duplicateCode = _existingItems.FirstOrDefault(delegate(SpoiDirectoryItem item)
{
return item != null
&& item.Id != Id
&& string.Equals(
string.IsNullOrWhiteSpace(item.Code) ? string.Empty : item.Code.Trim(),
normalizedCode,
StringComparison.OrdinalIgnoreCase);
});
if (duplicateCode != null)
{
ValidationMessage = string.Format("Код ОИ \"{0}\" уже существует в справочнике.", normalizedCode);
return;
}
var duplicateName = _existingItems.FirstOrDefault(delegate(SpoiDirectoryItem item)
{
return item != null
&& item.Id != Id
&& string.Equals(
string.IsNullOrWhiteSpace(item.Name) ? string.Empty : item.Name.Trim(),
normalizedName,
StringComparison.OrdinalIgnoreCase);
});
if (duplicateName != null)
{
ValidationMessage = string.Format("Область измерений \"{0}\" уже существует в справочнике.", normalizedName);
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
}
}

87
XLAB/TipsEditWindow.xaml Normal file
View File

@@ -0,0 +1,87 @@
<Window x:Class="XLAB.TipsEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="620"
Width="760"
MinHeight="580"
MinWidth="700"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Область измерений" />
<ComboBox Grid.Row="0" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding MeasurementAreas}" SelectedValue="{Binding MeasurementAreaId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Наименование типа СИ" />
<ComboBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding InstrumentNames}" SelectedValue="{Binding InstrumentNameId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Тип СИ" />
<TextBox Grid.Row="2" Grid.Column="1" Margin="0,0,0,8" Text="{Binding TypeName, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="3" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Категория СИ" />
<ComboBox Grid.Row="3" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding Categories}" SelectedValue="{Binding CategoryId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="4" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Конструктивное исполнение" />
<ComboBox Grid.Row="4" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding Designs}" SelectedValue="{Binding DesignId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="5" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Срок службы, лет" />
<TextBox Grid.Row="5" Grid.Column="1" Margin="0,0,0,8" Text="{Binding ServiceLifeYearsText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="6" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="МПИ по Госреестру, мес." />
<TextBox Grid.Row="6" Grid.Column="1" Margin="0,0,0,8" Text="{Binding RegistryPeriodMonthsText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="7" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="№ по Госреестру" />
<TextBox Grid.Row="7" Grid.Column="1" Margin="0,0,0,8" Text="{Binding RegistryTypeNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="8" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Top" Text="Дополнительные сведения" />
<TextBox Grid.Row="8" Grid.Column="1" Margin="0,0,0,8" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" TextWrapping="Wrap" Text="{Binding Notes, UpdateSourceTrigger=PropertyChanged}" />
<StackPanel Grid.Row="9" Grid.ColumnSpan="2" Orientation="Horizontal">
<StackPanel Width="360" Orientation="Horizontal">
<TextBlock Width="220" VerticalAlignment="Center" Text="Код ВНИИМС" />
<TextBox Width="120" Text="{Binding VniimsTypeCodeText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel Width="220" Margin="12,0,0,0" Orientation="Horizontal">
<TextBlock Width="90" VerticalAlignment="Center" Text="Код МК" />
<TextBox Width="120" Text="{Binding MetrControlCode, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</StackPanel>
</Grid>
<StackPanel Grid.Row="1" Margin="0,12,0,0" Orientation="Horizontal">
<CheckBox Margin="0,0,24,0" IsThreeState="True" IsChecked="{Binding IsSpecialPurpose}" Content="Специальное назначение" />
<CheckBox IsThreeState="True" IsChecked="{Binding IsMkPrimaryOnly}" Content="МК только первичный" />
</StackPanel>
<DockPanel Grid.Row="2" Margin="0,12,0,0">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Center" Foreground="Firebrick" Text="{Binding ValidationMessage}" />
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Width="100" Margin="0,0,8,0" IsDefault="True" Command="{Binding ConfirmCommand}" Content="Сохранить" />
<Button Width="90" Command="{Binding CancelCommand}" Content="Отмена" />
</StackPanel>
</DockPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,20 @@
using System.Windows;
namespace XLAB
{
public partial class TipsEditWindow : Window
{
internal TipsEditWindow(TipsEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB
{
internal sealed class TipsEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<TipsDirectoryItem> _existingItems;
private int _categoryId;
private int _designId;
private int _instrumentNameId;
private bool? _isMkPrimaryOnly;
private bool? _isSpecialPurpose;
private int _measurementAreaId;
private string _metrControlCode;
private string _notes;
private string _registryPeriodMonthsText;
private string _registryTypeNumber;
private string _serviceLifeYearsText;
private string _typeName;
private string _validationMessage;
private string _vniimsTypeCodeText;
public TipsEditWindowViewModel(TipsDirectoryItem seed, bool isNew, IReadOnlyList<TipsDirectoryItem> existingItems, TypeSizeDirectoryService service)
{
var source = seed ?? new TipsDirectoryItem();
_existingItems = existingItems ?? Array.Empty<TipsDirectoryItem>();
Id = source.Id;
IsNew = isNew;
MeasurementAreas = service.LoadSpoiReferences();
InstrumentNames = service.LoadSpnmtpReferences();
Categories = WithEmpty(service.LoadSpktReferences());
Designs = WithEmpty(service.LoadSpkiReferences());
MeasurementAreaId = source.MeasurementAreaId;
InstrumentNameId = source.InstrumentNameId;
CategoryId = source.CategoryId ?? 0;
DesignId = source.DesignId ?? 0;
TypeName = source.TypeName ?? string.Empty;
ServiceLifeYearsText = source.ServiceLifeYears.HasValue ? source.ServiceLifeYears.Value.ToString() : string.Empty;
RegistryPeriodMonthsText = source.RegistryPeriodMonths.HasValue ? source.RegistryPeriodMonths.Value.ToString() : string.Empty;
RegistryTypeNumber = source.RegistryTypeNumber ?? string.Empty;
VniimsTypeCodeText = source.VniimsTypeCode.HasValue ? source.VniimsTypeCode.Value.ToString() : string.Empty;
MetrControlCode = source.MetrControlCode ?? string.Empty;
Notes = source.Notes ?? string.Empty;
IsSpecialPurpose = source.IsSpecialPurpose;
IsMkPrimaryOnly = source.IsMkPrimaryOnly;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public IReadOnlyList<DirectoryLookupItem> Categories { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public IReadOnlyList<DirectoryLookupItem> Designs { get; private set; }
public int Id { get; private set; }
public IReadOnlyList<DirectoryLookupItem> InstrumentNames { get; private set; }
public bool IsNew { get; private set; }
public IReadOnlyList<DirectoryLookupItem> MeasurementAreas { get; private set; }
public int MeasurementAreaId { get { return _measurementAreaId; } set { SetProperty(ref _measurementAreaId, value); } }
public int InstrumentNameId { get { return _instrumentNameId; } set { SetProperty(ref _instrumentNameId, value); } }
public int CategoryId { get { return _categoryId; } set { SetProperty(ref _categoryId, value); } }
public int DesignId { get { return _designId; } set { SetProperty(ref _designId, value); } }
public string TypeName { get { return _typeName; } set { SetProperty(ref _typeName, value); } }
public string ServiceLifeYearsText { get { return _serviceLifeYearsText; } set { SetProperty(ref _serviceLifeYearsText, value); } }
public string RegistryPeriodMonthsText { get { return _registryPeriodMonthsText; } set { SetProperty(ref _registryPeriodMonthsText, value); } }
public string RegistryTypeNumber { get { return _registryTypeNumber; } set { SetProperty(ref _registryTypeNumber, value); } }
public string VniimsTypeCodeText { get { return _vniimsTypeCodeText; } set { SetProperty(ref _vniimsTypeCodeText, value); } }
public string MetrControlCode { get { return _metrControlCode; } set { SetProperty(ref _metrControlCode, value); } }
public string Notes { get { return _notes; } set { SetProperty(ref _notes, value); } }
public bool? IsSpecialPurpose { get { return _isSpecialPurpose; } set { SetProperty(ref _isSpecialPurpose, value); } }
public bool? IsMkPrimaryOnly { get { return _isMkPrimaryOnly; } set { SetProperty(ref _isMkPrimaryOnly, value); } }
public string Title
{
get { return IsNew ? "Новый тип СИ" : "Редактирование типа СИ"; }
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public TipsDirectoryItem ToResult()
{
return new TipsDirectoryItem
{
Id = Id,
MeasurementAreaId = MeasurementAreaId,
InstrumentNameId = InstrumentNameId,
CategoryId = CategoryId > 0 ? (int?)CategoryId : null,
DesignId = DesignId > 0 ? (int?)DesignId : null,
TypeName = Normalize(TypeName),
ServiceLifeYears = ParseNullableInt(ServiceLifeYearsText),
RegistryPeriodMonths = ParseNullableInt(RegistryPeriodMonthsText),
RegistryTypeNumber = Normalize(RegistryTypeNumber),
VniimsTypeCode = ParseNullableInt(VniimsTypeCodeText),
MetrControlCode = Normalize(MetrControlCode),
Notes = Normalize(Notes),
IsSpecialPurpose = IsSpecialPurpose,
IsMkPrimaryOnly = IsMkPrimaryOnly
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
if (MeasurementAreaId <= 0)
{
ValidationMessage = "Укажите область измерений.";
return;
}
if (InstrumentNameId <= 0)
{
ValidationMessage = "Укажите наименование типа СИ.";
return;
}
var normalizedTypeName = Normalize(TypeName);
if (string.IsNullOrWhiteSpace(normalizedTypeName))
{
ValidationMessage = "Укажите тип СИ.";
return;
}
if (normalizedTypeName.Length > TipsDirectoryRules.TypeNameMaxLength)
{
ValidationMessage = string.Format("Тип СИ не должен превышать {0} символов.", TipsDirectoryRules.TypeNameMaxLength);
return;
}
int? parsedValue;
if (!TryParseNullableInt(ServiceLifeYearsText, out parsedValue))
{
ValidationMessage = "Срок службы должен быть целым числом.";
return;
}
if (!TryParseNullableInt(RegistryPeriodMonthsText, out parsedValue))
{
ValidationMessage = "МПИ по Госреестру должен быть целым числом.";
return;
}
if (!TryParseNullableInt(VniimsTypeCodeText, out parsedValue))
{
ValidationMessage = "Код ВНИИМС должен быть целым числом.";
return;
}
var normalizedRegistryNumber = Normalize(RegistryTypeNumber);
if (!string.IsNullOrWhiteSpace(normalizedRegistryNumber) && normalizedRegistryNumber.Length > TipsDirectoryRules.RegistryTypeNumberMaxLength)
{
ValidationMessage = string.Format("Номер по Госреестру не должен превышать {0} символов.", TipsDirectoryRules.RegistryTypeNumberMaxLength);
return;
}
var normalizedMetrControlCode = Normalize(MetrControlCode);
if (!string.IsNullOrWhiteSpace(normalizedMetrControlCode) && normalizedMetrControlCode.Length > TipsDirectoryRules.MetrControlCodeMaxLength)
{
ValidationMessage = string.Format("Код АИС \"Метрконтроль\" не должен превышать {0} символов.", TipsDirectoryRules.MetrControlCodeMaxLength);
return;
}
var duplicate = _existingItems.FirstOrDefault(delegate(TipsDirectoryItem item)
{
return item != null
&& item.Id != Id
&& item.InstrumentNameId == InstrumentNameId
&& string.Equals(Normalize(item.TypeName), normalizedTypeName, StringComparison.OrdinalIgnoreCase);
});
if (duplicate != null)
{
ValidationMessage = string.Format("Тип СИ \"{0}\" уже существует для выбранного наименования типа СИ.", normalizedTypeName);
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private static IReadOnlyList<DirectoryLookupItem> WithEmpty(IReadOnlyList<DirectoryLookupItem> source)
{
return new[] { new DirectoryLookupItem { Id = 0, Name = string.Empty } }.Concat(source ?? Array.Empty<DirectoryLookupItem>()).ToList();
}
private static string Normalize(string value)
{
return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
}
private static int? ParseNullableInt(string value)
{
int? parsedValue;
return TryParseNullableInt(value, out parsedValue) ? parsedValue : (int?)null;
}
private static bool TryParseNullableInt(string value, out int? result)
{
if (string.IsNullOrWhiteSpace(value))
{
result = null;
return true;
}
int parsed;
if (int.TryParse(value.Trim(), out parsed))
{
result = parsed;
return true;
}
result = null;
return false;
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
}
}

View File

@@ -0,0 +1,48 @@
<Window x:Class="XLAB.TprmcpEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="320"
Width="620"
MinHeight="300"
MinWidth="580"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Цикл МК" />
<ComboBox Grid.Row="0" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding CycleItems}" SelectedValue="{Binding CycleId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Группа СИ" />
<ComboBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding GroupItems}" SelectedValue="{Binding GroupId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Период МК, мес." />
<TextBox Grid.Row="2" Grid.Column="1" Margin="0,0,0,8" Text="{Binding PeriodMonthsText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="3" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Top" Text="Комментарий" />
<TextBox Grid.Row="3" Grid.Column="1" Margin="0,0,0,8" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" TextWrapping="Wrap" Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<DockPanel Grid.Row="1" Margin="0,12,0,0">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Center" Foreground="Firebrick" Text="{Binding ValidationMessage}" />
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Width="100" Margin="0,0,8,0" IsDefault="True" Command="{Binding ConfirmCommand}" Content="Сохранить" />
<Button Width="90" Command="{Binding CancelCommand}" Content="Отмена" />
</StackPanel>
</DockPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,20 @@
using System.Windows;
namespace XLAB
{
public partial class TprmcpEditWindow : Window
{
internal TprmcpEditWindow(TprmcpEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB
{
internal sealed class TprmcpEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<TprmcpDirectoryItem> _existingItems;
private int _cycleId;
private string _comment;
private int _groupId;
private string _periodMonthsText;
private string _validationMessage;
public TprmcpEditWindowViewModel(TprmcpDirectoryItem seed, bool isNew, IReadOnlyList<TprmcpDirectoryItem> existingItems, TypeSizeDirectoryService service)
{
var source = seed ?? new TprmcpDirectoryItem();
_existingItems = existingItems ?? Array.Empty<TprmcpDirectoryItem>();
Id = source.Id;
TypeSizeId = source.TypeSizeId;
IsNew = isNew;
CycleItems = new[] { new DirectoryLookupItem { Id = 0, Name = string.Empty } }.Concat(service.LoadSpvdmcReferences()).ToList();
GroupItems = new[] { new DirectoryLookupItem { Id = 0, Name = string.Empty } }.Concat(service.LoadGrsiReferences()).ToList();
CycleId = source.CycleId ?? 0;
GroupId = source.GroupId ?? 0;
PeriodMonthsText = source.PeriodMonths.ToString();
Comment = source.Comment ?? string.Empty;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public IReadOnlyList<DirectoryLookupItem> CycleItems { get; private set; }
public IReadOnlyList<DirectoryLookupItem> GroupItems { get; private set; }
public int Id { get; private set; }
public bool IsNew { get; private set; }
public int TypeSizeId { get; private set; }
public int CycleId { get { return _cycleId; } set { SetProperty(ref _cycleId, value); } }
public int GroupId { get { return _groupId; } set { SetProperty(ref _groupId, value); } }
public string PeriodMonthsText { get { return _periodMonthsText; } set { SetProperty(ref _periodMonthsText, value); } }
public string Comment { get { return _comment; } set { SetProperty(ref _comment, value); } }
public string Title { get { return IsNew ? "Новый период МК" : "Редактирование периода МК"; } }
public string ValidationMessage { get { return _validationMessage; } private set { SetProperty(ref _validationMessage, value); } }
public TprmcpDirectoryItem ToResult()
{
return new TprmcpDirectoryItem
{
Id = Id,
TypeSizeId = TypeSizeId,
CycleId = CycleId > 0 ? (int?)CycleId : null,
GroupId = GroupId > 0 ? (int?)GroupId : null,
PeriodMonths = int.Parse(PeriodMonthsText.Trim()),
Comment = Normalize(Comment)
};
}
private void Cancel(object parameter) { RaiseCloseRequested(false); }
private void Confirm(object parameter)
{
int periodMonths;
if (!int.TryParse((PeriodMonthsText ?? string.Empty).Trim(), out periodMonths))
{
ValidationMessage = "Период МК должен быть целым числом.";
return;
}
var duplicate = _existingItems.FirstOrDefault(delegate(TprmcpDirectoryItem item)
{
return item != null
&& item.Id != Id
&& item.CycleId == (CycleId > 0 ? (int?)CycleId : null)
&& item.GroupId == (GroupId > 0 ? (int?)GroupId : null)
&& item.PeriodMonths == periodMonths;
});
if (duplicate != null)
{
ValidationMessage = "Такой цикл и период МК уже существуют для выбранного типоразмера.";
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private static string Normalize(string value) { return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim(); }
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null) handler(this, dialogResult);
}
}
}

78
XLAB/TprmkEditWindow.xaml Normal file
View File

@@ -0,0 +1,78 @@
<Window x:Class="XLAB.TprmkEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="620"
Width="760"
MinHeight="580"
MinWidth="700"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Вид МК" />
<ComboBox Grid.Row="0" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding VerificationTypes}" SelectedValue="{Binding VerificationTypeId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Организация / подразделение" />
<ComboBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding Organizations}" SelectedValue="{Binding OrganizationId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Квалификация" />
<ComboBox Grid.Row="2" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding Qualifications}" SelectedValue="{Binding QualificationId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="3" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Группа СИ" />
<ComboBox Grid.Row="3" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding Groups}" SelectedValue="{Binding GroupId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="4" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Место МК" />
<ComboBox Grid.Row="4" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding Places}" SelectedValue="{Binding PlaceId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="5" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Стоимость / доп. / срочность" />
<StackPanel Grid.Row="5" Grid.Column="1" Orientation="Horizontal">
<TextBox Width="110" Margin="0,0,8,8" Text="{Binding CostText, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Width="110" Margin="0,0,8,8" Text="{Binding ExtraCostText, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Width="110" Margin="0,0,0,8" Text="{Binding RushMarkupText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<TextBlock Grid.Row="6" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Норма времени / НД" />
<StackPanel Grid.Row="6" Grid.Column="1" Orientation="Horizontal">
<TextBox Width="110" Margin="0,0,8,8" Text="{Binding TimeNormHoursText, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Width="110" Margin="0,0,0,8" Text="{Binding NormDocHoursText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<TextBlock Grid.Row="7" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Код нормы / код поверки" />
<StackPanel Grid.Row="7" Grid.Column="1" Orientation="Horizontal">
<TextBox Width="140" Margin="0,0,8,8" Text="{Binding TimeNormCode, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Width="180" Margin="0,0,0,8" Text="{Binding VerificationCode, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<TextBlock Grid.Row="8" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Количество поверителей" />
<TextBox Grid.Row="8" Grid.Column="1" Margin="0,0,0,8" Text="{Binding VerifierCountText, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<DockPanel Grid.Row="1" Margin="0,12,0,0">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Center" Foreground="Firebrick" Text="{Binding ValidationMessage}" />
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Width="100" Margin="0,0,8,0" IsDefault="True" Command="{Binding ConfirmCommand}" Content="Сохранить" />
<Button Width="90" Command="{Binding CancelCommand}" Content="Отмена" />
</StackPanel>
</DockPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,20 @@
using System.Windows;
namespace XLAB
{
public partial class TprmkEditWindow : Window
{
internal TprmkEditWindow(TprmkEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB
{
internal sealed class TprmkEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<TprmkDirectoryItem> _existingItems;
private string _costText;
private string _extraCostText;
private int _groupId;
private int _organizationId;
private int _placeId;
private int _qualificationId;
private string _rushMarkupText;
private string _timeNormCode;
private string _timeNormHoursText;
private string _normDocHoursText;
private string _validationMessage;
private string _verificationCode;
private int _verificationTypeId;
private string _verifierCountText;
public TprmkEditWindowViewModel(TprmkDirectoryItem seed, bool isNew, IReadOnlyList<TprmkDirectoryItem> existingItems, TypeSizeDirectoryService service)
{
var source = seed ?? new TprmkDirectoryItem();
_existingItems = existingItems ?? Array.Empty<TprmkDirectoryItem>();
Id = source.Id;
TypeSizeId = source.TypeSizeId;
IsNew = isNew;
VerificationTypes = service.LoadSpvdmkReferences();
Organizations = service.LoadFrpdReferences();
Qualifications = WithEmpty(service.LoadSpkvReferences());
Groups = WithEmpty(service.LoadGrsiReferences());
Places = WithEmpty(service.LoadSpmpobReferences());
VerificationTypeId = source.VerificationTypeId;
OrganizationId = source.OrganizationId;
QualificationId = source.QualificationId ?? 0;
GroupId = source.GroupId ?? 0;
PlaceId = source.PlaceId ?? 0;
CostText = ToString(source.Cost);
ExtraCostText = ToString(source.ExtraCost);
RushMarkupText = ToString(source.RushMarkup);
TimeNormHoursText = ToString(source.TimeNormHours);
NormDocHoursText = ToString(source.NormDocHours);
TimeNormCode = source.TimeNormCode ?? string.Empty;
VerificationCode = source.VerificationCode ?? string.Empty;
VerifierCountText = source.VerifierCount.ToString();
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public IReadOnlyList<DirectoryLookupItem> VerificationTypes { get; private set; }
public IReadOnlyList<DirectoryLookupItem> Organizations { get; private set; }
public IReadOnlyList<DirectoryLookupItem> Qualifications { get; private set; }
public IReadOnlyList<DirectoryLookupItem> Groups { get; private set; }
public IReadOnlyList<DirectoryLookupItem> Places { get; private set; }
public int Id { get; private set; }
public bool IsNew { get; private set; }
public int TypeSizeId { get; private set; }
public int VerificationTypeId { get { return _verificationTypeId; } set { SetProperty(ref _verificationTypeId, value); } }
public int OrganizationId { get { return _organizationId; } set { SetProperty(ref _organizationId, value); } }
public int QualificationId { get { return _qualificationId; } set { SetProperty(ref _qualificationId, value); } }
public int GroupId { get { return _groupId; } set { SetProperty(ref _groupId, value); } }
public int PlaceId { get { return _placeId; } set { SetProperty(ref _placeId, value); } }
public string CostText { get { return _costText; } set { SetProperty(ref _costText, value); } }
public string ExtraCostText { get { return _extraCostText; } set { SetProperty(ref _extraCostText, value); } }
public string RushMarkupText { get { return _rushMarkupText; } set { SetProperty(ref _rushMarkupText, value); } }
public string TimeNormHoursText { get { return _timeNormHoursText; } set { SetProperty(ref _timeNormHoursText, value); } }
public string NormDocHoursText { get { return _normDocHoursText; } set { SetProperty(ref _normDocHoursText, value); } }
public string TimeNormCode { get { return _timeNormCode; } set { SetProperty(ref _timeNormCode, value); } }
public string VerificationCode { get { return _verificationCode; } set { SetProperty(ref _verificationCode, value); } }
public string VerifierCountText { get { return _verifierCountText; } set { SetProperty(ref _verifierCountText, value); } }
public string Title { get { return IsNew ? "Новый регламент МК" : "Редактирование регламента МК"; } }
public string ValidationMessage { get { return _validationMessage; } private set { SetProperty(ref _validationMessage, value); } }
public TprmkDirectoryItem ToResult()
{
return new TprmkDirectoryItem
{
Id = Id,
TypeSizeId = TypeSizeId,
VerificationTypeId = VerificationTypeId,
OrganizationId = OrganizationId,
QualificationId = QualificationId > 0 ? (int?)QualificationId : null,
GroupId = GroupId > 0 ? (int?)GroupId : null,
PlaceId = PlaceId > 0 ? (int?)PlaceId : null,
Cost = ParseNullableDecimal(CostText),
ExtraCost = ParseNullableDecimal(ExtraCostText),
RushMarkup = ParseNullableDecimal(RushMarkupText),
TimeNormHours = ParseNullableDecimal(TimeNormHoursText),
NormDocHours = ParseNullableDecimal(NormDocHoursText),
TimeNormCode = Normalize(TimeNormCode),
VerificationCode = Normalize(VerificationCode),
VerifierCount = int.Parse(VerifierCountText.Trim())
};
}
private void Cancel(object parameter) { RaiseCloseRequested(false); }
private void Confirm(object parameter)
{
if (VerificationTypeId <= 0) { ValidationMessage = "Укажите вид МК."; return; }
if (OrganizationId <= 0) { ValidationMessage = "Укажите организацию/подразделение."; return; }
int verifierCount;
if (!int.TryParse((VerifierCountText ?? string.Empty).Trim(), out verifierCount)) { ValidationMessage = "Количество поверителей должно быть целым числом."; return; }
decimal? value;
if (!TryParseNullableDecimal(CostText, out value)) { ValidationMessage = "Стоимость должна быть числом."; return; }
if (!TryParseNullableDecimal(ExtraCostText, out value)) { ValidationMessage = "Дополнительная стоимость должна быть числом."; return; }
if (!TryParseNullableDecimal(RushMarkupText, out value)) { ValidationMessage = "Наценка за срочность должна быть числом."; return; }
if (!TryParseNullableDecimal(TimeNormHoursText, out value)) { ValidationMessage = "Норма времени должна быть числом."; return; }
if (!TryParseNullableDecimal(NormDocHoursText, out value)) { ValidationMessage = "Норма времени по НД должна быть числом."; return; }
var timeNormCode = Normalize(TimeNormCode);
if (!string.IsNullOrWhiteSpace(timeNormCode) && timeNormCode.Length > TprmkDirectoryRules.TimeNormCodeMaxLength) { ValidationMessage = string.Format("Код нормы не должен превышать {0} символов.", TprmkDirectoryRules.TimeNormCodeMaxLength); return; }
var verificationCode = Normalize(VerificationCode);
if (!string.IsNullOrWhiteSpace(verificationCode) && verificationCode.Length > TprmkDirectoryRules.VerificationCodeMaxLength) { ValidationMessage = string.Format("Код поверки не должен превышать {0} символов.", TprmkDirectoryRules.VerificationCodeMaxLength); return; }
var duplicate = _existingItems.FirstOrDefault(delegate(TprmkDirectoryItem item)
{
return item != null && item.Id != Id && item.VerificationTypeId == VerificationTypeId && item.OrganizationId == OrganizationId && item.GroupId == (GroupId > 0 ? (int?)GroupId : null) && item.PlaceId == (PlaceId > 0 ? (int?)PlaceId : null);
});
if (duplicate != null) { ValidationMessage = "Такой регламент МК уже существует для выбранного типоразмера."; return; }
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private static IReadOnlyList<DirectoryLookupItem> WithEmpty(IReadOnlyList<DirectoryLookupItem> source)
{
return new[] { new DirectoryLookupItem { Id = 0, Name = string.Empty } }.Concat(source ?? Array.Empty<DirectoryLookupItem>()).ToList();
}
private static string Normalize(string value) { return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim(); }
private static string ToString(decimal? value) { return value.HasValue ? value.Value.ToString("0.##") : string.Empty; }
private static decimal? ParseNullableDecimal(string value) { decimal? parsed; return TryParseNullableDecimal(value, out parsed) ? parsed : (decimal?)null; }
private static bool TryParseNullableDecimal(string value, out decimal? result)
{
if (string.IsNullOrWhiteSpace(value)) { result = null; return true; }
decimal parsed;
if (decimal.TryParse(value.Trim(), out parsed)) { result = parsed; return true; }
result = null; return false;
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null) handler(this, dialogResult);
}
}
}

52
XLAB/TprzEditWindow.xaml Normal file
View File

@@ -0,0 +1,52 @@
<Window x:Class="XLAB.TprzEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="340"
Width="620"
MinHeight="320"
MinWidth="580"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<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 RangeText, 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 AccuracyText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Комплектность МК" />
<ComboBox Grid.Row="2" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding CompletenessItems}" SelectedValue="{Binding CompletenessId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<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" Text="{Binding RegistryTypeSizeNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="4" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Служебный код" />
<TextBox Grid.Row="4" Grid.Column="1" Margin="0,0,0,8" Text="{Binding ServiceCode, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<DockPanel Grid.Row="1" Margin="0,12,0,0">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Center" Foreground="Firebrick" Text="{Binding ValidationMessage}" />
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Width="100" Margin="0,0,8,0" IsDefault="True" Command="{Binding ConfirmCommand}" Content="Сохранить" />
<Button Width="90" Command="{Binding CancelCommand}" Content="Отмена" />
</StackPanel>
</DockPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,20 @@
using System.Windows;
namespace XLAB
{
public partial class TprzEditWindow : Window
{
internal TprzEditWindow(TprzEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB
{
internal sealed class TprzEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<TprzDirectoryItem> _existingItems;
private string _accuracyText;
private int _completenessId;
private string _rangeText;
private string _registryTypeSizeNumber;
private string _serviceCode;
private string _validationMessage;
public TprzEditWindowViewModel(TprzDirectoryItem seed, bool isNew, IReadOnlyList<TprzDirectoryItem> existingItems, TypeSizeDirectoryService service)
{
var source = seed ?? new TprzDirectoryItem();
_existingItems = existingItems ?? Array.Empty<TprzDirectoryItem>();
Id = source.Id;
TipsId = source.TipsId;
IsNew = isNew;
CompletenessItems = new[] { new DirectoryLookupItem { Id = 0, Name = string.Empty } }.Concat(service.LoadSpkmmkReferences()).ToList();
RangeText = source.RangeText ?? string.Empty;
AccuracyText = source.AccuracyText ?? string.Empty;
CompletenessId = source.CompletenessId ?? 0;
RegistryTypeSizeNumber = source.RegistryTypeSizeNumber ?? string.Empty;
ServiceCode = source.ServiceCode ?? string.Empty;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public IReadOnlyList<DirectoryLookupItem> CompletenessItems { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public int Id { get; private set; }
public bool IsNew { get; private set; }
public int TipsId { get; private set; }
public int CompletenessId { get { return _completenessId; } set { SetProperty(ref _completenessId, value); } }
public string RangeText { get { return _rangeText; } set { SetProperty(ref _rangeText, value); } }
public string AccuracyText { get { return _accuracyText; } set { SetProperty(ref _accuracyText, value); } }
public string RegistryTypeSizeNumber { get { return _registryTypeSizeNumber; } set { SetProperty(ref _registryTypeSizeNumber, value); } }
public string ServiceCode { get { return _serviceCode; } set { SetProperty(ref _serviceCode, value); } }
public string Title { get { return IsNew ? "Новый типоразмер" : "Редактирование типоразмера"; } }
public string ValidationMessage { get { return _validationMessage; } private set { SetProperty(ref _validationMessage, value); } }
public TprzDirectoryItem ToResult()
{
return new TprzDirectoryItem
{
Id = Id,
TipsId = TipsId,
CompletenessId = CompletenessId > 0 ? (int?)CompletenessId : null,
RangeText = Normalize(RangeText),
AccuracyText = Normalize(AccuracyText),
RegistryTypeSizeNumber = Normalize(RegistryTypeSizeNumber),
ServiceCode = Normalize(ServiceCode)
};
}
private void Cancel(object parameter) { RaiseCloseRequested(false); }
private void Confirm(object parameter)
{
var range = Normalize(RangeText);
var accuracy = Normalize(AccuracyText);
if (string.IsNullOrWhiteSpace(range)) { ValidationMessage = "Укажите диапазон."; return; }
if (range.Length > TprzDirectoryRules.RangeTextMaxLength) { ValidationMessage = string.Format("Диапазон не должен превышать {0} символов.", TprzDirectoryRules.RangeTextMaxLength); return; }
if (string.IsNullOrWhiteSpace(accuracy)) { ValidationMessage = "Укажите характеристику точности."; return; }
if (accuracy.Length > TprzDirectoryRules.AccuracyTextMaxLength) { ValidationMessage = string.Format("Характеристика точности не должна превышать {0} символов.", TprzDirectoryRules.AccuracyTextMaxLength); return; }
var registry = Normalize(RegistryTypeSizeNumber);
if (!string.IsNullOrWhiteSpace(registry) && registry.Length > TprzDirectoryRules.RegistryTypeSizeNumberMaxLength) { ValidationMessage = string.Format("Номер по Госреестру не должен превышать {0} символов.", TprzDirectoryRules.RegistryTypeSizeNumberMaxLength); return; }
var serviceCode = Normalize(ServiceCode);
if (!string.IsNullOrWhiteSpace(serviceCode) && serviceCode.Length > TprzDirectoryRules.ServiceCodeMaxLength) { ValidationMessage = string.Format("Служебный код не должен превышать {0} символов.", TprzDirectoryRules.ServiceCodeMaxLength); return; }
var duplicate = _existingItems.FirstOrDefault(delegate(TprzDirectoryItem item) { return item != null && item.Id != Id && string.Equals(Normalize(item.RangeText), range, StringComparison.OrdinalIgnoreCase) && string.Equals(Normalize(item.AccuracyText), accuracy, StringComparison.OrdinalIgnoreCase); });
if (duplicate != null) { ValidationMessage = string.Format("Типоразмер с диапазоном \"{0}\" и характеристикой точности \"{1}\" уже существует.", range, accuracy); return; }
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private static string Normalize(string value) { return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim(); }
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null) handler(this, dialogResult);
}
}
}

View File

@@ -0,0 +1,40 @@
<Window x:Class="XLAB.TpvdklEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="220"
Width="520"
MinHeight="210"
MinWidth="500"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Вид МК" />
<ComboBox Grid.Row="0" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding VerificationTypes}" SelectedValue="{Binding VerificationTypeId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Вид клейма" />
<ComboBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding StampKinds}" SelectedValue="{Binding StampKindId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
</Grid>
<DockPanel Grid.Row="1" Margin="0,12,0,0">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Center" Foreground="Firebrick" Text="{Binding ValidationMessage}" />
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Width="100" Margin="0,0,8,0" IsDefault="True" Command="{Binding ConfirmCommand}" Content="Сохранить" />
<Button Width="90" Command="{Binding CancelCommand}" Content="Отмена" />
</StackPanel>
</DockPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,20 @@
using System.Windows;
namespace XLAB
{
public partial class TpvdklEditWindow : Window
{
internal TpvdklEditWindow(TpvdklEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB
{
internal sealed class TpvdklEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<TpvdklDirectoryItem> _existingItems;
private int _stampKindId;
private string _validationMessage;
private int _verificationTypeId;
public TpvdklEditWindowViewModel(TpvdklDirectoryItem seed, bool isNew, IReadOnlyList<TpvdklDirectoryItem> existingItems, TypeSizeDirectoryService service)
{
var source = seed ?? new TpvdklDirectoryItem();
_existingItems = existingItems ?? Array.Empty<TpvdklDirectoryItem>();
Id = source.Id;
TipsId = source.TipsId;
IsNew = isNew;
VerificationTypes = service.LoadSpvdmkReferences();
StampKinds = service.LoadSpvdklReferences();
VerificationTypeId = source.VerificationTypeId;
StampKindId = source.StampKindId;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public int Id { get; private set; }
public bool IsNew { get; private set; }
public IReadOnlyList<DirectoryLookupItem> VerificationTypes { get; private set; }
public IReadOnlyList<DirectoryLookupItem> StampKinds { get; private set; }
public int TipsId { get; private set; }
public int VerificationTypeId { get { return _verificationTypeId; } set { SetProperty(ref _verificationTypeId, value); } }
public int StampKindId { get { return _stampKindId; } set { SetProperty(ref _stampKindId, value); } }
public string Title { get { return IsNew ? "Новый вид клейма" : "Редактирование вида клейма"; } }
public string ValidationMessage { get { return _validationMessage; } private set { SetProperty(ref _validationMessage, value); } }
public TpvdklDirectoryItem ToResult()
{
return new TpvdklDirectoryItem { Id = Id, TipsId = TipsId, VerificationTypeId = VerificationTypeId, StampKindId = StampKindId };
}
private void Cancel(object parameter) { RaiseCloseRequested(false); }
private void Confirm(object parameter)
{
if (VerificationTypeId <= 0) { ValidationMessage = "Укажите вид МК."; return; }
if (StampKindId <= 0) { ValidationMessage = "Укажите вид клейма."; return; }
var duplicate = _existingItems.FirstOrDefault(delegate(TpvdklDirectoryItem item) { return item != null && item.Id != Id && item.VerificationTypeId == VerificationTypeId && item.StampKindId == StampKindId; });
if (duplicate != null) { ValidationMessage = "Такая комбинация вида МК и вида клейма уже существует."; return; }
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null) handler(this, dialogResult);
}
}
}

68
XLAB/TpvdklWindow.xaml Normal file
View File

@@ -0,0 +1,68 @@
<Window x:Class="XLAB.TpvdklWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Виды клейм"
Height="420"
Width="720"
MinHeight="380"
MinWidth="660"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Margin="0,0,0,8"
FontWeight="SemiBold"
Text="{Binding Caption}" />
<DataGrid Grid.Row="1"
ItemsSource="{Binding Items}"
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 Header="Изменить"
Command="{Binding EditCommand}" />
<MenuItem Header="Удалить"
Command="{Binding DeleteCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="ID"
Width="70"
Binding="{Binding Id}" />
<DataGridTextColumn Header="Вид МК"
Width="220"
Binding="{Binding VerificationTypeName}" />
<DataGridTextColumn Header="Вид клейма"
Width="*"
Binding="{Binding StampKindName}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="2"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
IsCancel="True"
Content="Закрыть" />
</StackPanel>
</Grid>
</Window>

33
XLAB/TpvdklWindow.xaml.cs Normal file
View File

@@ -0,0 +1,33 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB
{
public partial class TpvdklWindow : Window
{
private readonly TpvdklWindowViewModel _viewModel;
internal TpvdklWindow(TipsDirectoryItem tipsItem, TypeSizeDirectoryService service)
{
InitializeComponent();
_viewModel = new TpvdklWindowViewModel(tipsItem, service, new TypeSizeDialogService(this));
DataContext = _viewModel;
}
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row != null)
{
row.IsSelected = true;
row.Focus();
}
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await _viewModel.InitializeAsync();
}
}
}

View File

@@ -0,0 +1,145 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace XLAB
{
internal sealed class TpvdklWindowViewModel : ObservableObject
{
private readonly ITypeSizeDialogService _dialogService;
private readonly TypeSizeDirectoryService _service;
private bool _isBusy;
private TpvdklDirectoryItem _selectedItem;
public TpvdklWindowViewModel(TipsDirectoryItem tipsItem, TypeSizeDirectoryService service, ITypeSizeDialogService dialogService)
{
TipsItem = tipsItem;
_service = service;
_dialogService = dialogService;
Items = new ObservableCollection<TpvdklDirectoryItem>();
AddCommand = new RelayCommand(delegate { AddAsync(); }, delegate { return !IsBusy; });
EditCommand = new RelayCommand(delegate { EditAsync(); }, delegate { return !IsBusy && SelectedItem != null; });
DeleteCommand = new RelayCommand(delegate { DeleteAsync(); }, delegate { return !IsBusy && SelectedItem != null; });
}
public ICommand AddCommand { get; private set; }
public string Caption { get { return string.Format("Виды клейм для типа СИ: {0}", TipsItem == null ? string.Empty : TipsItem.TypeName); } }
public ICommand DeleteCommand { get; private set; }
public ICommand EditCommand { get; private set; }
public ObservableCollection<TpvdklDirectoryItem> Items { get; private set; }
public TipsDirectoryItem TipsItem { get; private set; }
public bool IsBusy
{
get { return _isBusy; }
private set
{
if (SetProperty(ref _isBusy, value))
{
((RelayCommand)AddCommand).RaiseCanExecuteChanged();
((RelayCommand)EditCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteCommand).RaiseCanExecuteChanged();
}
}
}
public TpvdklDirectoryItem SelectedItem
{
get { return _selectedItem; }
set
{
if (SetProperty(ref _selectedItem, value))
{
((RelayCommand)EditCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteCommand).RaiseCanExecuteChanged();
}
}
}
public async Task InitializeAsync()
{
await RefreshCoreAsync(null);
}
private void AddAsync()
{
var result = _dialogService.ShowTpvdklEditDialog(new TpvdklDirectoryItem { TipsId = TipsItem.Id }, true, Items.ToList(), _service);
if (result == null) return;
RunMutation(async delegate
{
var id = await Task.Run(delegate { return _service.AddTpvdklItem(result); });
await RefreshCoreAsync(id);
_dialogService.ShowInfo("Вид клейма добавлен.");
});
}
private void EditAsync()
{
if (SelectedItem == null) return;
var result = _dialogService.ShowTpvdklEditDialog(new TpvdklDirectoryItem { Id = SelectedItem.Id, TipsId = SelectedItem.TipsId, VerificationTypeId = SelectedItem.VerificationTypeId, StampKindId = SelectedItem.StampKindId }, false, Items.ToList(), _service);
if (result == null) return;
RunMutation(async delegate
{
await Task.Run(delegate { _service.UpdateTpvdklItem(result); });
await RefreshCoreAsync(result.Id);
_dialogService.ShowInfo("Вид клейма обновлён.");
});
}
private void DeleteAsync()
{
if (SelectedItem == null) return;
var selected = SelectedItem;
if (!_dialogService.Confirm(string.Format("Удалить связь \"{0} / {1}\"?", selected.VerificationTypeName, selected.StampKindName))) return;
RunMutation(async delegate
{
await Task.Run(delegate { _service.DeleteTpvdklItem(selected.Id); });
await RefreshCoreAsync(null);
_dialogService.ShowInfo("Вид клейма удалён.");
});
}
private async Task RefreshCoreAsync(int? idToSelect)
{
try
{
IsBusy = true;
var items = await Task.Run(delegate { return _service.LoadTpvdklItems(TipsItem.Id); });
Items.Clear();
foreach (var item in items) Items.Add(item);
SelectedItem = idToSelect.HasValue ? Items.FirstOrDefault(delegate(TpvdklDirectoryItem item) { return item.Id == idToSelect.Value; }) : Items.FirstOrDefault();
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
}
finally
{
IsBusy = false;
}
}
private async void RunMutation(Func<Task> operation)
{
try
{
IsBusy = true;
await operation();
}
catch (InvalidOperationException ex)
{
_dialogService.ShowWarning(ex.Message);
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
}
finally
{
IsBusy = false;
}
}
}
}

View File

@@ -0,0 +1,115 @@
using System.Collections.Generic;
using System.Windows;
namespace XLAB
{
internal interface ITypeSizeDialogService
{
TipsDirectoryItem ShowTipsEditDialog(TipsDirectoryItem seed, bool isNew, IReadOnlyList<TipsDirectoryItem> existingItems, TypeSizeDirectoryService service);
TprzDirectoryItem ShowTprzEditDialog(TprzDirectoryItem seed, bool isNew, IReadOnlyList<TprzDirectoryItem> existingItems, TypeSizeDirectoryService service);
TprmcpDirectoryItem ShowTprmcpEditDialog(TprmcpDirectoryItem seed, bool isNew, IReadOnlyList<TprmcpDirectoryItem> existingItems, TypeSizeDirectoryService service);
TprmkDirectoryItem ShowTprmkEditDialog(TprmkDirectoryItem seed, bool isNew, IReadOnlyList<TprmkDirectoryItem> existingItems, TypeSizeDirectoryService service);
TpvdklDirectoryItem ShowTpvdklEditDialog(TpvdklDirectoryItem seed, bool isNew, IReadOnlyList<TpvdklDirectoryItem> existingItems, TypeSizeDirectoryService service);
void ShowTpvdklManagerDialog(TipsDirectoryItem tipsItem, TypeSizeDirectoryService service);
bool Confirm(string message);
void ShowError(string message);
void ShowInfo(string message);
void ShowWarning(string message);
}
internal sealed class TypeSizeDialogService : ITypeSizeDialogService
{
private readonly Window _owner;
public TypeSizeDialogService(Window owner)
{
_owner = owner;
}
public TipsDirectoryItem ShowTipsEditDialog(TipsDirectoryItem seed, bool isNew, IReadOnlyList<TipsDirectoryItem> existingItems, TypeSizeDirectoryService service)
{
var viewModel = new TipsEditWindowViewModel(seed, isNew, existingItems, service);
var window = new TipsEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public TprzDirectoryItem ShowTprzEditDialog(TprzDirectoryItem seed, bool isNew, IReadOnlyList<TprzDirectoryItem> existingItems, TypeSizeDirectoryService service)
{
var viewModel = new TprzEditWindowViewModel(seed, isNew, existingItems, service);
var window = new TprzEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public TprmcpDirectoryItem ShowTprmcpEditDialog(TprmcpDirectoryItem seed, bool isNew, IReadOnlyList<TprmcpDirectoryItem> existingItems, TypeSizeDirectoryService service)
{
var viewModel = new TprmcpEditWindowViewModel(seed, isNew, existingItems, service);
var window = new TprmcpEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public TprmkDirectoryItem ShowTprmkEditDialog(TprmkDirectoryItem seed, bool isNew, IReadOnlyList<TprmkDirectoryItem> existingItems, TypeSizeDirectoryService service)
{
var viewModel = new TprmkEditWindowViewModel(seed, isNew, existingItems, service);
var window = new TprmkEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public TpvdklDirectoryItem ShowTpvdklEditDialog(TpvdklDirectoryItem seed, bool isNew, IReadOnlyList<TpvdklDirectoryItem> existingItems, TypeSizeDirectoryService service)
{
var viewModel = new TpvdklEditWindowViewModel(seed, isNew, existingItems, service);
var window = new TpvdklEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public void ShowTpvdklManagerDialog(TipsDirectoryItem tipsItem, TypeSizeDirectoryService service)
{
var window = new TpvdklWindow(tipsItem, service);
window.Owner = _owner;
window.ShowDialog();
}
public bool Confirm(string message)
{
return MessageBox.Show(_owner, message, "ПСВ", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
}
public void ShowError(string message)
{
MessageBox.Show(_owner, message, "ПСВ", MessageBoxButton.OK, MessageBoxImage.Error);
}
public void ShowInfo(string message)
{
MessageBox.Show(_owner, message, "ПСВ", MessageBoxButton.OK, MessageBoxImage.Information);
}
public void ShowWarning(string message)
{
MessageBox.Show(_owner, message, "ПСВ", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
}

View File

@@ -0,0 +1,191 @@
using System;
namespace XLAB
{
public sealed class DirectoryLookupItem
{
public int Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name ?? string.Empty;
}
}
public sealed class TipsDirectoryItem
{
public int Id { get; set; }
public int MeasurementAreaId { get; set; }
public string MeasurementAreaName { get; set; }
public int InstrumentNameId { get; set; }
public string InstrumentName { get; set; }
public int? CategoryId { get; set; }
public string CategoryName { get; set; }
public int? DesignId { get; set; }
public string DesignName { get; set; }
public string TypeName { get; set; }
public int? ServiceLifeYears { get; set; }
public int? RegistryPeriodMonths { get; set; }
public string RegistryTypeNumber { get; set; }
public int? VniimsTypeCode { get; set; }
public string MetrControlCode { get; set; }
public string Notes { get; set; }
public bool? IsSpecialPurpose { get; set; }
public bool? IsMkPrimaryOnly { get; set; }
}
public sealed class TprzDirectoryItem
{
public int Id { get; set; }
public int TipsId { get; set; }
public int? CompletenessId { get; set; }
public string CompletenessName { get; set; }
public string RangeText { get; set; }
public string AccuracyText { get; set; }
public int? VniimsTypeSizeCode { get; set; }
public string RegistryTypeSizeNumber { get; set; }
public string ServiceCode { get; set; }
}
public sealed class TprmcpDirectoryItem
{
public int Id { get; set; }
public int TypeSizeId { get; set; }
public int? CycleId { get; set; }
public string CycleName { get; set; }
public int? GroupId { get; set; }
public string GroupName { get; set; }
public int PeriodMonths { get; set; }
public string Comment { get; set; }
}
public sealed class TprmkDirectoryItem
{
public int Id { get; set; }
public int TypeSizeId { get; set; }
public int VerificationTypeId { get; set; }
public string VerificationTypeName { get; set; }
public int OrganizationId { get; set; }
public string OrganizationName { get; set; }
public int? QualificationId { get; set; }
public string QualificationName { get; set; }
public int? GroupId { get; set; }
public string GroupName { get; set; }
public int? PlaceId { get; set; }
public string PlaceName { get; set; }
public decimal? Cost { get; set; }
public decimal? ExtraCost { get; set; }
public decimal? RushMarkup { get; set; }
public decimal? TimeNormHours { get; set; }
public string TimeNormCode { get; set; }
public int VerifierCount { get; set; }
public string VerificationCode { get; set; }
public decimal? NormDocHours { get; set; }
}
public sealed class TpvdklDirectoryItem
{
public int Id { get; set; }
public int TipsId { get; set; }
public int VerificationTypeId { get; set; }
public string VerificationTypeName { get; set; }
public int StampKindId { get; set; }
public string StampKindName { get; set; }
}
internal sealed class DirectoryDeleteResult
{
public bool IsDeleted { get; set; }
public string WarningMessage { get; set; }
}
internal static class TipsDirectoryRules
{
public const int TypeNameMaxLength = 160;
public const int RegistryTypeNumberMaxLength = 8;
public const int MetrControlCodeMaxLength = 11;
}
internal static class TprzDirectoryRules
{
public const int RangeTextMaxLength = 160;
public const int AccuracyTextMaxLength = 140;
public const int RegistryTypeSizeNumberMaxLength = 8;
public const int ServiceCodeMaxLength = 10;
}
internal static class TprmcpDirectoryRules
{
}
internal static class TprmkDirectoryRules
{
public const int TimeNormCodeMaxLength = 7;
public const int VerificationCodeMaxLength = 15;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,199 @@
<Window x:Class="XLAB.TypeSizeDirectoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Типоразмеры СИ"
Height="900"
Width="1500"
MinHeight="760"
MinWidth="1220"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="2.1*" />
<RowDefinition Height="2*" />
<RowDefinition Height="2.1*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0"
Margin="0,0,0,12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<Button DockPanel.Dock="Right"
Width="110"
Margin="12,0,0,0"
Command="{Binding RefreshCommand}"
Content="Обновить" />
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,0,8,0"
VerticalAlignment="Center"
Text="Поиск по TIPS и TPRZ:" />
<TextBox Width="360"
VerticalAlignment="Center"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DockPanel>
<TextBlock Grid.Row="1"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
</Grid>
<GroupBox Grid.Row="1"
Header="Типы СИ (TIPS)">
<DataGrid ItemsSource="{Binding TipsItems}"
SelectedItem="{Binding SelectedTips, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddTipsCommand}" />
<MenuItem Header="Изменить"
Command="{Binding EditTipsCommand}" />
<MenuItem Header="Удалить"
Command="{Binding DeleteTipsCommand}" />
<Separator />
<MenuItem Header="Виды клейм..."
Command="{Binding ManageTpvdklCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="70" Binding="{Binding Id}" />
<DataGridTextColumn Header="Область измерений" Width="260" Binding="{Binding MeasurementAreaName}" />
<DataGridTextColumn Header="Наименование типа СИ" Width="260" Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип СИ" Width="*" Binding="{Binding TypeName}" />
<DataGridTextColumn Header="Категория" Width="180" Binding="{Binding CategoryName}" />
<DataGridTextColumn Header="Исполнение" Width="160" Binding="{Binding DesignName}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<GroupBox Grid.Row="2"
Margin="0,12,0,0"
Header="Типоразмеры СИ (TPRZ)">
<DataGrid ItemsSource="{Binding TprzItems}"
SelectedItem="{Binding SelectedTprz, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" Command="{Binding AddTprzCommand}" />
<MenuItem Header="Изменить" Command="{Binding EditTprzCommand}" />
<MenuItem Header="Удалить" Command="{Binding DeleteTprzCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="70" Binding="{Binding Id}" />
<DataGridTextColumn Header="Диапазон" Width="*" Binding="{Binding RangeText}" />
<DataGridTextColumn Header="Х-ка точности" Width="260" Binding="{Binding AccuracyText}" />
<DataGridTextColumn Header="Комплектность МК" Width="220" Binding="{Binding CompletenessName}" />
<DataGridTextColumn Header="№ Госреестра" Width="130" Binding="{Binding RegistryTypeSizeNumber}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<Grid Grid.Row="3"
Margin="0,12,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="12" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<GroupBox Grid.Column="0"
Header="Регламент МК для типоразмера СИ (TPRMK)">
<DataGrid ItemsSource="{Binding TprmkItems}"
SelectedItem="{Binding SelectedTprmk, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" Command="{Binding AddTprmkCommand}" />
<MenuItem Header="Изменить" Command="{Binding EditTprmkCommand}" />
<MenuItem Header="Удалить" Command="{Binding DeleteTprmkCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="70" Binding="{Binding Id}" />
<DataGridTextColumn Header="Вид МК" Width="130" Binding="{Binding VerificationTypeName}" />
<DataGridTextColumn Header="Организация" Width="*" Binding="{Binding OrganizationName}" />
<DataGridTextColumn Header="Место МК" Width="170" Binding="{Binding PlaceName}" />
<DataGridTextColumn Header="Группа СИ" Width="220" Binding="{Binding GroupName}" />
<DataGridTextColumn Header="Кол-во повер." Width="110" Binding="{Binding VerifierCount}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<GroupBox Grid.Column="2"
Header="Циклы и периоды МК (TPRMCP)">
<DataGrid ItemsSource="{Binding TprmcpItems}"
SelectedItem="{Binding SelectedTprmcp, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" Command="{Binding AddTprmcpCommand}" />
<MenuItem Header="Изменить" Command="{Binding EditTprmcpCommand}" />
<MenuItem Header="Удалить" Command="{Binding DeleteTprmcpCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="70" Binding="{Binding Id}" />
<DataGridTextColumn Header="Цикл МК" Width="180" Binding="{Binding CycleName}" />
<DataGridTextColumn Header="Группа СИ" Width="*" Binding="{Binding GroupName}" />
<DataGridTextColumn Header="Период, мес." Width="110" Binding="{Binding PeriodMonths}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
</Grid>
<StackPanel Grid.Row="4"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
IsCancel="True"
Content="Закрыть" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,33 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB
{
public partial class TypeSizeDirectoryWindow : Window
{
private readonly TypeSizeDirectoryWindowViewModel _viewModel;
public TypeSizeDirectoryWindow()
{
InitializeComponent();
_viewModel = new TypeSizeDirectoryWindowViewModel(new TypeSizeDirectoryService(), new TypeSizeDialogService(this));
DataContext = _viewModel;
}
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row != null)
{
row.IsSelected = true;
row.Focus();
}
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await _viewModel.InitializeAsync();
}
}
}

View File

@@ -0,0 +1,664 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace XLAB
{
internal sealed class TypeSizeDirectoryWindowViewModel : ObservableObject
{
private readonly ITypeSizeDialogService _dialogService;
private readonly TypeSizeDirectoryService _service;
private readonly Dictionary<int, List<TprzDirectoryItem>> _tprzSearchIndex;
private List<TipsDirectoryItem> _tipsCache;
private List<TprzDirectoryItem> _currentTprzCache;
private bool _isBusy;
private string _searchText;
private TipsDirectoryItem _selectedTips;
private TprmcpDirectoryItem _selectedTprmcp;
private TprmkDirectoryItem _selectedTprmk;
private TprzDirectoryItem _selectedTprz;
private string _statusText;
public TypeSizeDirectoryWindowViewModel(TypeSizeDirectoryService service, ITypeSizeDialogService dialogService)
{
_service = service;
_dialogService = dialogService;
_tipsCache = new List<TipsDirectoryItem>();
_currentTprzCache = new List<TprzDirectoryItem>();
_tprzSearchIndex = new Dictionary<int, List<TprzDirectoryItem>>();
TipsItems = new ObservableCollection<TipsDirectoryItem>();
TprzItems = new ObservableCollection<TprzDirectoryItem>();
TprmcpItems = new ObservableCollection<TprmcpDirectoryItem>();
TprmkItems = new ObservableCollection<TprmkDirectoryItem>();
AddTipsCommand = new RelayCommand(delegate { AddTipsAsync(); }, delegate { return !IsBusy; });
EditTipsCommand = new RelayCommand(delegate { EditTipsAsync(); }, delegate { return !IsBusy && SelectedTips != null; });
DeleteTipsCommand = new RelayCommand(delegate { DeleteTipsAsync(); }, delegate { return !IsBusy && SelectedTips != null; });
ManageTpvdklCommand = new RelayCommand(delegate { ManageTpvdkl(); }, delegate { return !IsBusy && SelectedTips != null; });
AddTprzCommand = new RelayCommand(delegate { AddTprzAsync(); }, delegate { return !IsBusy && SelectedTips != null; });
EditTprzCommand = new RelayCommand(delegate { EditTprzAsync(); }, delegate { return !IsBusy && SelectedTprz != null; });
DeleteTprzCommand = new RelayCommand(delegate { DeleteTprzAsync(); }, delegate { return !IsBusy && SelectedTprz != null; });
AddTprmcpCommand = new RelayCommand(delegate { AddTprmcpAsync(); }, delegate { return !IsBusy && SelectedTprz != null; });
EditTprmcpCommand = new RelayCommand(delegate { EditTprmcpAsync(); }, delegate { return !IsBusy && SelectedTprmcp != null; });
DeleteTprmcpCommand = new RelayCommand(delegate { DeleteTprmcpAsync(); }, delegate { return !IsBusy && SelectedTprmcp != null; });
AddTprmkCommand = new RelayCommand(delegate { AddTprmkAsync(); }, delegate { return !IsBusy && SelectedTprz != null; });
EditTprmkCommand = new RelayCommand(delegate { EditTprmkAsync(); }, delegate { return !IsBusy && SelectedTprmk != null; });
DeleteTprmkCommand = new RelayCommand(delegate { DeleteTprmkAsync(); }, delegate { return !IsBusy && SelectedTprmk != null; });
RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });
UpdateStatus();
}
public ObservableCollection<TipsDirectoryItem> TipsItems { get; private set; }
public ObservableCollection<TprzDirectoryItem> TprzItems { get; private set; }
public ObservableCollection<TprmcpDirectoryItem> TprmcpItems { get; private set; }
public ObservableCollection<TprmkDirectoryItem> TprmkItems { get; private set; }
public ICommand AddTipsCommand { get; private set; }
public ICommand EditTipsCommand { get; private set; }
public ICommand DeleteTipsCommand { get; private set; }
public ICommand ManageTpvdklCommand { get; private set; }
public ICommand AddTprzCommand { get; private set; }
public ICommand EditTprzCommand { get; private set; }
public ICommand DeleteTprzCommand { get; private set; }
public ICommand AddTprmcpCommand { get; private set; }
public ICommand EditTprmcpCommand { get; private set; }
public ICommand DeleteTprmcpCommand { get; private set; }
public ICommand AddTprmkCommand { get; private set; }
public ICommand EditTprmkCommand { get; private set; }
public ICommand DeleteTprmkCommand { get; private set; }
public ICommand RefreshCommand { get; private set; }
public bool IsBusy
{
get { return _isBusy; }
private set
{
if (SetProperty(ref _isBusy, value))
{
RaiseCommandStates();
}
}
}
public TipsDirectoryItem SelectedTips
{
get { return _selectedTips; }
set
{
if (SetProperty(ref _selectedTips, value))
{
RaiseCommandStates();
LoadTprzForSelection();
UpdateStatus();
}
}
}
public TprzDirectoryItem SelectedTprz
{
get { return _selectedTprz; }
set
{
if (SetProperty(ref _selectedTprz, value))
{
RaiseCommandStates();
LoadLeafTablesForSelection();
UpdateStatus();
}
}
}
public TprmcpDirectoryItem SelectedTprmcp
{
get { return _selectedTprmcp; }
set
{
if (SetProperty(ref _selectedTprmcp, value))
{
RaiseCommandStates();
UpdateStatus();
}
}
}
public TprmkDirectoryItem SelectedTprmk
{
get { return _selectedTprmk; }
set
{
if (SetProperty(ref _selectedTprmk, value))
{
RaiseCommandStates();
UpdateStatus();
}
}
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public string SearchText
{
get { return _searchText; }
set
{
if (SetProperty(ref _searchText, value))
{
ApplySearchFilter();
UpdateStatus();
}
}
}
public async Task InitializeAsync()
{
await ExecuteBusyOperationAsync(async delegate { await RefreshTipsCoreAsync(null); });
}
private void AddTipsAsync()
{
var result = _dialogService.ShowTipsEditDialog(new TipsDirectoryItem(), true, _tipsCache.ToList(), _service);
if (result == null) return;
RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _service.AddTipsItem(result); });
await RefreshTipsCoreAsync(createdId);
_dialogService.ShowInfo("Запись TIPS добавлена.");
});
}
private void EditTipsAsync()
{
if (SelectedTips == null) return;
var result = _dialogService.ShowTipsEditDialog(CloneTips(SelectedTips), false, _tipsCache.ToList(), _service);
if (result == null) return;
RunMutationOperation(async delegate
{
await Task.Run(delegate { _service.UpdateTipsItem(result); });
await RefreshTipsCoreAsync(result.Id);
_dialogService.ShowInfo("Запись TIPS обновлена.");
});
}
private void DeleteTipsAsync()
{
if (SelectedTips == null) return;
var selected = SelectedTips;
if (!_dialogService.Confirm(string.Format("Удалить тип СИ \"{0}\"?", selected.TypeName))) return;
RunMutationOperation(async delegate
{
var result = await Task.Run(delegate { return _service.DeleteTipsItem(selected.Id); });
if (!result.IsDeleted) { _dialogService.ShowWarning(result.WarningMessage); return; }
await RefreshTipsCoreAsync(null);
_dialogService.ShowInfo("Запись TIPS удалена.");
});
}
private void ManageTpvdkl()
{
if (SelectedTips != null)
{
_dialogService.ShowTpvdklManagerDialog(CloneTips(SelectedTips), _service);
}
}
private void AddTprzAsync()
{
if (SelectedTips == null) return;
var result = _dialogService.ShowTprzEditDialog(new TprzDirectoryItem { TipsId = SelectedTips.Id }, true, _currentTprzCache.ToList(), _service);
if (result == null) return;
RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _service.AddTprzItem(result); });
await RefreshTipsCoreAsync(result.TipsId);
if (SelectedTips != null && SelectedTips.Id == result.TipsId && SelectedTprz != null && SelectedTprz.Id != createdId)
{
await RefreshTprzCoreAsync(result.TipsId, createdId);
}
_dialogService.ShowInfo("Запись TPRZ добавлена.");
});
}
private void EditTprzAsync()
{
if (SelectedTprz == null) return;
var result = _dialogService.ShowTprzEditDialog(CloneTprz(SelectedTprz), false, _currentTprzCache.ToList(), _service);
if (result == null) return;
RunMutationOperation(async delegate
{
await Task.Run(delegate { _service.UpdateTprzItem(result); });
await RefreshTipsCoreAsync(result.TipsId);
if (SelectedTips != null && SelectedTips.Id == result.TipsId && SelectedTprz != null && SelectedTprz.Id != result.Id)
{
await RefreshTprzCoreAsync(result.TipsId, result.Id);
}
_dialogService.ShowInfo("Запись TPRZ обновлена.");
});
}
private void DeleteTprzAsync()
{
if (SelectedTprz == null) return;
var selected = SelectedTprz;
if (!_dialogService.Confirm(string.Format("Удалить типоразмер \"{0}\"?", selected.RangeText))) return;
RunMutationOperation(async delegate
{
var result = await Task.Run(delegate { return _service.DeleteTprzItem(selected.Id); });
if (!result.IsDeleted) { _dialogService.ShowWarning(result.WarningMessage); return; }
await RefreshTipsCoreAsync(selected.TipsId);
_dialogService.ShowInfo("Запись TPRZ удалена.");
});
}
private void AddTprmcpAsync()
{
if (SelectedTprz == null) return;
if (TprmkItems.Count == 0)
{
_dialogService.ShowWarning("Сначала заполните таблицу TPRMK для выбранного типоразмера, затем добавляйте цикл и период в TPRMCP.");
return;
}
var result = _dialogService.ShowTprmcpEditDialog(new TprmcpDirectoryItem { TypeSizeId = SelectedTprz.Id }, true, TprmcpItems.ToList(), _service);
if (result == null) return;
RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _service.AddTprmcpItem(result); });
await RefreshLeafTablesCoreAsync(SelectedTprz.Id, createdId, null);
_dialogService.ShowInfo("Запись TPRMCP добавлена.");
});
}
private void EditTprmcpAsync()
{
if (SelectedTprmcp == null) return;
var result = _dialogService.ShowTprmcpEditDialog(CloneTprmcp(SelectedTprmcp), false, TprmcpItems.ToList(), _service);
if (result == null) return;
RunMutationOperation(async delegate
{
await Task.Run(delegate { _service.UpdateTprmcpItem(result); });
await RefreshLeafTablesCoreAsync(result.TypeSizeId, result.Id, null);
_dialogService.ShowInfo("Запись TPRMCP обновлена.");
});
}
private void DeleteTprmcpAsync()
{
if (SelectedTprmcp == null) return;
var selected = SelectedTprmcp;
if (!_dialogService.Confirm(string.Format("Удалить период МК \"{0}\" мес.?", selected.PeriodMonths))) return;
RunMutationOperation(async delegate
{
var result = await Task.Run(delegate { return _service.DeleteTprmcpItem(selected.Id); });
if (!result.IsDeleted) { _dialogService.ShowWarning(result.WarningMessage); return; }
await RefreshLeafTablesCoreAsync(selected.TypeSizeId, null, null);
_dialogService.ShowInfo("Запись TPRMCP удалена.");
});
}
private void AddTprmkAsync()
{
if (SelectedTprz == null) return;
var result = _dialogService.ShowTprmkEditDialog(new TprmkDirectoryItem { TypeSizeId = SelectedTprz.Id }, true, TprmkItems.ToList(), _service);
if (result == null) return;
RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _service.AddTprmkItem(result); });
await RefreshLeafTablesCoreAsync(SelectedTprz.Id, null, createdId);
_dialogService.ShowInfo("Запись TPRMK добавлена.");
});
}
private void EditTprmkAsync()
{
if (SelectedTprmk == null) return;
var result = _dialogService.ShowTprmkEditDialog(CloneTprmk(SelectedTprmk), false, TprmkItems.ToList(), _service);
if (result == null) return;
RunMutationOperation(async delegate
{
await Task.Run(delegate { _service.UpdateTprmkItem(result); });
await RefreshLeafTablesCoreAsync(result.TypeSizeId, null, result.Id);
_dialogService.ShowInfo("Запись TPRMK обновлена.");
});
}
private void DeleteTprmkAsync()
{
if (SelectedTprmk == null) return;
var selected = SelectedTprmk;
if (!_dialogService.Confirm(string.Format("Удалить регламент МК \"{0}\"?", selected.VerificationTypeName))) return;
RunMutationOperation(async delegate
{
var result = await Task.Run(delegate { return _service.DeleteTprmkItem(selected.Id); });
if (!result.IsDeleted) { _dialogService.ShowWarning(result.WarningMessage); return; }
await RefreshLeafTablesCoreAsync(selected.TypeSizeId, null, null);
_dialogService.ShowInfo("Запись TPRMK удалена.");
});
}
private async Task ExecuteBusyOperationAsync(Func<Task> operation)
{
try { IsBusy = true; await operation(); }
catch (Exception ex) { _dialogService.ShowError(ex.Message); }
finally { IsBusy = false; }
}
private async Task ExecuteMutationOperationAsync(Func<Task> operation)
{
try { IsBusy = true; await operation(); }
catch (InvalidOperationException ex) { _dialogService.ShowWarning(ex.Message); }
catch (Exception ex) { _dialogService.ShowError(ex.Message); }
finally { IsBusy = false; }
}
private async Task RefreshTipsCoreAsync(int? tipsIdToSelect)
{
var currentTipsId = SelectedTips == null ? (int?)null : SelectedTips.Id;
var currentTprzId = SelectedTprz == null ? (int?)null : SelectedTprz.Id;
var tipsTask = Task.Run(delegate { return _service.LoadTipsItems(); });
var tprzSearchTask = Task.Run(delegate { return _service.LoadTprzSearchItems(); });
await Task.WhenAll(tipsTask, tprzSearchTask);
_tipsCache = tipsTask.Result.ToList();
_tprzSearchIndex.Clear();
foreach (var group in tprzSearchTask.Result.GroupBy(delegate(TprzDirectoryItem item) { return item.TipsId; }))
{
_tprzSearchIndex[group.Key] = group.ToList();
}
ApplyTipsFilter(tipsIdToSelect.HasValue ? tipsIdToSelect : currentTipsId);
if (SelectedTips == null)
{
_currentTprzCache = new List<TprzDirectoryItem>();
TprzItems.Clear();
TprmcpItems.Clear();
TprmkItems.Clear();
SelectedTprz = null;
SelectedTprmcp = null;
SelectedTprmk = null;
UpdateStatus();
return;
}
var tprzIdToSelect = currentTipsId.HasValue && currentTipsId.Value == SelectedTips.Id ? currentTprzId : null;
await RefreshTprzCoreAsync(SelectedTips.Id, tprzIdToSelect);
UpdateStatus();
}
private async Task RefreshTprzCoreAsync(int tipsId, int? tprzIdToSelect)
{
var currentTprzId = SelectedTprz == null ? (int?)null : SelectedTprz.Id;
var currentTprmcpId = SelectedTprmcp == null ? (int?)null : SelectedTprmcp.Id;
var currentTprmkId = SelectedTprmk == null ? (int?)null : SelectedTprmk.Id;
var rows = await Task.Run(delegate { return _service.LoadTprzItems(tipsId); });
_currentTprzCache = rows.ToList();
_tprzSearchIndex[tipsId] = _currentTprzCache.ToList();
ApplyTprzFilter(tprzIdToSelect.HasValue ? tprzIdToSelect : currentTprzId);
if (SelectedTprz == null)
{
TprmcpItems.Clear();
TprmkItems.Clear();
SelectedTprmcp = null;
SelectedTprmk = null;
UpdateStatus();
return;
}
var tprmcpIdToSelect = currentTprzId.HasValue && currentTprzId.Value == SelectedTprz.Id ? currentTprmcpId : null;
var tprmkIdToSelect = currentTprzId.HasValue && currentTprzId.Value == SelectedTprz.Id ? currentTprmkId : null;
await RefreshLeafTablesCoreAsync(SelectedTprz.Id, tprmcpIdToSelect, tprmkIdToSelect);
UpdateStatus();
}
private async Task RefreshLeafTablesCoreAsync(int typeSizeId, int? tprmcpIdToSelect, int? tprmkIdToSelect)
{
var tprmkTask = Task.Run(delegate { return _service.LoadTprmkItems(typeSizeId); });
var tprmcpTask = Task.Run(delegate { return _service.LoadTprmcpItems(typeSizeId); });
await Task.WhenAll(tprmcpTask, tprmkTask);
TprmkItems.Clear();
foreach (var item in tprmkTask.Result) TprmkItems.Add(item);
SelectedTprmk = tprmkIdToSelect.HasValue ? TprmkItems.FirstOrDefault(delegate(TprmkDirectoryItem item) { return item.Id == tprmkIdToSelect.Value; }) : TprmkItems.FirstOrDefault();
TprmcpItems.Clear();
foreach (var item in tprmcpTask.Result) TprmcpItems.Add(item);
SelectedTprmcp = tprmcpIdToSelect.HasValue ? TprmcpItems.FirstOrDefault(delegate(TprmcpDirectoryItem item) { return item.Id == tprmcpIdToSelect.Value; }) : TprmcpItems.FirstOrDefault();
UpdateStatus();
}
private void ApplySearchFilter()
{
if (IsBusy)
{
return;
}
var previousTipsId = SelectedTips == null ? (int?)null : SelectedTips.Id;
var previousTprzId = SelectedTprz == null ? (int?)null : SelectedTprz.Id;
ApplyTipsFilter(previousTipsId);
if (SelectedTips == null)
{
_currentTprzCache = new List<TprzDirectoryItem>();
TprzItems.Clear();
TprmcpItems.Clear();
TprmkItems.Clear();
SelectedTprz = null;
SelectedTprmcp = null;
SelectedTprmk = null;
return;
}
if (previousTipsId.HasValue && previousTipsId.Value == SelectedTips.Id)
{
ApplyTprzFilter(previousTprzId);
}
}
private void ApplyTipsFilter(int? preferredTipsId)
{
var filteredItems = _tipsCache
.Where(delegate(TipsDirectoryItem item) { return MatchesTipsSearch(item); })
.ToList();
TipsItems.Clear();
foreach (var item in filteredItems)
{
TipsItems.Add(item);
}
SelectedTips = preferredTipsId.HasValue
? TipsItems.FirstOrDefault(delegate(TipsDirectoryItem item) { return item.Id == preferredTipsId.Value; })
: TipsItems.FirstOrDefault();
}
private void ApplyTprzFilter(int? preferredTprzId)
{
var selectedTips = SelectedTips;
var showAllTypeSizes = selectedTips != null && MatchesTipsOwnFields(selectedTips);
var filteredItems = _currentTprzCache
.Where(delegate(TprzDirectoryItem item) { return showAllTypeSizes || MatchesTprzOwnFields(item); })
.ToList();
TprzItems.Clear();
foreach (var item in filteredItems)
{
TprzItems.Add(item);
}
SelectedTprz = preferredTprzId.HasValue
? TprzItems.FirstOrDefault(delegate(TprzDirectoryItem item) { return item.Id == preferredTprzId.Value; })
: TprzItems.FirstOrDefault();
}
private bool MatchesTipsSearch(TipsDirectoryItem item)
{
if (item == null)
{
return false;
}
if (IsSearchEmpty())
{
return true;
}
if (MatchesTipsOwnFields(item))
{
return true;
}
List<TprzDirectoryItem> typeSizes;
return _tprzSearchIndex.TryGetValue(item.Id, out typeSizes)
&& typeSizes.Any(delegate(TprzDirectoryItem typeSize) { return MatchesTprzOwnFields(typeSize); });
}
private bool MatchesTipsOwnFields(TipsDirectoryItem item)
{
return MatchesSearchTokens(
item == null ? null : item.Id.ToString(),
item == null ? null : item.MeasurementAreaName,
item == null ? null : item.InstrumentName,
item == null ? null : item.TypeName,
item == null ? null : item.CategoryName,
item == null ? null : item.DesignName,
item == null ? null : item.RegistryTypeNumber,
item == null ? null : item.VniimsTypeCode.ToString(),
item == null ? null : item.MetrControlCode,
item == null ? null : item.Notes);
}
private bool MatchesTprzOwnFields(TprzDirectoryItem item)
{
return MatchesSearchTokens(
item == null ? null : item.Id.ToString(),
item == null ? null : item.RangeText,
item == null ? null : item.AccuracyText,
item == null ? null : item.CompletenessName,
item == null ? null : item.RegistryTypeSizeNumber,
item == null ? null : item.ServiceCode,
item == null ? null : item.VniimsTypeSizeCode.ToString());
}
private bool IsSearchEmpty()
{
return string.IsNullOrWhiteSpace(SearchText);
}
private bool MatchesSearchTokens(params string[] values)
{
var tokens = GetSearchTokens();
if (tokens.Length == 0)
{
return true;
}
var haystack = string.Join(" ", values.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })).ToUpperInvariant();
return tokens.All(delegate(string token) { return haystack.IndexOf(token, StringComparison.Ordinal) >= 0; });
}
private string[] GetSearchTokens()
{
return (SearchText ?? string.Empty)
.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(delegate(string token) { return token.Trim().ToUpperInvariant(); })
.Where(delegate(string token) { return token.Length > 0; })
.ToArray();
}
private void RefreshAsync()
{
RunBusyOperation(async delegate { await RefreshTipsCoreAsync(SelectedTips == null ? (int?)null : SelectedTips.Id); });
}
private void LoadTprzForSelection()
{
if (IsBusy) return;
RunBusyOperation(async delegate
{
if (SelectedTips == null)
{
_currentTprzCache = new List<TprzDirectoryItem>();
TprzItems.Clear(); TprmcpItems.Clear(); TprmkItems.Clear();
SelectedTprz = null; SelectedTprmcp = null; SelectedTprmk = null;
UpdateStatus();
return;
}
await RefreshTprzCoreAsync(SelectedTips.Id, null);
});
}
private void LoadLeafTablesForSelection()
{
if (IsBusy) return;
RunBusyOperation(async delegate
{
if (SelectedTprz == null)
{
TprmcpItems.Clear(); TprmkItems.Clear(); SelectedTprmcp = null; SelectedTprmk = null; UpdateStatus(); return;
}
await RefreshLeafTablesCoreAsync(SelectedTprz.Id, null, null);
});
}
private void RaiseCommandStates()
{
((RelayCommand)AddTipsCommand).RaiseCanExecuteChanged(); ((RelayCommand)EditTipsCommand).RaiseCanExecuteChanged(); ((RelayCommand)DeleteTipsCommand).RaiseCanExecuteChanged(); ((RelayCommand)ManageTpvdklCommand).RaiseCanExecuteChanged();
((RelayCommand)AddTprzCommand).RaiseCanExecuteChanged(); ((RelayCommand)EditTprzCommand).RaiseCanExecuteChanged(); ((RelayCommand)DeleteTprzCommand).RaiseCanExecuteChanged();
((RelayCommand)AddTprmcpCommand).RaiseCanExecuteChanged(); ((RelayCommand)EditTprmcpCommand).RaiseCanExecuteChanged(); ((RelayCommand)DeleteTprmcpCommand).RaiseCanExecuteChanged();
((RelayCommand)AddTprmkCommand).RaiseCanExecuteChanged(); ((RelayCommand)EditTprmkCommand).RaiseCanExecuteChanged(); ((RelayCommand)DeleteTprmkCommand).RaiseCanExecuteChanged();
((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
}
private async void RunBusyOperation(Func<Task> operation)
{
try { await ExecuteBusyOperationAsync(operation); }
catch (Exception ex) { IsBusy = false; _dialogService.ShowError(ex.Message); }
}
private async void RunMutationOperation(Func<Task> operation)
{
try { await ExecuteMutationOperationAsync(operation); }
catch (Exception ex) { IsBusy = false; _dialogService.ShowError(ex.Message); }
}
private void UpdateStatus()
{
var searchText = string.IsNullOrWhiteSpace(SearchText) ? null : SearchText.Trim();
StatusText = string.Format(
"{0}TIPS: {1}/{2}. TPRZ: {3}/{4}. TPRMK: {5}. TPRMCP: {6}.",
string.IsNullOrWhiteSpace(searchText) ? string.Empty : string.Format("Поиск: \"{0}\". ", searchText),
TipsItems.Count,
_tipsCache.Count,
TprzItems.Count,
_currentTprzCache.Count,
TprmkItems.Count,
TprmcpItems.Count);
}
private static TipsDirectoryItem CloneTips(TipsDirectoryItem source)
{
return new TipsDirectoryItem { Id = source.Id, MeasurementAreaId = source.MeasurementAreaId, MeasurementAreaName = source.MeasurementAreaName, InstrumentNameId = source.InstrumentNameId, InstrumentName = source.InstrumentName, CategoryId = source.CategoryId, CategoryName = source.CategoryName, DesignId = source.DesignId, DesignName = source.DesignName, TypeName = source.TypeName, ServiceLifeYears = source.ServiceLifeYears, RegistryPeriodMonths = source.RegistryPeriodMonths, RegistryTypeNumber = source.RegistryTypeNumber, VniimsTypeCode = source.VniimsTypeCode, MetrControlCode = source.MetrControlCode, Notes = source.Notes, IsSpecialPurpose = source.IsSpecialPurpose, IsMkPrimaryOnly = source.IsMkPrimaryOnly };
}
private static TprzDirectoryItem CloneTprz(TprzDirectoryItem source)
{
return new TprzDirectoryItem { Id = source.Id, TipsId = source.TipsId, CompletenessId = source.CompletenessId, CompletenessName = source.CompletenessName, RangeText = source.RangeText, AccuracyText = source.AccuracyText, VniimsTypeSizeCode = source.VniimsTypeSizeCode, RegistryTypeSizeNumber = source.RegistryTypeSizeNumber, ServiceCode = source.ServiceCode };
}
private static TprmcpDirectoryItem CloneTprmcp(TprmcpDirectoryItem source)
{
return new TprmcpDirectoryItem { Id = source.Id, TypeSizeId = source.TypeSizeId, CycleId = source.CycleId, CycleName = source.CycleName, GroupId = source.GroupId, GroupName = source.GroupName, PeriodMonths = source.PeriodMonths, Comment = source.Comment };
}
private static TprmkDirectoryItem CloneTprmk(TprmkDirectoryItem source)
{
return new TprmkDirectoryItem { Id = source.Id, TypeSizeId = source.TypeSizeId, VerificationTypeId = source.VerificationTypeId, VerificationTypeName = source.VerificationTypeName, OrganizationId = source.OrganizationId, OrganizationName = source.OrganizationName, QualificationId = source.QualificationId, QualificationName = source.QualificationName, GroupId = source.GroupId, GroupName = source.GroupName, PlaceId = source.PlaceId, PlaceName = source.PlaceName, Cost = source.Cost, ExtraCost = source.ExtraCost, RushMarkup = source.RushMarkup, TimeNormHours = source.TimeNormHours, TimeNormCode = source.TimeNormCode, VerifierCount = source.VerifierCount, VerificationCode = source.VerificationCode, NormDocHours = source.NormDocHours };
}
}
}

View File

@@ -108,6 +108,63 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="SelectInstrumentTypeWindowViewModel.cs" />
<Page Include="TipsEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="TipsEditWindow.xaml.cs">
<DependentUpon>TipsEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="TipsEditWindowViewModel.cs" />
<Page Include="SpoiDirectoryWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="SpoiDirectoryWindow.xaml.cs">
<DependentUpon>SpoiDirectoryWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="SpoiDirectoryWindowViewModel.cs" />
<Page Include="SpoiEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="SpoiEditWindow.xaml.cs">
<DependentUpon>SpoiEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="SpoiEditWindowViewModel.cs" />
<Page Include="TpvdklEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="TpvdklEditWindow.xaml.cs">
<DependentUpon>TpvdklEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="TpvdklEditWindowViewModel.cs" />
<Page Include="TpvdklWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="TpvdklWindow.xaml.cs">
<DependentUpon>TpvdklWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="TpvdklWindowViewModel.cs" />
<Compile Include="TypeSizeDialogService.cs" />
<Page Include="TypeSizeDirectoryWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="TypeSizeDirectoryWindow.xaml.cs">
<DependentUpon>TypeSizeDirectoryWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="TypeSizeDirectoryWindowViewModel.cs" />
<Compile Include="TypeSizeDirectoryModels.cs" />
<Compile Include="TypeSizeDirectoryService.cs" />
<Page Include="SpnmtpDirectoryWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
@@ -126,6 +183,33 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="SpnmtpEditWindowViewModel.cs" />
<Page Include="TprmcpEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="TprmcpEditWindow.xaml.cs">
<DependentUpon>TprmcpEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="TprmcpEditWindowViewModel.cs" />
<Page Include="TprmkEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="TprmkEditWindow.xaml.cs">
<DependentUpon>TprmkEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="TprmkEditWindowViewModel.cs" />
<Page Include="TprzEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="TprzEditWindow.xaml.cs">
<DependentUpon>TprzEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="TprzEditWindowViewModel.cs" />
<Page Include="VerificationEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>