Compare commits

...

14 Commits

Author SHA1 Message Date
Курнат Андрей
a2b4762702 edit 2026-04-03 21:06:10 +03:00
Курнат Андрей
1b20b39aca edit 2026-04-03 20:04:58 +03:00
Курнат Андрей
22ed7d978a edit 2026-04-02 21:44:28 +03:00
Курнат Андрей
db15503315 edit 2026-04-01 19:33:59 +03:00
Курнат Андрей
339b9ab8aa edit 2026-03-25 19:08:46 +03:00
Курнат Андрей
054d10cb6d edit 2026-03-25 18:26:40 +03:00
Курнат Андрей
d46172beb9 edit 2026-03-24 22:31:05 +03:00
Курнат Андрей
74d793948e edit 2026-03-23 21:24:09 +03:00
Курнат Андрей
bf9f54f91c edit 2026-03-23 19:56:35 +03:00
Курнат Андрей
d54f5b8e22 edit 2026-03-23 06:07:50 +03:00
Курнат Андрей
234dac7d32 edit 2026-03-22 22:04:04 +03:00
Курнат Андрей
7bbca6ba55 edit 2026-03-22 21:44:29 +03:00
Курнат Андрей
a47a7a5a3b edit 2026-03-19 23:31:41 +03:00
Курнат Андрей
ce3a3f02d2 edit 2026-03-18 21:56:53 +03:00
1746 changed files with 56496 additions and 293 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -5,20 +5,54 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XLAB.DATA", "XLAB.DATA\XLAB
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XLAB", "XLAB\XLAB.csproj", "{B8DAAB84-777A-4274-8452-E602DB1AF587}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XLAB", "XLAB\XLAB.csproj", "{B8DAAB84-777A-4274-8452-E602DB1AF587}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XLAB2", "XLAB2\XLAB2.csproj", "{6B248955-05FF-43E9-B038-5CD501D21442}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AE0E35D7-DFA4-4150-9889-255043B232BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {AE0E35D7-DFA4-4150-9889-255043B232BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Debug|x64.ActiveCfg = Debug|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Debug|x64.Build.0 = Debug|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Debug|x86.ActiveCfg = Debug|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Debug|x86.Build.0 = Debug|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {AE0E35D7-DFA4-4150-9889-255043B232BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Release|Any CPU.Build.0 = Release|Any CPU {AE0E35D7-DFA4-4150-9889-255043B232BB}.Release|Any CPU.Build.0 = Release|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Release|x64.ActiveCfg = Release|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Release|x64.Build.0 = Release|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Release|x86.ActiveCfg = Release|Any CPU
{AE0E35D7-DFA4-4150-9889-255043B232BB}.Release|x86.Build.0 = Release|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B8DAAB84-777A-4274-8452-E602DB1AF587}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Debug|Any CPU.Build.0 = Debug|Any CPU {B8DAAB84-777A-4274-8452-E602DB1AF587}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Debug|x64.ActiveCfg = Debug|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Debug|x64.Build.0 = Debug|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Debug|x86.ActiveCfg = Debug|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Debug|x86.Build.0 = Debug|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8DAAB84-777A-4274-8452-E602DB1AF587}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Release|Any CPU.Build.0 = Release|Any CPU {B8DAAB84-777A-4274-8452-E602DB1AF587}.Release|Any CPU.Build.0 = Release|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Release|x64.ActiveCfg = Release|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Release|x64.Build.0 = Release|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Release|x86.ActiveCfg = Release|Any CPU
{B8DAAB84-777A-4274-8452-E602DB1AF587}.Release|x86.Build.0 = Release|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Debug|x64.ActiveCfg = Debug|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Debug|x64.Build.0 = Debug|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Debug|x86.ActiveCfg = Debug|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Debug|x86.Build.0 = Debug|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Release|Any CPU.Build.0 = Release|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Release|x64.ActiveCfg = Release|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Release|x64.Build.0 = Release|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Release|x86.ActiveCfg = Release|Any CPU
{6B248955-05FF-43E9-B038-5CD501D21442}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -1,4 +1,5 @@
<Solution> <Solution>
<Project Path="XLAB.DATA/XLAB.DATA.csproj" Id="ae0e35d7-dfa4-4150-9889-255043b232bb" /> <Project Path="XLAB.DATA/XLAB.DATA.csproj" Id="ae0e35d7-dfa4-4150-9889-255043b232bb" />
<Project Path="XLAB/XLAB.csproj" Id="b8daab84-777a-4274-8452-e602db1af587" /> <Project Path="XLAB/XLAB.csproj" Id="b8daab84-777a-4274-8452-e602db1af587" />
<Project Path="XLAB2/XLAB2.csproj" />
</Solution> </Solution>

View File

@@ -5,18 +5,23 @@
StartupUri="MainWindow.xaml"> StartupUri="MainWindow.xaml">
<Application.Resources> <Application.Resources>
<LinearGradientBrush x:Key="AppWindowBackgroundBrush" StartPoint="0,0" EndPoint="1,1"> <LinearGradientBrush x:Key="AppWindowBackgroundBrush" StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#FFF8FAFD" Offset="0" /> <GradientStop Color="#FFDCE5EE" Offset="0" />
<GradientStop Color="#FFF2F6FB" Offset="1" /> <GradientStop Color="#FFC9D6E3" Offset="1" />
</LinearGradientBrush> </LinearGradientBrush>
<SolidColorBrush x:Key="AppSurfaceBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="AppPanelBrush" Color="#FFF1F6FB" />
<SolidColorBrush x:Key="AppSurfaceBrush" Color="#FFFCFEFF" />
<SolidColorBrush x:Key="AppAccentBrush" Color="#FF5C7FA8" /> <SolidColorBrush x:Key="AppAccentBrush" Color="#FF5C7FA8" />
<SolidColorBrush x:Key="AppAccentSoftBrush" Color="#FFDDE8F3" /> <SolidColorBrush x:Key="AppAccentSoftBrush" Color="#FFD6E3F0" />
<SolidColorBrush x:Key="AppBorderBrush" Color="#FFC9D6E2" /> <SolidColorBrush x:Key="AppBorderBrush" Color="#FFAABBCD" />
<SolidColorBrush x:Key="AppTextBrush" Color="#FF263645" /> <SolidColorBrush x:Key="AppTextBrush" Color="#FF263645" />
<SolidColorBrush x:Key="AppMutedTextBrush" Color="#FF6B7B88" /> <SolidColorBrush x:Key="AppMutedTextBrush" Color="#FF6B7B88" />
<SolidColorBrush x:Key="AppButtonBrush" Color="#FFF5F8FC" /> <SolidColorBrush x:Key="AppButtonBrush" Color="#FFF7FAFD" />
<SolidColorBrush x:Key="AppButtonHoverBrush" Color="#FFEAF2FA" /> <SolidColorBrush x:Key="AppButtonHoverBrush" Color="#FFE7F0F9" />
<SolidColorBrush x:Key="AppSelectionBrush" Color="#FFDDEAF7" /> <SolidColorBrush x:Key="AppMenuIconAccentBrush" Color="#FF4F7FA7" />
<SolidColorBrush x:Key="AppMenuIconSuccessBrush" Color="#FF47A772" />
<SolidColorBrush x:Key="AppMenuIconDangerBrush" Color="#FFC76868" />
<SolidColorBrush x:Key="AppMenuIconWarningBrush" Color="#FFCC9B52" />
<SolidColorBrush x:Key="AppSelectionBrush" Color="#FFD8E6F4" />
<SolidColorBrush x:Key="AppSelectionTextBrush" Color="#FF17324A" /> <SolidColorBrush x:Key="AppSelectionTextBrush" Color="#FF17324A" />
<SolidColorBrush x:Key="OpenDocumentTenDaysBrush" Color="#FFF2F8EA" /> <SolidColorBrush x:Key="OpenDocumentTenDaysBrush" Color="#FFF2F8EA" />
<SolidColorBrush x:Key="OpenDocumentTwentyDaysBrush" Color="#FFFCF4E3" /> <SolidColorBrush x:Key="OpenDocumentTwentyDaysBrush" Color="#FFFCF4E3" />
@@ -35,9 +40,38 @@
</Style> </Style>
<Style TargetType="{x:Type GroupBox}"> <Style TargetType="{x:Type GroupBox}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" /> <Setter Property="Background" Value="{StaticResource AppPanelBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" /> <Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppAccentBrush}" /> <Setter Property="Foreground" Value="{StaticResource AppAccentBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupBox}">
<Grid SnapsToDevicePixels="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="1"
Margin="0,2,0,0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
CornerRadius="3">
<ContentPresenter ContentSource="Content" />
</Border>
<Border Grid.Row="0"
Margin="12,0,12,0"
Padding="8,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="{StaticResource AppWindowBackgroundBrush}">
<ContentPresenter ContentSource="Header"
RecognizesAccessKey="True" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style> </Style>
<Style TargetType="{x:Type Button}"> <Style TargetType="{x:Type Button}">
@@ -75,7 +109,7 @@
</Style> </Style>
<Style TargetType="{x:Type Menu}"> <Style TargetType="{x:Type Menu}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" /> <Setter Property="Background" Value="{StaticResource AppPanelBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" /> <Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
</Style> </Style>
@@ -88,6 +122,166 @@
<Style TargetType="{x:Type MenuItem}"> <Style TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" /> <Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="Header" Value="Добавить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Изменить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Удалить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Распечатать">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="1.5" Width="8" Height="4" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="5" Width="12" Height="5" RadiusX="1.5" RadiusY="1.5" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="4" Canvas.Top="8.5" Width="8" Height="5" Fill="#FFF9FCFE" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Добавить по заводским номерам">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="2" Canvas.Top="3" Width="8" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="7" Width="8" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="11" Width="6" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Ellipse Canvas.Left="10.5" Canvas.Top="6" Width="4" Height="4" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="12" Canvas.Top="4.5" Width="1" Height="7" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="9.5" Canvas.Top="7" Width="6" Height="1" Fill="{StaticResource AppMenuIconSuccessBrush}" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Добавить по типу">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="2" Canvas.Top="3" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="9" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="8" Canvas.Top="6" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="12" Canvas.Top="1.5" Width="1.5" Height="5" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="10.25" Canvas.Top="3.25" Width="5" Height="1.5" Fill="{StaticResource AppMenuIconSuccessBrush}" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Клонировать поверку в выбранные строки">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="3" Canvas.Top="4" Width="7" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="2" Width="7" Height="8" RadiusX="1" RadiusY="1" Fill="#FFEAF3FB" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Распечатать документ о поверке">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="1.5" Width="8" Height="4" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="5" Width="12" Height="5" RadiusX="1.5" RadiusY="1.5" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="4" Canvas.Top="8.5" Width="8" Height="5" Fill="#FFF9FCFE" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
<Ellipse Canvas.Left="10.5" Canvas.Top="9.5" Width="4" Height="4" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Path Fill="White" Data="M12.1,10.4 L12.9,11.2 L14.2,9.6 L14.8,10.1 L12.9,12.4 L11.5,11 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Годен">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Path Fill="White" Data="M5.1,8.2 L7.2,10.3 L11.6,5.6 L12.8,6.6 L7.3,12.3 L3.9,8.9 Z" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Забракован">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Path Stroke="White" StrokeThickness="1.8" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Data="M5.1,5.1 L10.9,10.9 M10.9,5.1 L5.1,10.9" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Отменить проверку">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconWarningBrush}" Data="M7.8,2.2 C10.8,2.2 13.2,4.6 13.2,7.6 C13.2,10.6 10.8,13 7.8,13 C5.5,13 3.6,11.6 2.8,9.5 L4.5,9.5 C5.2,10.8 6.4,11.5 7.8,11.5 C10,11.5 11.7,9.8 11.7,7.6 C11.7,5.4 10,3.7 7.8,3.7 C6.5,3.7 5.3,4.3 4.6,5.4 L6.7,5.4 L3.8,8.2 L1.1,5.4 L3.1,5.4 C4,3.4 5.8,2.2 7.8,2.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Виды клейм...">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Ellipse Canvas.Left="2.5" Canvas.Top="2.5" Width="11" Height="11" Fill="{StaticResource AppMenuIconWarningBrush}" />
<Path Fill="White" Data="M8,4 L8.9,6.2 L11.3,6.3 L9.4,7.8 L10.1,10.1 L8,8.8 L5.9,10.1 L6.6,7.8 L4.7,6.3 L7.1,6.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style> </Style>
<Style TargetType="{x:Type ListBox}"> <Style TargetType="{x:Type ListBox}">
@@ -110,12 +304,14 @@
<Style TargetType="{x:Type DataGrid}"> <Style TargetType="{x:Type DataGrid}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" /> <Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" /> <Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="RowBackground" Value="#FFFFFFFF" /> <Setter Property="HorizontalGridLinesBrush" Value="#FFD9E3ED" />
<Setter Property="AlternatingRowBackground" Value="#FFF7FAFD" /> <Setter Property="VerticalGridLinesBrush" Value="#FFD9E3ED" />
<Setter Property="RowBackground" Value="#FFFEFFFF" />
<Setter Property="AlternatingRowBackground" Value="#FFF6FAFD" />
<Setter Property="ColumnHeaderStyle"> <Setter Property="ColumnHeaderStyle">
<Setter.Value> <Setter.Value>
<Style TargetType="{x:Type DataGridColumnHeader}"> <Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="#FFEAF2FA" /> <Setter Property="Background" Value="#FFE4EDF6" />
<Setter Property="Foreground" Value="{StaticResource AppAccentBrush}" /> <Setter Property="Foreground" Value="{StaticResource AppAccentBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" /> <Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="FontWeight" Value="SemiBold" /> <Setter Property="FontWeight" Value="SemiBold" />

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Windows;
namespace XLAB
{
internal interface IEkzDirectoryDialogService
{
EkzDirectoryItem ShowEkzEditDialog(EkzDirectoryItem seed, bool isNew, IReadOnlyList<EkzDirectoryItem> existingItems, EkzDirectoryService service);
bool Confirm(string message);
void ShowError(string message);
void ShowInfo(string message);
void ShowWarning(string message);
}
internal sealed class EkzDirectoryDialogService : IEkzDirectoryDialogService
{
private readonly Window _owner;
public EkzDirectoryDialogService(Window owner)
{
_owner = owner;
}
public bool Confirm(string message)
{
return MessageBox.Show(_owner, message, "ПСВ", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
}
public EkzDirectoryItem ShowEkzEditDialog(EkzDirectoryItem seed, bool isNew, IReadOnlyList<EkzDirectoryItem> existingItems, EkzDirectoryService service)
{
var viewModel = new EkzEditWindowViewModel(seed, isNew, existingItems, service);
var window = new EkzEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
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);
}
}
}

138
XLAB/EkzDirectoryModels.cs Normal file
View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
namespace XLAB
{
public sealed class EkzDirectoryItem
{
public int Id { get; set; }
public int TypeSizeId { get; set; }
public string MeasurementAreaName { get; set; }
public string InstrumentName { get; set; }
public string TypeName { get; set; }
public string RangeText { get; set; }
public string AccuracyText { get; set; }
public string RegistryNumber { get; set; }
public int OwnerOrganizationId { get; set; }
public string OwnerOrganizationName { get; set; }
public string SerialNumber { get; set; }
public string InventoryNumber { get; set; }
public string StickerNumbers { get; set; }
public string Notes { get; set; }
}
public sealed class EkzMkDirectoryItem
{
public int CardId { get; set; }
public int InstrumentId { get; set; }
public string VerificationTypeName { get; set; }
public string VerificationOrganizationName { get; set; }
public string DocumentNumber { get; set; }
public string VerificationDocumentNumber { get; set; }
public DateTime? VerificationDocumentDate { get; set; }
public string StickerNumber { get; set; }
public string VerifierName { get; set; }
public int PeriodMonths { get; set; }
public DateTime? AcceptedOn { get; set; }
public DateTime? PlannedOn { get; set; }
public DateTime? PerformedOn { get; set; }
public DateTime? IssuedOn { get; set; }
public bool? IsPassed { get; set; }
public string Notes { get; set; }
public string ResultText
{
get
{
if (!IsPassed.HasValue)
{
return string.Empty;
}
return IsPassed.Value ? "Годен" : "Не годен";
}
}
public string VerificationDocumentDisplay
{
get
{
if (string.IsNullOrWhiteSpace(VerificationDocumentNumber))
{
return VerificationDocumentDate.HasValue ? VerificationDocumentDate.Value.ToString("d") : string.Empty;
}
if (!VerificationDocumentDate.HasValue)
{
return VerificationDocumentNumber;
}
return string.Format("{0} от {1:d}", VerificationDocumentNumber, VerificationDocumentDate.Value);
}
}
}
internal sealed class EkzDeleteImpactItem
{
public string TableName { get; set; }
public int RowCount { get; set; }
}
internal sealed class EkzDeletePreview
{
public bool CanDelete { get; set; }
public IReadOnlyList<EkzDeleteImpactItem> ImpactItems { get; set; }
public string ConfirmationMessage { get; set; }
public string WarningMessage { get; set; }
}
internal sealed class EkzDeleteResult
{
public bool IsDeleted { get; set; }
public IReadOnlyList<EkzDeleteImpactItem> ImpactItems { get; set; }
public string WarningMessage { get; set; }
}
internal static class EkzDirectoryRules
{
public const int SerialNumberMaxLength = 30;
public const int InventoryNumberMaxLength = 30;
public const int NotesMaxLength = 8000;
}
}

858
XLAB/EkzDirectoryService.cs Normal file
View File

@@ -0,0 +1,858 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Data.SqlClient;
namespace XLAB
{
internal sealed class EkzDirectoryService
{
private static readonly string[] CascadingEkzChildTables = { "EKZMK", "EKZMCP" };
private static readonly string[] CascadingEkzMkChildTables = { "EKZMKFCTVL", "EKZMKDH", "EKZMKEKZK", "EKZMKND", "KSPELEKZMK" };
public int AddEkzItem(EkzDirectoryItem item)
{
var normalizedItem = NormalizeEkzItem(item);
const string sql = @"
INSERT INTO dbo.EKZ
(
IDTPRZ,
IDFRPDV,
KLSIPR,
NNZV,
NNIN,
DSEKZ,
GUIDEKZ,
IsDeleted
)
VALUES
(
@TypeSizeId,
@OwnerOrganizationId,
1,
@SerialNumber,
@InventoryNumber,
@Notes,
@Guid,
0
);
SELECT CAST(SCOPE_IDENTITY() AS int);";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureEkzIsUnique(connection, normalizedItem.TypeSizeId, normalizedItem.OwnerOrganizationId, normalizedItem.SerialNumber, null);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = normalizedItem.OwnerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = normalizedItem.SerialNumber;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@InventoryNumber", SqlDbType.VarChar, EkzDirectoryRules.InventoryNumberMaxLength, normalizedItem.InventoryNumber);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Notes", SqlDbType.VarChar, EkzDirectoryRules.NotesMaxLength, normalizedItem.Notes);
command.Parameters.Add("@Guid", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
return Convert.ToInt32(command.ExecuteScalar());
}
}
public EkzDeletePreview GetEkzDeletePreview(int id)
{
if (id <= 0)
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Не выбрана запись EKZ для удаления."
};
}
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
return BuildEkzDeletePreview(connection, null, id);
}
}
public EkzDeleteResult DeleteEkzItem(int id)
{
if (id <= 0)
{
throw new InvalidOperationException("Не выбрана запись EKZ для удаления.");
}
try
{
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
var preview = BuildEkzDeletePreview(connection, transaction, id);
if (!preview.CanDelete)
{
transaction.Rollback();
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = preview.ImpactItems ?? Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = preview.WarningMessage
};
}
var impactItems = new List<EkzDeleteImpactItem>();
AddImpactItem(impactItems, "EKZMKFCTVL", DeleteEkzMkFctvl(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKDH", DeleteEkzMkDh(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKEKZK", DeleteEkzMkEkzk(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKND", DeleteEkzMkNd(connection, transaction, id));
AddImpactItem(impactItems, "KSPELEKZMK", DeleteKspelEkzMk(connection, transaction, id));
AddImpactItem(impactItems, "DMS", DeleteEkzDms(connection, transaction, id));
AddImpactItem(impactItems, "EKZMK", DeleteEkzMk(connection, transaction, id));
AddImpactItem(impactItems, "EKZMCP", DeleteEkzMcp(connection, transaction, id));
var deletedEkzCount = DeleteEkz(connection, transaction, id);
if (deletedEkzCount == 0)
{
transaction.Rollback();
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Запись EKZ для удаления не найдена."
};
}
AddImpactItem(impactItems, "EKZ", deletedEkzCount);
transaction.Commit();
return new EkzDeleteResult
{
IsDeleted = true,
ImpactItems = OrderImpactItems(impactItems)
};
}
}
}
catch (SqlException ex) when (ex.Number == 547)
{
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = CreateEkzDeleteFailedMessage(ex)
};
}
}
public IReadOnlyList<EkzDirectoryItem> LoadEkzItems()
{
const string sql = @"
SELECT
z.IDEKZ AS Id,
z.IDTPRZ AS TypeSizeId,
areas.NMOI AS MeasurementAreaName,
names.NMTP AS InstrumentName,
tips.TP AS TypeName,
tprz.DPZN AS RangeText,
tprz.HRTC AS AccuracyText,
tprz.NNGSRS AS RegistryNumber,
z.IDFRPDV AS OwnerOrganizationId,
ownerOrg.NMFRPD AS OwnerOrganizationName,
z.NNZV AS SerialNumber,
z.NNIN AS InventoryNumber,
stickers.StickerNumbers AS StickerNumbers,
CAST(z.DSEKZ AS nvarchar(max)) AS Notes
FROM dbo.EKZ z
JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
JOIN dbo.FRPD ownerOrg ON ownerOrg.IDFRPD = z.IDFRPDV
OUTER APPLY
(
SELECT STUFF(
(
SELECT N' ' + sticker.StickerNumber
FROM
(
SELECT DISTINCT LTRIM(RTRIM(m.NNNKL)) AS StickerNumber
FROM dbo.EKZMK m
WHERE m.IDEKZ = z.IDEKZ
AND NULLIF(LTRIM(RTRIM(m.NNNKL)), N'') IS NOT NULL
) sticker
ORDER BY sticker.StickerNumber
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'),
1,
1,
N'') AS StickerNumbers
) stickers
WHERE ISNULL(z.IsDeleted, 0) = 0
ORDER BY ownerOrg.NMFRPD, areas.NMOI, names.NMTP, tips.TP, tprz.DPZN, z.NNZV, z.IDEKZ;";
var items = new List<EkzDirectoryItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new EkzDirectoryItem
{
Id = ReferenceDirectorySqlHelpers.GetInt32(reader, "Id"),
TypeSizeId = ReferenceDirectorySqlHelpers.GetInt32(reader, "TypeSizeId"),
MeasurementAreaName = ReferenceDirectorySqlHelpers.GetString(reader, "MeasurementAreaName"),
InstrumentName = ReferenceDirectorySqlHelpers.GetString(reader, "InstrumentName"),
TypeName = ReferenceDirectorySqlHelpers.GetString(reader, "TypeName"),
RangeText = ReferenceDirectorySqlHelpers.GetString(reader, "RangeText"),
AccuracyText = ReferenceDirectorySqlHelpers.GetString(reader, "AccuracyText"),
RegistryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "RegistryNumber"),
OwnerOrganizationId = ReferenceDirectorySqlHelpers.GetInt32(reader, "OwnerOrganizationId"),
OwnerOrganizationName = ReferenceDirectorySqlHelpers.GetString(reader, "OwnerOrganizationName"),
SerialNumber = ReferenceDirectorySqlHelpers.GetString(reader, "SerialNumber"),
InventoryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "InventoryNumber"),
StickerNumbers = ReferenceDirectorySqlHelpers.GetString(reader, "StickerNumbers"),
Notes = ReferenceDirectorySqlHelpers.GetString(reader, "Notes")
});
}
}
}
return items;
}
public IReadOnlyList<EkzMkDirectoryItem> LoadEkzMkItems(int instrumentId)
{
const string sql = @"
SELECT
m.IDEKZMK AS CardId,
m.IDEKZ AS InstrumentId,
verificationType.NMVDMK AS VerificationTypeName,
organization.NMFRPD AS VerificationOrganizationName,
m.NNZVPV AS DocumentNumber,
verificationDocument.NNDMS AS VerificationDocumentNumber,
verificationDocument.DTDMS AS VerificationDocumentDate,
m.NNNKL AS StickerNumber,
verifier.PRFIO AS VerifierName,
m.PRMK AS PeriodMonths,
m.DTPRM AS AcceptedOn,
m.DTMKPL AS PlannedOn,
m.DTMKFK AS PerformedOn,
m.DTVDM AS IssuedOn,
m.GDN AS IsPassed,
CAST(m.DSEKZMK AS nvarchar(max)) AS Notes
FROM dbo.EKZMK m
LEFT JOIN dbo.SPVDMK verificationType ON verificationType.IDSPVDMK = m.IDSPVDMK
LEFT JOIN dbo.FRPD organization ON organization.IDFRPD = m.IDFRPD
LEFT JOIN dbo.PRSN verifier ON verifier.IDPRSN = m.IDPRSN
OUTER APPLY
(
SELECT TOP (1)
d.NND AS NNDMS,
d.DTD AS DTDMS
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.FRDMS frdms ON frdms.IDFRDMS = d.IDFRDMS
JOIN dbo.SPVDD spvdd ON spvdd.IDSPVDD = frdms.IDSPVDD
WHERE d.IDOD = m.IDEKZMK
AND vdd.IDSPVDOD = 2
AND spvdd.IDSPVDD IN (2, 6, 8)
ORDER BY d.DTD DESC
) verificationDocument
WHERE m.IDEKZ = @InstrumentId
ORDER BY ISNULL(m.DTPRM, CONVERT(datetime, '19000101', 112)) DESC, m.IDEKZMK DESC;";
var items = new List<EkzMkDirectoryItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = instrumentId;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new EkzMkDirectoryItem
{
CardId = ReferenceDirectorySqlHelpers.GetInt32(reader, "CardId"),
InstrumentId = ReferenceDirectorySqlHelpers.GetInt32(reader, "InstrumentId"),
VerificationTypeName = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationTypeName"),
VerificationOrganizationName = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationOrganizationName"),
DocumentNumber = ReferenceDirectorySqlHelpers.GetString(reader, "DocumentNumber"),
VerificationDocumentNumber = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationDocumentNumber"),
VerificationDocumentDate = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "VerificationDocumentDate"),
StickerNumber = ReferenceDirectorySqlHelpers.GetString(reader, "StickerNumber"),
VerifierName = ReferenceDirectorySqlHelpers.GetString(reader, "VerifierName"),
PeriodMonths = ReferenceDirectorySqlHelpers.GetInt32(reader, "PeriodMonths"),
AcceptedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "AcceptedOn"),
PlannedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "PlannedOn"),
PerformedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "PerformedOn"),
IssuedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "IssuedOn"),
IsPassed = ReferenceDirectorySqlHelpers.GetNullableBoolean(reader, "IsPassed"),
Notes = ReferenceDirectorySqlHelpers.GetString(reader, "Notes")
});
}
}
}
return items;
}
public IReadOnlyList<DirectoryLookupItem> LoadFrpdReferences()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
fr.IDFRPD AS Id,
fr.NMFRPD AS Name
FROM dbo.FRPD fr
WHERE NULLIF(LTRIM(RTRIM(fr.NMFRPD)), '') IS NOT NULL
ORDER BY fr.NMFRPD, fr.IDFRPD;");
}
public IReadOnlyList<DirectoryLookupItem> LoadTypeSizeReferences()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
tprz.IDTPRZ AS Id,
LTRIM(RTRIM(
COALESCE(NULLIF(areas.NMOI, N'') + N' / ', N'')
+ COALESCE(NULLIF(names.NMTP, N'') + N' / ', N'')
+ COALESCE(NULLIF(tips.TP, N''), N'')
+ CASE WHEN NULLIF(LTRIM(RTRIM(tprz.DPZN)), N'') IS NULL THEN N'' ELSE N' / ' + tprz.DPZN END
+ CASE
WHEN NULLIF(LTRIM(RTRIM(CONVERT(nvarchar(50), tprz.NNGSRS))), N'') IS NULL THEN N''
ELSE N' / № ГР ' + CONVERT(nvarchar(50), tprz.NNGSRS)
END
)) AS Name
FROM dbo.TPRZ tprz
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
ORDER BY areas.NMOI, names.NMTP, tips.TP, tprz.DPZN, tprz.IDTPRZ;");
}
public void UpdateEkzItem(EkzDirectoryItem item)
{
var normalizedItem = NormalizeEkzItem(item);
if (normalizedItem.Id <= 0)
{
throw new InvalidOperationException("Не выбрана запись EKZ для изменения.");
}
const string sql = @"
UPDATE dbo.EKZ
SET IDTPRZ = @TypeSizeId,
IDFRPDV = @OwnerOrganizationId,
NNZV = @SerialNumber,
NNIN = @InventoryNumber,
DSEKZ = @Notes
WHERE IDEKZ = @Id;
SELECT @@ROWCOUNT;";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureEkzIsUnique(connection, normalizedItem.TypeSizeId, normalizedItem.OwnerOrganizationId, normalizedItem.SerialNumber, normalizedItem.Id);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = normalizedItem.OwnerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = normalizedItem.SerialNumber;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@InventoryNumber", SqlDbType.VarChar, EkzDirectoryRules.InventoryNumberMaxLength, normalizedItem.InventoryNumber);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Notes", SqlDbType.VarChar, EkzDirectoryRules.NotesMaxLength, normalizedItem.Notes);
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись EKZ для изменения не найдена.");
}
}
}
private static EkzDeletePreview BuildEkzDeletePreview(SqlConnection connection, SqlTransaction transaction, int id)
{
if (!EkzExists(connection, transaction, id))
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Запись EKZ для удаления не найдена."
};
}
var cardIds = LoadEkzMkCardIds(connection, transaction, id);
var blockers = new List<DeleteBlockerInfo>();
blockers.AddRange(ReferenceDirectorySqlHelpers.LoadDeleteBlockersFromForeignKeys(connection, transaction, "EKZ", id, CascadingEkzChildTables));
blockers.AddRange(LoadUnhandledDeleteBlockers(connection, transaction, "EKZMK", cardIds, CascadingEkzMkChildTables));
var impactItems = BuildImpactItems(connection, transaction, id, cardIds);
var mergedBlockers = MergeBlockers(blockers);
if (mergedBlockers.Count > 0)
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = impactItems,
WarningMessage = CreateEkzCascadeBlockedMessage(mergedBlockers)
};
}
return new EkzDeletePreview
{
CanDelete = true,
ImpactItems = impactItems,
ConfirmationMessage = CreateEkzDeleteConfirmationMessage(impactItems)
};
}
private static IReadOnlyList<EkzDeleteImpactItem> BuildImpactItems(SqlConnection connection, SqlTransaction transaction, int instrumentId, IReadOnlyCollection<int> cardIds)
{
var impactItems = new List<EkzDeleteImpactItem>();
AddImpactItem(impactItems, "EKZ", 1);
AddImpactItem(impactItems, "EKZMK", cardIds == null ? 0 : cardIds.Count);
AddImpactItem(impactItems, "DMS", CountEkzDms(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKFCTVL", CountEkzMkFctvl(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKDH", CountEkzMkDh(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKEKZK", CountEkzMkEkzk(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKND", CountEkzMkNd(connection, transaction, instrumentId));
AddImpactItem(impactItems, "KSPELEKZMK", CountKspelEkzMk(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMCP", CountEkzMcp(connection, transaction, instrumentId));
return OrderImpactItems(impactItems);
}
private static IReadOnlyList<EkzDeleteImpactItem> OrderImpactItems(IEnumerable<EkzDeleteImpactItem> impactItems)
{
var order = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
{ "EKZ", 1 },
{ "EKZMK", 2 },
{ "DMS", 3 },
{ "EKZMKFCTVL", 4 },
{ "EKZMKDH", 5 },
{ "EKZMKEKZK", 6 },
{ "EKZMKND", 7 },
{ "KSPELEKZMK", 8 },
{ "EKZMCP", 9 }
};
return (impactItems ?? Enumerable.Empty<EkzDeleteImpactItem>())
.Where(delegate(EkzDeleteImpactItem item) { return item != null && item.RowCount > 0; })
.OrderBy(delegate(EkzDeleteImpactItem item)
{
int value;
return order.TryGetValue(item.TableName ?? string.Empty, out value) ? value : int.MaxValue;
})
.ThenBy(delegate(EkzDeleteImpactItem item) { return item.TableName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static void AddImpactItem(ICollection<EkzDeleteImpactItem> impactItems, string tableName, int rowCount)
{
if (impactItems == null || string.IsNullOrWhiteSpace(tableName) || rowCount <= 0)
{
return;
}
impactItems.Add(new EkzDeleteImpactItem
{
TableName = tableName,
RowCount = rowCount
});
}
private static string CreateEkzDeleteConfirmationMessage(IEnumerable<EkzDeleteImpactItem> impactItems)
{
var items = OrderImpactItems(impactItems).ToList();
var lines = new List<string>();
if (items.Count == 0)
{
lines.Add("Будет физически удалена только запись EKZ.");
}
else
{
lines.Add("Будут физически удалены записи:");
foreach (var item in items)
{
lines.Add(string.Format("{0}: {1}", item.TableName, item.RowCount));
}
}
lines.Add(string.Empty);
lines.Add("Продолжить?");
return string.Join(Environment.NewLine, lines.ToArray());
}
private static string CreateEkzCascadeBlockedMessage(IEnumerable<DeleteBlockerInfo> blockers)
{
return string.Format(
"Экземпляр не может быть удалён автоматически. Есть связанные записи в таблицах, которые не входят в каскад удаления: {0}.",
FormatBlockerDetails(blockers));
}
private static string CreateEkzDeleteFailedMessage(SqlException ex)
{
var suffix = ex == null || string.IsNullOrWhiteSpace(ex.Message)
? string.Empty
: " " + ex.Message.Trim();
return "Экземпляр не может быть удалён из-за ограничений ссылочной целостности БД." + suffix;
}
private static string FormatBlockerDetails(IEnumerable<DeleteBlockerInfo> blockers)
{
var details = string.Join(", ", (blockers ?? Enumerable.Empty<DeleteBlockerInfo>())
.Where(delegate(DeleteBlockerInfo blocker) { return blocker != null && blocker.RowCount > 0; })
.OrderBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.Select(delegate(DeleteBlockerInfo blocker) { return string.Format("{0}: {1}", blocker.TableName, blocker.RowCount); }));
return string.IsNullOrWhiteSpace(details) ? "связанные данные" : details;
}
private static List<DeleteBlockerInfo> MergeBlockers(IEnumerable<DeleteBlockerInfo> blockers)
{
return (blockers ?? Enumerable.Empty<DeleteBlockerInfo>())
.Where(delegate(DeleteBlockerInfo blocker) { return blocker != null && blocker.RowCount > 0 && !string.IsNullOrWhiteSpace(blocker.TableName); })
.GroupBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, DeleteBlockerInfo> group)
{
return new DeleteBlockerInfo
{
TableName = group.Key,
RowCount = group.Sum(delegate(DeleteBlockerInfo blocker) { return blocker.RowCount; })
};
})
.OrderBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static List<DeleteBlockerInfo> LoadUnhandledDeleteBlockers(SqlConnection connection, SqlTransaction transaction, string parentTableName, IEnumerable<int> ids, IEnumerable<string> excludedChildTables)
{
var blockers = new List<DeleteBlockerInfo>();
foreach (var id in (ids ?? Enumerable.Empty<int>()).Where(delegate(int value) { return value > 0; }).Distinct())
{
blockers.AddRange(ReferenceDirectorySqlHelpers.LoadDeleteBlockersFromForeignKeys(connection, transaction, parentTableName, id, excludedChildTables));
}
return MergeBlockers(blockers);
}
private static bool EkzExists(SqlConnection connection, SqlTransaction transaction, int id)
{
const string sql = @"
SELECT COUNT(1)
FROM dbo.EKZ
WHERE IDEKZ = @Id;";
return ExecuteScalarForId(connection, transaction, sql, id) > 0;
}
private static List<int> LoadEkzMkCardIds(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
const string sql = @"
SELECT IDEKZMK
FROM dbo.EKZMK
WHERE IDEKZ = @InstrumentId
ORDER BY IDEKZMK;";
return ExecuteIdList(connection, transaction, sql, "@InstrumentId", instrumentId);
}
private static int CountEkzMcp(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMCP
WHERE IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkFctvl(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKFCTVL child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkDh(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKDH child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkEkzk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKEKZK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkNd(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKND child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountKspelEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.KSPELEKZMK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzDms(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.EKZMK m ON m.IDEKZMK = d.IDOD
WHERE m.IDEKZ = @InstrumentId
AND vdd.IDSPVDOD = 2;", instrumentId);
}
private static int DeleteEkzMcp(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE FROM dbo.EKZMCP
WHERE IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkFctvl(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKFCTVL child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkDh(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKDH child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkEkzk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKEKZK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkNd(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKND child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteKspelEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.KSPELEKZMK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzDms(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE d
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.EKZMK m ON m.IDEKZMK = d.IDOD
WHERE m.IDEKZ = @InstrumentId
AND vdd.IDSPVDOD = 2;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE FROM dbo.EKZMK
WHERE IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkz(SqlConnection connection, SqlTransaction transaction, int id)
{
return ExecuteScalarForId(connection, transaction, @"
DELETE FROM dbo.EKZ
WHERE IDEKZ = @Id;
SELECT @@ROWCOUNT;", id);
}
private static int ExecuteScalarForInstrument(SqlConnection connection, SqlTransaction transaction, string sql, int instrumentId)
{
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = instrumentId;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static int ExecuteScalarForId(SqlConnection connection, SqlTransaction transaction, string sql, int id)
{
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static List<int> ExecuteIdList(SqlConnection connection, SqlTransaction transaction, string sql, string parameterName, int parameterValue)
{
var result = new List<int>();
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add(parameterName, SqlDbType.Int).Value = parameterValue;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
result.Add(reader.GetInt32(0));
}
}
}
return result;
}
private static string NormalizeOptional(string value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
private static EkzDirectoryItem NormalizeEkzItem(EkzDirectoryItem item)
{
if (item == null)
{
throw new InvalidOperationException("Не переданы данные записи EKZ.");
}
var normalizedItem = new EkzDirectoryItem
{
Id = item.Id,
TypeSizeId = item.TypeSizeId,
OwnerOrganizationId = item.OwnerOrganizationId,
SerialNumber = NormalizeOptional(item.SerialNumber),
InventoryNumber = NormalizeOptional(item.InventoryNumber),
Notes = NormalizeOptional(item.Notes)
};
if (normalizedItem.TypeSizeId <= 0)
{
throw new InvalidOperationException("Не указан типоразмер СИ.");
}
if (normalizedItem.OwnerOrganizationId <= 0)
{
throw new InvalidOperationException("Не указана организация-владелец.");
}
if (string.IsNullOrWhiteSpace(normalizedItem.SerialNumber))
{
throw new InvalidOperationException("Не указан заводской номер.");
}
if (normalizedItem.SerialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength));
}
if (!string.IsNullOrWhiteSpace(normalizedItem.InventoryNumber) && normalizedItem.InventoryNumber.Length > EkzDirectoryRules.InventoryNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Инвентарный номер не должен превышать {0} символов.", EkzDirectoryRules.InventoryNumberMaxLength));
}
if (!string.IsNullOrWhiteSpace(normalizedItem.Notes) && normalizedItem.Notes.Length > EkzDirectoryRules.NotesMaxLength)
{
throw new InvalidOperationException(string.Format("Примечание не должно превышать {0} символов.", EkzDirectoryRules.NotesMaxLength));
}
return normalizedItem;
}
private static void EnsureEkzIsUnique(SqlConnection connection, int typeSizeId, int ownerOrganizationId, string serialNumber, int? excludeId)
{
const string sql = @"
SELECT TOP (1) z.IDEKZ
FROM dbo.EKZ z
WHERE z.IDTPRZ = @TypeSizeId
AND z.IDFRPDV = @OwnerOrganizationId
AND z.NNZV = @SerialNumber
AND ISNULL(z.IsDeleted, 0) = 0
AND (@ExcludeId IS NULL OR z.IDEKZ <> @ExcludeId)
ORDER BY z.IDEKZ DESC;";
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = ownerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = serialNumber;
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@ExcludeId", excludeId);
if (command.ExecuteScalar() != null)
{
throw new InvalidOperationException("Экземпляр с таким типоразмером, владельцем и заводским номером уже существует.");
}
}
}
}
}

View File

@@ -0,0 +1,176 @@
<Window x:Class="XLAB.EkzDirectoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Экземпляры"
Height="900"
Width="1540"
MinHeight="760"
MinWidth="1260"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="2.2*" />
<RowDefinition Height="1.6*" />
<RowDefinition Height="Auto" />
<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="Поиск" />
<TextBox Width="360"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DockPanel>
<StackPanel Grid.Row="1"
Margin="0,8,0,0"
Orientation="Horizontal">
<TextBlock Margin="0,0,8,0"
VerticalAlignment="Center"
Text="Организация-владелец" />
<ComboBox Width="420"
ItemsSource="{Binding OwnerFilterItems}"
SelectedValue="{Binding SelectedOwnerFilterId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
</Grid>
<GroupBox Grid.Row="1"
Header="Экземпляры (EKZ)">
<DataGrid ItemsSource="{Binding EkzItems}"
SelectedItem="{Binding SelectedEkz, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddEkzCommand}" />
<MenuItem Header="Изменить"
Command="{Binding EditEkzCommand}" />
<MenuItem Header="Удалить"
Command="{Binding DeleteEkzCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Организация-владелец"
Width="220"
Binding="{Binding OwnerOrganizationName}" />
<DataGridTextColumn Header="Область измерений"
Width="160"
Binding="{Binding MeasurementAreaName}" />
<DataGridTextColumn Header="Наименование"
Width="220"
Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип"
Width="180"
Binding="{Binding TypeName}" />
<DataGridTextColumn Header="Диапазон"
Width="220"
Binding="{Binding RangeText}" />
<DataGridTextColumn Header="Х-ка точности"
Width="150"
Binding="{Binding AccuracyText}" />
<DataGridTextColumn Header="№ Госреестра"
Width="120"
Binding="{Binding RegistryNumber}" />
<DataGridTextColumn Header="Заводской номер"
Width="140"
Binding="{Binding SerialNumber}" />
<DataGridTextColumn Header="Инвентарный номер"
Width="140"
Binding="{Binding InventoryNumber}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<GroupBox Grid.Row="2"
Margin="0,12,0,0"
Header="МК выбранного экземпляра (EKZMK)">
<DataGrid ItemsSource="{Binding EkzMkItems}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="ПСВ/Акт-справка"
Width="220"
Binding="{Binding DocumentNumber}" />
<DataGridTextColumn Header="Документ по поверке"
Width="180"
Binding="{Binding VerificationDocumentDisplay}" />
<DataGridTextColumn Header="Номер наклейки"
Width="140"
Binding="{Binding StickerNumber}" />
<DataGridTextColumn Header="Поверитель"
Width="180"
Binding="{Binding VerifierName}" />
<DataGridTextColumn Header="Вид МК"
Width="120"
Binding="{Binding VerificationTypeName}" />
<DataGridTextColumn Header="Организация"
Width="220"
Binding="{Binding VerificationOrganizationName}" />
<DataGridTextColumn Header="Период, мес."
Width="95"
Binding="{Binding PeriodMonths}" />
<DataGridTextColumn Header="Принят"
Width="95"
Binding="{Binding AcceptedOn, StringFormat=d}" />
<DataGridTextColumn Header="План"
Width="95"
Binding="{Binding PlannedOn, StringFormat=d}" />
<DataGridTextColumn Header="Поверен"
Width="95"
Binding="{Binding PerformedOn, StringFormat=d}" />
<DataGridTextColumn Header="Выдан"
Width="95"
Binding="{Binding IssuedOn, StringFormat=d}" />
<DataGridTextColumn Header="Результат"
Width="95"
Binding="{Binding ResultText}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<TextBlock Grid.Row="3"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<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 EkzDirectoryWindow : Window
{
private readonly EkzDirectoryWindowViewModel _viewModel;
public EkzDirectoryWindow()
{
InitializeComponent();
_viewModel = new EkzDirectoryWindowViewModel(new EkzDirectoryService(), new EkzDirectoryDialogService(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,568 @@
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 EkzDirectoryWindowViewModel : ObservableObject
{
private readonly IEkzDirectoryDialogService _dialogService;
private readonly EkzDirectoryService _service;
private List<EkzDirectoryItem> _ekzCache;
private bool _isApplyingFilter;
private bool _isBusy;
private string _searchText;
private EkzDirectoryItem _selectedEkz;
private int _selectedOwnerFilterId;
private string _statusText;
public EkzDirectoryWindowViewModel(EkzDirectoryService service, IEkzDirectoryDialogService dialogService)
{
_service = service;
_dialogService = dialogService;
_ekzCache = new List<EkzDirectoryItem>();
EkzItems = new ObservableCollection<EkzDirectoryItem>();
EkzMkItems = new ObservableCollection<EkzMkDirectoryItem>();
OwnerFilterItems = new ObservableCollection<DirectoryLookupItem>();
OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" });
AddEkzCommand = new RelayCommand(delegate { AddEkzAsync(); }, delegate { return !IsBusy; });
EditEkzCommand = new RelayCommand(delegate { EditEkzAsync(); }, delegate { return !IsBusy && SelectedEkz != null; });
DeleteEkzCommand = new RelayCommand(delegate { DeleteEkzWithPreviewAsync(); }, delegate { return !IsBusy && SelectedEkz != null; });
RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });
UpdateStatus();
}
public ICommand AddEkzCommand { get; private set; }
public ICommand DeleteEkzCommand { get; private set; }
public ICommand EditEkzCommand { get; private set; }
public ObservableCollection<EkzDirectoryItem> EkzItems { get; private set; }
public ObservableCollection<EkzMkDirectoryItem> EkzMkItems { get; private set; }
public bool IsBusy
{
get { return _isBusy; }
private set
{
if (SetProperty(ref _isBusy, value))
{
RaiseCommandStates();
}
}
}
public ObservableCollection<DirectoryLookupItem> OwnerFilterItems { get; private set; }
public ICommand RefreshCommand { get; private set; }
public string SearchText
{
get { return _searchText; }
set
{
if (SetProperty(ref _searchText, value))
{
ApplyFilter(SelectedEkz == null ? (int?)null : SelectedEkz.Id);
}
}
}
public EkzDirectoryItem SelectedEkz
{
get { return _selectedEkz; }
set
{
if (SetProperty(ref _selectedEkz, value))
{
RaiseCommandStates();
if (!_isApplyingFilter)
{
LoadEkzMkForSelection();
}
UpdateStatus();
}
}
}
public int SelectedOwnerFilterId
{
get { return _selectedOwnerFilterId; }
set
{
if (SetProperty(ref _selectedOwnerFilterId, value))
{
ApplyFilter(SelectedEkz == null ? (int?)null : SelectedEkz.Id);
}
}
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public async Task InitializeAsync()
{
await ExecuteBusyOperationAsync(delegate { return RefreshCoreAsync(null); });
}
private void AddEkzAsync()
{
var result = _dialogService.ShowEkzEditDialog(new EkzDirectoryItem(), true, _ekzCache.ToList(), _service);
if (result == null)
{
return;
}
RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _service.AddEkzItem(result); });
await RefreshCoreAsync(createdId);
_dialogService.ShowInfo("Запись EKZ добавлена.");
});
}
private void ApplyFilter(int? preferredId)
{
var filteredItems = _ekzCache.Where(delegate(EkzDirectoryItem item)
{
return MatchesOwnerFilter(item) && MatchesSearch(item);
}).ToList();
_isApplyingFilter = true;
try
{
EkzItems.Clear();
foreach (var item in filteredItems)
{
EkzItems.Add(item);
}
SelectedEkz = preferredId.HasValue
? EkzItems.FirstOrDefault(delegate(EkzDirectoryItem item) { return item.Id == preferredId.Value; })
: EkzItems.FirstOrDefault();
}
finally
{
_isApplyingFilter = false;
}
if (!IsBusy)
{
LoadEkzMkForSelection();
}
UpdateStatus();
}
private static EkzDirectoryItem CloneEkz(EkzDirectoryItem source)
{
return new EkzDirectoryItem
{
Id = source.Id,
TypeSizeId = source.TypeSizeId,
MeasurementAreaName = source.MeasurementAreaName,
InstrumentName = source.InstrumentName,
TypeName = source.TypeName,
RangeText = source.RangeText,
AccuracyText = source.AccuracyText,
RegistryNumber = source.RegistryNumber,
OwnerOrganizationId = source.OwnerOrganizationId,
OwnerOrganizationName = source.OwnerOrganizationName,
SerialNumber = source.SerialNumber,
InventoryNumber = source.InventoryNumber,
StickerNumbers = source.StickerNumbers,
Notes = source.Notes
};
}
private async void DeleteEkzWithPreviewAsync()
{
if (SelectedEkz == null)
{
return;
}
var selected = SelectedEkz;
EkzDeletePreview preview;
try
{
IsBusy = true;
preview = await Task.Run(delegate { return _service.GetEkzDeletePreview(selected.Id); });
}
catch (InvalidOperationException ex)
{
_dialogService.ShowWarning(ex.Message);
return;
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
return;
}
finally
{
IsBusy = false;
}
if (preview == null)
{
return;
}
if (!preview.CanDelete)
{
_dialogService.ShowWarning(preview.WarningMessage);
return;
}
if (!_dialogService.Confirm(BuildDeleteConfirmationMessage(selected, preview)))
{
return;
}
RunMutationOperation(async delegate
{
var result = await Task.Run(delegate { return _service.DeleteEkzItem(selected.Id); });
if (!result.IsDeleted)
{
_dialogService.ShowWarning(result.WarningMessage);
return;
}
await RefreshCoreAsync(null);
_dialogService.ShowInfo(BuildDeleteResultMessage(result));
});
}
private async void DeleteEkzAsync()
{
if (SelectedEkz == null)
{
return;
}
var selected = SelectedEkz;
EkzDeletePreview preview;
try
{
IsBusy = true;
preview = await Task.Run(delegate { return _service.GetEkzDeletePreview(selected.Id); });
}
catch (InvalidOperationException ex)
{
_dialogService.ShowWarning(ex.Message);
return;
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
return;
}
finally
{
IsBusy = false;
}
if (preview == null)
{
return;
}
if (!preview.CanDelete)
{
_dialogService.ShowWarning(preview.WarningMessage);
return;
}
if (!_dialogService.Confirm(string.Format("Удалить экземпляр \"{0}\"?", selected.SerialNumber)))
{
return;
}
RunMutationOperation(async delegate
{
var result = await Task.Run(delegate { return _service.DeleteEkzItem(selected.Id); });
if (!result.IsDeleted)
{
_dialogService.ShowWarning(result.WarningMessage);
return;
}
await RefreshCoreAsync(null);
_dialogService.ShowInfo("Запись EKZ удалена.");
});
}
private void EditEkzAsync()
{
if (SelectedEkz == null)
{
return;
}
var result = _dialogService.ShowEkzEditDialog(CloneEkz(SelectedEkz), false, _ekzCache.ToList(), _service);
if (result == null)
{
return;
}
RunMutationOperation(async delegate
{
await Task.Run(delegate { _service.UpdateEkzItem(result); });
await RefreshCoreAsync(result.Id);
_dialogService.ShowInfo("Запись EKZ обновлена.");
});
}
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 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 LoadEkzMkForSelection()
{
if (IsBusy)
{
return;
}
RunBusyOperation(async delegate
{
if (SelectedEkz == null)
{
EkzMkItems.Clear();
UpdateStatus();
return;
}
await RefreshEkzMkCoreAsync(SelectedEkz.Id);
});
}
private bool MatchesOwnerFilter(EkzDirectoryItem item)
{
return SelectedOwnerFilterId <= 0
|| (item != null && item.OwnerOrganizationId == SelectedOwnerFilterId);
}
private bool MatchesSearch(EkzDirectoryItem item)
{
var tokens = GetSearchTokens();
if (tokens.Length == 0)
{
return true;
}
var haystack = string.Join(
" ",
new[]
{
item == null ? null : item.Id.ToString(),
item == null ? null : item.OwnerOrganizationName,
item == null ? null : item.MeasurementAreaName,
item == null ? null : item.InstrumentName,
item == null ? null : item.TypeName,
item == null ? null : item.RangeText,
item == null ? null : item.AccuracyText,
item == null ? null : item.RegistryNumber,
item == null ? null : item.SerialNumber,
item == null ? null : item.InventoryNumber,
item == null ? null : item.StickerNumbers,
item == null ? null : item.Notes
}.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); }))
.ToUpperInvariant();
return tokens.All(delegate(string token) { return haystack.IndexOf(token, StringComparison.Ordinal) >= 0; });
}
private async Task RefreshCoreAsync(int? idToSelect)
{
var currentSelectedId = idToSelect ?? (SelectedEkz == null ? (int?)null : SelectedEkz.Id);
var currentOwnerFilterId = SelectedOwnerFilterId;
var ekzTask = Task.Run(delegate { return _service.LoadEkzItems(); });
var ownerTask = Task.Run(delegate { return _service.LoadFrpdReferences(); });
await Task.WhenAll(ekzTask, ownerTask);
_ekzCache = ekzTask.Result.ToList();
RebuildOwnerFilters(ownerTask.Result, currentOwnerFilterId);
ApplyFilter(currentSelectedId);
if (SelectedEkz == null)
{
EkzMkItems.Clear();
UpdateStatus();
return;
}
await RefreshEkzMkCoreAsync(SelectedEkz.Id);
UpdateStatus();
}
private async Task RefreshEkzMkCoreAsync(int instrumentId)
{
var items = await Task.Run(delegate { return _service.LoadEkzMkItems(instrumentId); });
EkzMkItems.Clear();
foreach (var item in items)
{
EkzMkItems.Add(item);
}
}
private void RebuildOwnerFilters(IReadOnlyList<DirectoryLookupItem> owners, int selectedId)
{
OwnerFilterItems.Clear();
OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" });
foreach (var owner in owners ?? Array.Empty<DirectoryLookupItem>())
{
OwnerFilterItems.Add(owner);
}
_selectedOwnerFilterId = OwnerFilterItems.Any(delegate(DirectoryLookupItem item) { return item.Id == selectedId; })
? selectedId
: 0;
OnPropertyChanged("SelectedOwnerFilterId");
}
private void RefreshAsync()
{
RunBusyOperation(delegate { return RefreshCoreAsync(SelectedEkz == null ? (int?)null : SelectedEkz.Id); });
}
private void RaiseCommandStates()
{
((RelayCommand)AddEkzCommand).RaiseCanExecuteChanged();
((RelayCommand)EditEkzCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteEkzCommand).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 static string BuildDeleteConfirmationMessage(EkzDirectoryItem selected, EkzDeletePreview preview)
{
return string.Format(
"Удалить экземпляр \"{0}\"?{1}{1}{2}",
selected == null || string.IsNullOrWhiteSpace(selected.SerialNumber) ? "(без номера)" : selected.SerialNumber,
Environment.NewLine,
preview == null ? string.Empty : preview.ConfirmationMessage ?? string.Empty);
}
private static string BuildDeleteResultMessage(EkzDeleteResult result)
{
var impacts = result == null || result.ImpactItems == null
? new List<EkzDeleteImpactItem>()
: result.ImpactItems.Where(delegate(EkzDeleteImpactItem item) { return item != null && item.RowCount > 0; }).ToList();
if (impacts.Count == 0)
{
return "Запись EKZ удалена.";
}
return "Удалены записи: " + string.Join(", ", impacts.Select(delegate(EkzDeleteImpactItem item)
{
return string.Format("{0}: {1}", item.TableName, item.RowCount);
})) + ".";
}
private void UpdateStatus()
{
var searchPrefix = string.IsNullOrWhiteSpace(SearchText)
? string.Empty
: string.Format("Поиск: \"{0}\". ", SearchText.Trim());
var ownerName = OwnerFilterItems.FirstOrDefault(delegate(DirectoryLookupItem item) { return item.Id == SelectedOwnerFilterId; });
var ownerPrefix = SelectedOwnerFilterId <= 0 || ownerName == null
? string.Empty
: string.Format("Владелец: \"{0}\". ", ownerName.Name);
StatusText = string.Format(
"{0}{1}EKZ: {2}/{3}. EKZMK: {4}.",
searchPrefix,
ownerPrefix,
EkzItems.Count,
_ekzCache.Count,
EkzMkItems.Count);
}
}
}

109
XLAB/EkzEditWindow.xaml Normal file
View File

@@ -0,0 +1,109 @@
<Window x:Class="XLAB.EkzEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="340"
Width="860"
MinHeight="340"
MinWidth="760"
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="*" />
</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 TypeSizeItems}"
SelectedValue="{Binding TypeSizeId}"
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 OwnerItems}"
SelectedValue="{Binding OwnerOrganizationId}"
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 SerialNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="3"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Инвентарный номер" />
<TextBox Grid.Row="3"
Grid.Column="1"
Margin="0,0,0,8"
Text="{Binding InventoryNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="4"
Grid.Column="0"
Margin="0,0,12,0"
VerticalAlignment="Top"
Text="Примечание" />
<TextBox Grid.Row="4"
Grid.Column="1"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
Text="{Binding Notes, 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 EkzEditWindow : Window
{
internal EkzEditWindow(EkzEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB
{
internal sealed class EkzEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<EkzDirectoryItem> _existingItems;
private string _inventoryNumber;
private string _notes;
private int _ownerOrganizationId;
private string _serialNumber;
private int _typeSizeId;
private string _validationMessage;
public EkzEditWindowViewModel(EkzDirectoryItem seed, bool isNew, IReadOnlyList<EkzDirectoryItem> existingItems, EkzDirectoryService service)
{
var source = seed ?? new EkzDirectoryItem();
_existingItems = existingItems ?? Array.Empty<EkzDirectoryItem>();
Id = source.Id;
IsNew = isNew;
TypeSizeItems = service.LoadTypeSizeReferences();
OwnerItems = service.LoadFrpdReferences();
TypeSizeId = source.TypeSizeId;
OwnerOrganizationId = source.OwnerOrganizationId;
SerialNumber = source.SerialNumber ?? string.Empty;
InventoryNumber = source.InventoryNumber ?? string.Empty;
Notes = source.Notes ?? 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 int Id { get; private set; }
public bool IsNew { get; private set; }
public string InventoryNumber
{
get { return _inventoryNumber; }
set { SetProperty(ref _inventoryNumber, value); }
}
public string Notes
{
get { return _notes; }
set { SetProperty(ref _notes, value); }
}
public IReadOnlyList<DirectoryLookupItem> OwnerItems { get; private set; }
public int OwnerOrganizationId
{
get { return _ownerOrganizationId; }
set { SetProperty(ref _ownerOrganizationId, value); }
}
public string SerialNumber
{
get { return _serialNumber; }
set { SetProperty(ref _serialNumber, value); }
}
public string Title
{
get { return IsNew ? "Новый экземпляр" : "Редактирование экземпляра"; }
}
public IReadOnlyList<DirectoryLookupItem> TypeSizeItems { get; private set; }
public int TypeSizeId
{
get { return _typeSizeId; }
set { SetProperty(ref _typeSizeId, value); }
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public EkzDirectoryItem ToResult()
{
return new EkzDirectoryItem
{
Id = Id,
TypeSizeId = TypeSizeId,
OwnerOrganizationId = OwnerOrganizationId,
SerialNumber = Normalize(SerialNumber),
InventoryNumber = Normalize(InventoryNumber),
Notes = Normalize(Notes)
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
var serialNumber = Normalize(SerialNumber);
var inventoryNumber = Normalize(InventoryNumber);
var notes = Normalize(Notes);
if (TypeSizeId <= 0)
{
ValidationMessage = "Укажите типоразмер СИ.";
return;
}
if (OwnerOrganizationId <= 0)
{
ValidationMessage = "Укажите организацию-владельца.";
return;
}
if (string.IsNullOrWhiteSpace(serialNumber))
{
ValidationMessage = "Укажите заводской номер.";
return;
}
if (serialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
ValidationMessage = string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength);
return;
}
if (!string.IsNullOrWhiteSpace(inventoryNumber) && inventoryNumber.Length > EkzDirectoryRules.InventoryNumberMaxLength)
{
ValidationMessage = string.Format("Инвентарный номер не должен превышать {0} символов.", EkzDirectoryRules.InventoryNumberMaxLength);
return;
}
if (!string.IsNullOrWhiteSpace(notes) && notes.Length > EkzDirectoryRules.NotesMaxLength)
{
ValidationMessage = string.Format("Примечание не должно превышать {0} символов.", EkzDirectoryRules.NotesMaxLength);
return;
}
var duplicate = _existingItems.FirstOrDefault(delegate(EkzDirectoryItem item)
{
return item != null
&& item.Id != Id
&& item.TypeSizeId == TypeSizeId
&& item.OwnerOrganizationId == OwnerOrganizationId
&& string.Equals(item.SerialNumber ?? string.Empty, serialNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
});
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);
}
}
}
}

View File

@@ -11,6 +11,9 @@ namespace XLAB
public int AddFrpdItem(FrpdDirectoryItem item) public int AddFrpdItem(FrpdDirectoryItem item)
{ {
var normalizedItem = NormalizeFrpdItem(item); var normalizedItem = NormalizeFrpdItem(item);
var guidForInsert = string.IsNullOrWhiteSpace(normalizedItem.Guid)
? Guid.NewGuid().ToString().ToUpperInvariant()
: normalizedItem.Guid;
const string sql = @" const string sql = @"
INSERT INTO dbo.FRPD INSERT INTO dbo.FRPD
@@ -38,13 +41,13 @@ SELECT CAST(SCOPE_IDENTITY() AS int);";
using (var command = new SqlCommand(sql, connection)) using (var command = new SqlCommand(sql, connection))
{ {
connection.Open(); connection.Open();
EnsureFrpdGuidIsUnique(connection, normalizedItem.Guid, null); EnsureFrpdGuidIsUnique(connection, guidForInsert, null);
command.CommandTimeout = 60; command.CommandTimeout = 60;
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@ParentId", normalizedItem.ParentId); ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@ParentId", normalizedItem.ParentId);
command.Parameters.Add("@Name", SqlDbType.VarChar, FrpdDirectoryRules.NameMaxLength).Value = normalizedItem.Name; command.Parameters.Add("@Name", SqlDbType.VarChar, FrpdDirectoryRules.NameMaxLength).Value = normalizedItem.Name;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@LocalCode", SqlDbType.VarChar, FrpdDirectoryRules.LocalCodeMaxLength, normalizedItem.LocalCode); ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@LocalCode", SqlDbType.VarChar, FrpdDirectoryRules.LocalCodeMaxLength, normalizedItem.LocalCode);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Guid", SqlDbType.VarChar, FrpdDirectoryRules.GuidMaxLength, normalizedItem.Guid); ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Guid", SqlDbType.VarChar, FrpdDirectoryRules.GuidMaxLength, guidForInsert);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@CreatedOn", normalizedItem.CreatedOn); ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@CreatedOn", normalizedItem.CreatedOn);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@LiquidatedOn", normalizedItem.LiquidatedOn); ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@LiquidatedOn", normalizedItem.LiquidatedOn);
@@ -54,7 +57,7 @@ SELECT CAST(SCOPE_IDENTITY() AS int);";
} }
catch (SqlException ex) when (ReferenceDirectorySqlHelpers.IsDuplicateViolation(ex, "IX_FRPD_FRPDGUID")) catch (SqlException ex) when (ReferenceDirectorySqlHelpers.IsDuplicateViolation(ex, "IX_FRPD_FRPDGUID"))
{ {
throw CreateFrpdDuplicateGuidException(normalizedItem.Guid); throw CreateFrpdDuplicateGuidException(guidForInsert);
} }
} }
} }

View File

@@ -27,14 +27,14 @@
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Margin="0,0,8,0" <TextBlock Margin="0,0,8,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Поиск по FRPD" /> Text="Поиск " />
<TextBox Width="360" <TextBox Width="360"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" /> Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
<GroupBox Grid.Row="1" <GroupBox Grid.Row="1"
Header="Организации и подразделения (FRPD)"> Header="Организации и подразделения">
<DataGrid ItemsSource="{Binding FrpdItems}" <DataGrid ItemsSource="{Binding FrpdItems}"
SelectedItem="{Binding SelectedFrpd, Mode=TwoWay}" SelectedItem="{Binding SelectedFrpd, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
@@ -58,21 +58,15 @@
</Style> </Style>
</DataGrid.RowStyle> </DataGrid.RowStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="80" Binding="{Binding Id}" /> <DataGridTextColumn Header="Наименование" Width="*" Binding="{Binding Name}" />
<DataGridTextColumn Header="Организация/подразделение" Width="*" Binding="{Binding Name}" />
<DataGridTextColumn Header="Родительская запись" Width="240" Binding="{Binding ParentName}" />
<DataGridTextColumn Header="Локальный код" Width="150" Binding="{Binding LocalCode}" /> <DataGridTextColumn Header="Локальный код" Width="150" Binding="{Binding LocalCode}" />
<DataGridTextColumn Header="GUID" Width="220" Binding="{Binding Guid}" />
<DataGridTextColumn Header="Дата создания" Width="120" Binding="{Binding CreatedOn, StringFormat=d}" />
<DataGridTextColumn Header="Дата ликвидации" Width="130" Binding="{Binding LiquidatedOn, StringFormat=d}" />
<DataGridTextColumn Header="Виды деятельности" Width="260" Binding="{Binding ActivityNames}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</GroupBox> </GroupBox>
<GroupBox Grid.Row="2" <GroupBox Grid.Row="2"
Margin="0,12,0,0" Margin="0,12,0,0"
Header="Виды деятельности организации/подразделения (FRPDVD)"> Header="Виды деятельности подразделения">
<DataGrid ItemsSource="{Binding FrpdvdItems}" <DataGrid ItemsSource="{Binding FrpdvdItems}"
SelectedItem="{Binding SelectedFrpdvd, Mode=TwoWay}" SelectedItem="{Binding SelectedFrpdvd, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
@@ -96,7 +90,6 @@
</Style> </Style>
</DataGrid.RowStyle> </DataGrid.RowStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="100" Binding="{Binding Id}" />
<DataGridTextColumn Header="Вид деятельности" Width="*" Binding="{Binding ActivityName}" /> <DataGridTextColumn Header="Вид деятельности" Width="*" Binding="{Binding ActivityName}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>

View File

@@ -219,7 +219,7 @@ namespace XLAB
} }
await RefreshFrpdCoreAsync(null, null); await RefreshFrpdCoreAsync(null, null);
_dialogService.ShowInfo("Запись FRPD удалена."); _dialogService.ShowInfo("Запись удалена.");
}); });
} }
@@ -246,7 +246,7 @@ namespace XLAB
} }
await RefreshFrpdCoreAsync(selected.FrpdId, null); await RefreshFrpdCoreAsync(selected.FrpdId, null);
_dialogService.ShowInfo("Запись FRPDVD удалена."); _dialogService.ShowInfo("Запись удалена.");
}); });
} }
@@ -305,7 +305,7 @@ namespace XLAB
{ {
await Task.Run(delegate { _service.UpdateFrpdItem(result); }); await Task.Run(delegate { _service.UpdateFrpdItem(result); });
await RefreshFrpdCoreAsync(result.Id, SelectedFrpdvd == null ? (int?)null : SelectedFrpdvd.Id); await RefreshFrpdCoreAsync(result.Id, SelectedFrpdvd == null ? (int?)null : SelectedFrpdvd.Id);
_dialogService.ShowInfo("Запись FRPD обновлена."); _dialogService.ShowInfo("Запись обновлена.");
}); });
} }
@@ -326,7 +326,7 @@ namespace XLAB
{ {
await Task.Run(delegate { _service.UpdateFrpdvdItem(result); }); await Task.Run(delegate { _service.UpdateFrpdvdItem(result); });
await RefreshFrpdCoreAsync(result.FrpdId, result.Id); await RefreshFrpdCoreAsync(result.FrpdId, result.Id);
_dialogService.ShowInfo("Запись FRPDVD обновлена."); _dialogService.ShowInfo("Запись обновлена.");
}); });
} }
@@ -466,7 +466,7 @@ namespace XLAB
{ {
var searchText = string.IsNullOrWhiteSpace(SearchText) ? null : SearchText.Trim(); var searchText = string.IsNullOrWhiteSpace(SearchText) ? null : SearchText.Trim();
StatusText = string.Format( StatusText = string.Format(
"{0}FRPD: {1}/{2}. FRPDVD: {3}.", "{0}Подразделений: {1}/{2}. Видов деятельности: {3}.",
string.IsNullOrWhiteSpace(searchText) ? string.Empty : string.Format("Поиск: \"{0}\". ", searchText), string.IsNullOrWhiteSpace(searchText) ? string.Empty : string.Format("Поиск: \"{0}\". ", searchText),
FrpdItems.Count, FrpdItems.Count,
_frpdCache.Count, _frpdCache.Count,

View File

@@ -2,16 +2,15 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}" Title="{Binding Title}"
Height="360" Height="220"
Width="680" Width="680"
MinHeight="340" MinHeight="220"
MinWidth="620" MinWidth="620"
WindowStartupLocation="CenterOwner"> WindowStartupLocation="CenterOwner">
<Grid Margin="16"> <Grid Margin="16">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="0"> <Grid Grid.Row="0">
@@ -23,13 +22,10 @@
<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> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Родительская запись" /> <!--<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 ParentItems}" SelectedValue="{Binding ParentId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" /> <ComboBox Grid.Row="0" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding ParentItems}" SelectedValue="{Binding ParentId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />-->
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Организация/подразделение" /> <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}" /> <TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,8" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
@@ -37,14 +33,9 @@
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Локальный код" /> <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 LocalCode, UpdateSourceTrigger=PropertyChanged}" /> <TextBox Grid.Row="2" Grid.Column="1" Margin="0,0,0,8" Text="{Binding LocalCode, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="3" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="GUID подразделения" />
<TextBox Grid.Row="3" Grid.Column="1" Margin="0,0,0,8" Text="{Binding Guid, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="4" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Дата создания" />
<DatePicker Grid.Row="4" Grid.Column="1" Margin="0,0,0,8" SelectedDate="{Binding CreatedOn, Mode=TwoWay}" SelectedDateFormat="Short" />
<TextBlock Grid.Row="5" Grid.Column="0" Margin="0,0,12,0" VerticalAlignment="Center" Text="Дата ликвидации" />
<DatePicker Grid.Row="5" Grid.Column="1" SelectedDate="{Binding LiquidatedOn, Mode=TwoWay}" SelectedDateFormat="Short" />
</Grid> </Grid>
<TextBlock Grid.Row="1" Margin="0,12,0,0" Foreground="Firebrick" Text="{Binding ValidationMessage}" /> <TextBlock Grid.Row="1" Margin="0,12,0,0" Foreground="Firebrick" Text="{Binding ValidationMessage}" />

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows;
using System.Windows.Input; using System.Windows.Input;
namespace XLAB namespace XLAB
@@ -48,6 +49,11 @@ namespace XLAB
set { SetProperty(ref _guid, value); } set { SetProperty(ref _guid, value); }
} }
public Visibility GuidFieldVisibility
{
get { return IsNew ? Visibility.Collapsed : Visibility.Visible; }
}
public int Id { get; private set; } public int Id { get; private set; }
public bool IsNew { get; private set; } public bool IsNew { get; private set; }
public DateTime? LiquidatedOn { get; set; } public DateTime? LiquidatedOn { get; set; }

View File

@@ -1,4 +1,4 @@
<Window x:Class="XLAB.MainWindow" <Window x:Class="XLAB.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Приемо-сдаточные ведомости" Title="Приемо-сдаточные ведомости"
@@ -8,6 +8,22 @@
MinWidth="1180" MinWidth="1180"
WindowState="Maximized" WindowState="Maximized"
Loaded="Window_Loaded"> Loaded="Window_Loaded">
<Window.Resources>
<Style x:Key="GhostGridSplitterStyle" TargetType="GridSplitter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="ShowsPreview" Value="True" />
<Setter Property="Opacity" Value="1" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#D7DADF" />
<Setter Property="BorderBrush" Value="#BCC1C7" />
<Setter Property="BorderThickness" Value="1" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Margin="12"> <Grid Margin="12">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@@ -28,6 +44,12 @@
Click="PrsnDirectoryMenuItem_Click" /> Click="PrsnDirectoryMenuItem_Click" />
<MenuItem Header="Типоразмеры" <MenuItem Header="Типоразмеры"
Click="TypeSizeDirectoryMenuItem_Click" /> Click="TypeSizeDirectoryMenuItem_Click" />
<MenuItem Header="Экземпляры"
Click="EkzDirectoryMenuItem_Click" />
<MenuItem Header="Планирование"
Click="PlanningMenuItem_Click" />
<MenuItem Header="Отчеты"
Click="VerificationReportsMenuItem_Click" />
</Menu> </Menu>
<Grid Grid.Row="1"> <Grid Grid.Row="1">
@@ -97,7 +119,7 @@
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<Border Padding="8" <Border Padding="8"
BorderBrush="#DDD" BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="0,0,0,1"> BorderThickness="0,0,0,1">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -148,9 +170,16 @@
Foreground="DimGray" Foreground="DimGray"
Text="{Binding AcceptedOn, StringFormat=d}" /> Text="{Binding AcceptedOn, StringFormat=d}" />
</DockPanel> </DockPanel>
<TextBlock Margin="0,4,0,0" <DockPanel Margin="0,4,0,0"
LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="DimGray" Foreground="DimGray"
Text="{Binding CustomerName}" /> Text="{Binding TimelineDisplay}" />
<TextBlock Margin="0,0,12,0"
Foreground="DimGray"
Text="{Binding CustomerName}"
TextTrimming="CharacterEllipsis" />
</DockPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>
@@ -165,10 +194,19 @@
</Grid> </Grid>
</GroupBox> </GroupBox>
<GridSplitter Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ResizeDirection="Columns"
ResizeBehavior="PreviousAndNext"
Style="{StaticResource GhostGridSplitterStyle}"
Cursor="SizeWE" />
<Grid Grid.Column="2"> <Grid Grid.Column="2">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="10" />
<RowDefinition Height="340" /> <RowDefinition Height="340" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
@@ -250,6 +288,19 @@
SelectedValue="{Binding SelectedCustomerId, Mode=TwoWay}" SelectedValue="{Binding SelectedCustomerId, Mode=TwoWay}"
IsEnabled="{Binding IsCustomerEditable}" /> IsEnabled="{Binding IsCustomerEditable}" />
<TextBlock Grid.Row="1"
Grid.Column="4"
Margin="0,0,8,6"
VerticalAlignment="Center"
Text="Приборов в ПСВ" />
<TextBlock Grid.Row="1"
Grid.Column="5"
Grid.ColumnSpan="2"
Margin="0,0,0,6"
VerticalAlignment="Center"
FontWeight="SemiBold"
Text="{Binding HeaderInstrumentCount}" />
<TextBlock Grid.Row="2" <TextBlock Grid.Row="2"
Grid.ColumnSpan="7" Grid.ColumnSpan="7"
Foreground="DimGray" Foreground="DimGray"
@@ -259,7 +310,28 @@
<GroupBox Grid.Row="1" Header="Группы приборов выбранного документа"> <GroupBox Grid.Row="1" Header="Группы приборов выбранного документа">
<Grid Margin="8"> <Grid Margin="8">
<DataGrid ItemsSource="{Binding DocumentGroupSummaries}" <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="290" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding GroupFilterText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Column="1"
Margin="12,0,0,0"
VerticalAlignment="Center"
Foreground="DimGray"
Text="Поиск по наименованию, типу, диапазону, характеристикам, госреестру или зав. №" />
</Grid>
<DataGrid Grid.Row="1"
ItemsSource="{Binding DocumentGroupsView}"
SelectedItem="{Binding SelectedDocumentGroup, Mode=TwoWay}" SelectedItem="{Binding SelectedDocumentGroup, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"
@@ -293,12 +365,18 @@
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTextColumn Header="Наименование"
Width="220"
Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип" <DataGridTextColumn Header="Тип"
Width="180" Width="160"
Binding="{Binding InstrumentType}" /> Binding="{Binding InstrumentType}" />
<DataGridTextColumn Header="Диапазон" <DataGridTextColumn Header="Диапазон"
Width="170" Width="160"
Binding="{Binding RangeText}" /> Binding="{Binding RangeText}" />
<DataGridTextColumn Header="Характеристики"
Width="160"
Binding="{Binding AccuracyText}" />
<DataGridTextColumn Header="Госреестр" <DataGridTextColumn Header="Госреестр"
Width="120" Width="120"
Binding="{Binding RegistryNumber}" /> Binding="{Binding RegistryNumber}" />
@@ -319,28 +397,48 @@
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Grid.Row="2" Header="Состав выбранной группы"> <GridSplitter Grid.Row="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ResizeDirection="Rows"
ResizeBehavior="PreviousAndNext"
Style="{StaticResource GhostGridSplitterStyle}"
Cursor="SizeNS" />
<GroupBox Grid.Row="3" Header="Состав выбранной группы">
<Grid Margin="8"> <Grid Margin="8">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<WrapPanel Margin="0,0,0,8"> <Grid Margin="0,0,0,8">
<TextBlock Width="140" <Grid.ColumnDefinitions>
<ColumnDefinition Width="140" />
<ColumnDefinition Width="320" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Margin="0,0,6,0" Margin="0,0,6,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Поиск по зав. №" /> Text="Поиск по зав. №" />
<TextBox Width="320" <TextBox Grid.Column="1"
Text="{Binding GroupDetailFilterText, UpdateSourceTrigger=PropertyChanged}" /> Text="{Binding GroupDetailFilterText, UpdateSourceTrigger=PropertyChanged}" />
</WrapPanel> <TextBlock Grid.Column="2"
Margin="12,0,0,0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Foreground="DimGray"
Text="{Binding DetailTableCountText}" />
</Grid>
<DataGrid Grid.Row="1" <DataGrid Grid.Row="1"
ItemsSource="{Binding DocumentLinesView}" ItemsSource="{Binding DocumentLinesView}"
SelectedItem="{Binding SelectedDocumentLine, Mode=TwoWay}" SelectedItem="{Binding SelectedDocumentLine, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"
IsReadOnly="True" IsReadOnly="{Binding IsDocumentLinesReadOnly}"
HeadersVisibility="Column"> HeadersVisibility="Column">
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
@@ -381,22 +479,31 @@
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTextColumn Header="Зав. №" <DataGridTextColumn Header="Зав. №"
Width="120" Width="120"
Binding="{Binding SerialNumber}" /> Binding="{Binding SerialNumber}"
IsReadOnly="True" />
<DataGridTextColumn Header="Дата поверки" <DataGridTextColumn Header="Дата поверки"
Width="110" Width="110"
Binding="{Binding VerificationDateDisplay}" /> Binding="{Binding VerificationDateDisplay}"
IsReadOnly="True" />
<DataGridTextColumn Header="Поверитель" <DataGridTextColumn Header="Поверитель"
Width="180" Width="180"
Binding="{Binding VerifierName}" /> Binding="{Binding VerifierName}"
IsReadOnly="True" />
<DataGridTextColumn Header="Номер наклейки" <DataGridTextColumn Header="Номер наклейки"
Width="150" Width="150"
Binding="{Binding StickerNumber}" /> Binding="{Binding StickerNumber}"
IsReadOnly="True" />
<DataGridTextColumn Header="Результат поверки" <DataGridTextColumn Header="Результат поверки"
Width="140" Width="140"
Binding="{Binding ResultText}" /> Binding="{Binding ResultText}"
IsReadOnly="True" />
<DataGridTextColumn Header="Номер документа по поверке" <DataGridTextColumn Header="Номер документа по поверке"
Width="240" Width="240"
Binding="{Binding VerificationDocumentDisplay}" /> Binding="{Binding VerificationDocumentDisplay}"
IsReadOnly="True" />
<DataGridTextColumn Header="Комплектность"
Width="240"
Binding="{Binding Completeness, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</Grid> </Grid>

View File

@@ -86,6 +86,27 @@ namespace XLAB
window.ShowDialog(); window.ShowDialog();
} }
private void EkzDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new EkzDirectoryWindow();
window.Owner = this;
window.ShowDialog();
}
private void VerificationReportsMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new VerificationReportsWindow();
window.Owner = this;
window.ShowDialog();
}
private void PlanningMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new PlanningWindow();
window.Owner = this;
window.ShowDialog();
}
private void SpoiDirectoryMenuItem_Click(object sender, RoutedEventArgs e) private void SpoiDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{ {
var window = new SpoiDirectoryWindow(); var window = new SpoiDirectoryWindow();

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
@@ -19,8 +19,11 @@ namespace XLAB
private string _documentFilterText; private string _documentFilterText;
private string _documentNumberEditor; private string _documentNumberEditor;
private string _documentStatusText; private string _documentStatusText;
private string _detailTableCountText;
private string _groupFilterText;
private string _groupDetailFilterText; private string _groupDetailFilterText;
private string _headerDepartmentName; private string _headerDepartmentName;
private int _headerInstrumentCount;
private DateTime? _headerIssuedOn; private DateTime? _headerIssuedOn;
private DateTime? _headerReceivedOn; private DateTime? _headerReceivedOn;
private bool _isBusy; private bool _isBusy;
@@ -48,6 +51,9 @@ namespace XLAB
DocumentsView = CollectionViewSource.GetDefaultView(Documents); DocumentsView = CollectionViewSource.GetDefaultView(Documents);
DocumentsView.Filter = FilterDocuments; DocumentsView.Filter = FilterDocuments;
DocumentGroupsView = CollectionViewSource.GetDefaultView(DocumentGroupSummaries);
DocumentGroupsView.Filter = FilterDocumentGroups;
DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines); DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines);
DocumentLinesView.Filter = FilterDocumentLines; DocumentLinesView.Filter = FilterDocumentLines;
@@ -67,6 +73,7 @@ namespace XLAB
SaveDocumentHeaderCommand = new RelayCommand(delegate { SaveDocumentAsync(); }, delegate { return CanSaveDocument(); }); SaveDocumentHeaderCommand = new RelayCommand(delegate { SaveDocumentAsync(); }, delegate { return CanSaveDocument(); });
DocumentStatusText = "Готово."; DocumentStatusText = "Готово.";
DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = "Документ не выбран."; LineStatusText = "Документ не выбран.";
} }
@@ -129,12 +136,20 @@ namespace XLAB
public ObservableCollection<PsvDocumentGroupSummary> DocumentGroupSummaries { get; private set; } public ObservableCollection<PsvDocumentGroupSummary> DocumentGroupSummaries { get; private set; }
public ICollectionView DocumentGroupsView { get; private set; }
public string DocumentStatusText public string DocumentStatusText
{ {
get { return _documentStatusText; } get { return _documentStatusText; }
private set { SetProperty(ref _documentStatusText, value); } private set { SetProperty(ref _documentStatusText, value); }
} }
public string DetailTableCountText
{
get { return _detailTableCountText; }
private set { SetProperty(ref _detailTableCountText, value); }
}
public ObservableCollection<PsvDocumentSummary> Documents { get; private set; } public ObservableCollection<PsvDocumentSummary> Documents { get; private set; }
public ICollectionView DocumentsView { get; private set; } public ICollectionView DocumentsView { get; private set; }
@@ -145,6 +160,18 @@ namespace XLAB
public ICommand DeleteSelectedGroupsCommand { get; private set; } public ICommand DeleteSelectedGroupsCommand { get; private set; }
public string GroupFilterText
{
get { return _groupFilterText; }
set
{
if (SetProperty(ref _groupFilterText, value))
{
RefreshDocumentGroupsView();
}
}
}
public string GroupDetailFilterText public string GroupDetailFilterText
{ {
get { return _groupDetailFilterText; } get { return _groupDetailFilterText; }
@@ -163,6 +190,12 @@ namespace XLAB
private set { SetProperty(ref _headerDepartmentName, value); } private set { SetProperty(ref _headerDepartmentName, value); }
} }
public int HeaderInstrumentCount
{
get { return _headerInstrumentCount; }
private set { SetProperty(ref _headerInstrumentCount, value); }
}
public bool IsDocumentHeaderEditable public bool IsDocumentHeaderEditable
{ {
get get
@@ -173,6 +206,11 @@ namespace XLAB
} }
} }
public bool IsDocumentLinesReadOnly
{
get { return !CanModifySelectedDocument(); }
}
public DateTime? HeaderIssuedOn public DateTime? HeaderIssuedOn
{ {
get { return _headerIssuedOn; } get { return _headerIssuedOn; }
@@ -195,6 +233,7 @@ namespace XLAB
RaiseCommandStates(); RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsCustomerEditable");
OnPropertyChanged("IsDocumentHeaderEditable"); OnPropertyChanged("IsDocumentHeaderEditable");
OnPropertyChanged("IsDocumentLinesReadOnly");
} }
} }
} }
@@ -260,6 +299,7 @@ namespace XLAB
RaiseCommandStates(); RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsCustomerEditable");
OnPropertyChanged("IsDocumentHeaderEditable"); OnPropertyChanged("IsDocumentHeaderEditable");
OnPropertyChanged("IsDocumentLinesReadOnly");
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
} }
} }
@@ -451,6 +491,40 @@ namespace XLAB
return document != null && document.IssuedOn.HasValue; return document != null && document.IssuedOn.HasValue;
} }
private static string BuildSerialNumbersText(IEnumerable<PsvDocumentLine> lines)
{
var serialNumbers = (lines ?? Enumerable.Empty<PsvDocumentLine>())
.Select(delegate(PsvDocumentLine line)
{
return line == null || string.IsNullOrWhiteSpace(line.SerialNumber)
? null
: line.SerialNumber.Trim();
})
.Where(delegate(string serialNumber) { return !string.IsNullOrWhiteSpace(serialNumber); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string serialNumber) { return serialNumber; }, StringComparer.OrdinalIgnoreCase)
.ToList();
return serialNumbers.Count == 0 ? string.Empty : string.Join(", ", serialNumbers.ToArray());
}
private static string BuildInstrumentNamesText(IEnumerable<PsvDocumentLine> lines)
{
var instrumentNames = (lines ?? Enumerable.Empty<PsvDocumentLine>())
.Select(delegate(PsvDocumentLine line)
{
return line == null || string.IsNullOrWhiteSpace(line.InstrumentName)
? null
: line.InstrumentName.Trim();
})
.Where(delegate(string instrumentName) { return !string.IsNullOrWhiteSpace(instrumentName); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string instrumentName) { return instrumentName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
return instrumentNames.Count == 0 ? string.Empty : string.Join("; ", instrumentNames.ToArray());
}
private static bool HasVerificationData(PsvDocumentLine line) private static bool HasVerificationData(PsvDocumentLine line)
{ {
return line != null return line != null
@@ -631,6 +705,7 @@ namespace XLAB
} }
ClearCollections(DocumentLines); ClearCollections(DocumentLines);
HeaderInstrumentCount = 0;
} }
private void ClearDocumentGroups() private void ClearDocumentGroups()
@@ -885,14 +960,14 @@ namespace XLAB
foreach (var pendingLinesByDocument in _pendingLinesByDocumentKey) foreach (var pendingLinesByDocument in _pendingLinesByDocumentKey)
{ {
if (string.Equals(pendingLinesByDocument.Key, currentDocument.DocumentKey, StringComparison.OrdinalIgnoreCase)) if (MatchesPendingLinesStorageKey(currentDocument, pendingLinesByDocument.Key))
{ {
continue; continue;
} }
var otherDocument = Documents.FirstOrDefault(delegate(PsvDocumentSummary document) var otherDocument = Documents.FirstOrDefault(delegate(PsvDocumentSummary document)
{ {
return string.Equals(document.DocumentKey, pendingLinesByDocument.Key, StringComparison.OrdinalIgnoreCase); return MatchesPendingLinesStorageKey(document, pendingLinesByDocument.Key);
}); });
if (otherDocument == null if (otherDocument == null
@@ -1112,7 +1187,7 @@ namespace XLAB
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
await Task.Run(delegate { _service.SaveLineVerification(persistedCardIds, result); }); await _service.SaveLineVerificationAsync(persistedCardIds, result);
foreach (var pendingLine in pendingLines) foreach (var pendingLine in pendingLines)
{ {
@@ -1196,8 +1271,8 @@ namespace XLAB
try try
{ {
IsBusy = true; IsBusy = true;
verifiers = await Task.Run(delegate { return _service.LoadVerifiers(); }); verifiers = await _service.LoadVerifiersAsync();
documentForms = await Task.Run(delegate { return _service.LoadVerificationDocumentForms(isPassed); }); documentForms = await _service.LoadVerificationDocumentFormsAsync(isPassed);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1236,7 +1311,7 @@ namespace XLAB
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
await Task.Run(delegate { _service.SaveLineVerification(persistedCardIds, result); }); await _service.SaveLineVerificationAsync(persistedCardIds, result);
foreach (var pendingLine in pendingLines) foreach (var pendingLine in pendingLines)
{ {
@@ -1286,7 +1361,7 @@ namespace XLAB
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
await Task.Run(delegate { _service.ResetLineVerification(persistedCardIds); }); await _service.ResetLineVerificationAsync(persistedCardIds);
foreach (var pendingLine in pendingLines) foreach (var pendingLine in pendingLines)
{ {
@@ -1319,8 +1394,8 @@ namespace XLAB
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
var result = await Task.Run(delegate { return _service.DeleteDocument(selectedDocument.DocumentNumber); }); var result = await _service.DeleteDocumentAsync(selectedDocument.DocumentNumber);
_pendingLinesByDocumentKey.Remove(selectedDocument.DocumentKey); _pendingLinesByDocumentKey.Remove(GetPendingLinesStorageKey(selectedDocument));
await RefreshDocumentsCoreAsync(null, null); await RefreshDocumentsCoreAsync(null, null);
_dialogService.ShowInfo( _dialogService.ShowInfo(
string.Format( string.Format(
@@ -1334,7 +1409,7 @@ namespace XLAB
private void DeleteDraftDocument(PsvDocumentSummary draft) private void DeleteDraftDocument(PsvDocumentSummary draft)
{ {
_draftDocuments.RemoveAll(delegate(PsvDocumentSummary item) { return item.DocumentKey == draft.DocumentKey; }); _draftDocuments.RemoveAll(delegate(PsvDocumentSummary item) { return item.DocumentKey == draft.DocumentKey; });
_pendingLinesByDocumentKey.Remove(draft.DocumentKey); _pendingLinesByDocumentKey.Remove(GetPendingLinesStorageKey(draft));
Documents.Remove(draft); Documents.Remove(draft);
DocumentsView.Refresh(); DocumentsView.Refresh();
SelectedDocument = Documents.Count > 0 ? Documents[0] : null; SelectedDocument = Documents.Count > 0 ? Documents[0] : null;
@@ -1363,7 +1438,7 @@ namespace XLAB
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
var persistedLines = await Task.Run(delegate { return _service.LoadDocumentLines(selectedDocument.DocumentNumber); }); var persistedLines = await _service.LoadDocumentLinesAsync(selectedDocument.DocumentNumber);
var linesToPrint = MergeDocumentLinesForPrint(selectedDocument, persistedLines); var linesToPrint = MergeDocumentLinesForPrint(selectedDocument, persistedLines);
if (linesToPrint.Count == 0) if (linesToPrint.Count == 0)
{ {
@@ -1441,10 +1516,7 @@ namespace XLAB
if (persistedCardIds.Count > 0) if (persistedCardIds.Count > 0)
{ {
deletedResult = await Task.Run(delegate deletedResult = await _service.DeleteDocumentGroupsAsync(selectedDocumentNumber, persistedCardIds);
{
return _service.DeleteDocumentGroups(selectedDocumentNumber, persistedCardIds);
});
} }
var remainingPendingCount = pendingLines.Count; var remainingPendingCount = pendingLines.Count;
@@ -1549,10 +1621,7 @@ namespace XLAB
if (persistedCardIds.Count > 0) if (persistedCardIds.Count > 0)
{ {
deletedResult = await Task.Run(delegate deletedResult = await _service.DeleteDocumentGroupsAsync(selectedDocumentNumber, persistedCardIds);
{
return _service.DeleteDocumentGroups(selectedDocumentNumber, persistedCardIds);
});
} }
var remainingPendingCount = pendingLines.Count; var remainingPendingCount = pendingLines.Count;
@@ -1652,6 +1721,7 @@ namespace XLAB
document.PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }); document.PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; });
document.FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }); document.FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; });
document.IssuedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IssuedOn.HasValue; }); document.IssuedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IssuedOn.HasValue; });
document.SerialNumbersText = BuildSerialNumbersText(materializedLines);
} }
private async Task ExecuteBusyOperationAsync(Func<Task> operation) private async Task ExecuteBusyOperationAsync(Func<Task> operation)
@@ -1691,7 +1761,8 @@ namespace XLAB
if (!string.IsNullOrWhiteSpace(DocumentFilterText) if (!string.IsNullOrWhiteSpace(DocumentFilterText)
&& !Contains(document.DocumentNumber, DocumentFilterText) && !Contains(document.DocumentNumber, DocumentFilterText)
&& !Contains(document.CustomerName, DocumentFilterText)) && !Contains(document.CustomerName, DocumentFilterText)
&& !Contains(document.SerialNumbersText, DocumentFilterText))
{ {
return false; return false;
} }
@@ -1699,6 +1770,27 @@ namespace XLAB
return true; return true;
} }
private bool FilterDocumentGroups(object item)
{
var group = item as PsvDocumentGroupSummary;
if (group == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(GroupFilterText))
{
return true;
}
return Contains(group.InstrumentType, GroupFilterText)
|| Contains(group.InstrumentName, GroupFilterText)
|| Contains(group.RangeText, GroupFilterText)
|| Contains(group.AccuracyText, GroupFilterText)
|| Contains(group.RegistryNumber, GroupFilterText)
|| Contains(group.SerialNumbersText, GroupFilterText);
}
private string BuildDocumentStatusText(int count) private string BuildDocumentStatusText(int count)
{ {
if (ShowClosedDocuments) if (ShowClosedDocuments)
@@ -1743,12 +1835,33 @@ namespace XLAB
}); });
} }
private PsvDocumentGroupSummary FindMatchingVisibleGroup(PsvDocumentGroupSummary group)
{
if (group == null)
{
return null;
}
return GetVisibleDocumentGroups().FirstOrDefault(delegate(PsvDocumentGroupSummary current)
{
return AreSameGroup(current, group);
});
}
private List<PsvDocumentGroupSummary> GetVisibleDocumentGroups()
{
return DocumentGroupsView == null
? new List<PsvDocumentGroupSummary>()
: DocumentGroupsView.Cast<object>().OfType<PsvDocumentGroupSummary>().ToList();
}
private static bool AreSameGroup(PsvDocumentGroupSummary left, PsvDocumentGroupSummary right) private static bool AreSameGroup(PsvDocumentGroupSummary left, PsvDocumentGroupSummary right)
{ {
return left != null return left != null
&& right != null && right != null
&& string.Equals(left.InstrumentType ?? string.Empty, right.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(left.InstrumentType ?? string.Empty, right.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(left.RangeText ?? string.Empty, right.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(left.RangeText ?? string.Empty, right.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(left.AccuracyText ?? string.Empty, right.AccuracyText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(left.RegistryNumber ?? string.Empty, right.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase); && string.Equals(left.RegistryNumber ?? string.Empty, right.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
} }
@@ -1774,12 +1887,41 @@ namespace XLAB
return new List<PsvDocumentLine>(); return new List<PsvDocumentLine>();
} }
var pendingKey = GetPendingLinesStorageKey(document);
if (string.IsNullOrWhiteSpace(pendingKey))
{
return new List<PsvDocumentLine>();
}
List<PsvDocumentLine> lines; List<PsvDocumentLine> lines;
return _pendingLinesByDocumentKey.TryGetValue(document.DocumentKey, out lines) return _pendingLinesByDocumentKey.TryGetValue(pendingKey, out lines)
? lines ? lines
: new List<PsvDocumentLine>(); : new List<PsvDocumentLine>();
} }
private static string GetPendingLinesStorageKey(PsvDocumentSummary document)
{
if (document == null)
{
return string.Empty;
}
if (!document.IsDraft && !string.IsNullOrWhiteSpace(document.DocumentNumber))
{
return document.DocumentNumber.Trim();
}
return string.IsNullOrWhiteSpace(document.DocumentKey)
? string.Empty
: document.DocumentKey.Trim();
}
private static bool MatchesPendingLinesStorageKey(PsvDocumentSummary document, string pendingKey)
{
return !string.IsNullOrWhiteSpace(pendingKey)
&& string.Equals(GetPendingLinesStorageKey(document), pendingKey, StringComparison.OrdinalIgnoreCase);
}
private List<PsvDocumentLine> MergeDocumentLinesForPrint(PsvDocumentSummary document, IEnumerable<PsvDocumentLine> persistedLines) private List<PsvDocumentLine> MergeDocumentLinesForPrint(PsvDocumentSummary document, IEnumerable<PsvDocumentLine> persistedLines)
{ {
var mergedLines = new List<PsvDocumentLine>(); var mergedLines = new List<PsvDocumentLine>();
@@ -1840,7 +1982,7 @@ namespace XLAB
private async Task LoadCustomersCoreAsync() private async Task LoadCustomersCoreAsync()
{ {
var customers = await Task.Run(delegate { return _service.LoadCustomers(); }); var customers = await _service.LoadCustomersAsync();
ClearCollections(Customers); ClearCollections(Customers);
foreach (var customer in customers) foreach (var customer in customers)
{ {
@@ -1891,7 +2033,7 @@ namespace XLAB
var previousGroup = SelectedDocumentGroup; var previousGroup = SelectedDocumentGroup;
var documentNumber = SelectedDocument.DocumentNumber; var documentNumber = SelectedDocument.DocumentNumber;
var persistedLines = await Task.Run(delegate { return _service.LoadDocumentLines(documentNumber); }); var persistedLines = await _service.LoadDocumentLinesAsync(documentNumber);
var mergedLines = persistedLines.Concat(GetPendingLines(SelectedDocument)).ToList(); var mergedLines = persistedLines.Concat(GetPendingLines(SelectedDocument)).ToList();
ApplyDocumentLines(mergedLines, previousGroup); ApplyDocumentLines(mergedLines, previousGroup);
@@ -1920,7 +2062,7 @@ namespace XLAB
try try
{ {
IsBusy = true; IsBusy = true;
instruments = await Task.Run(delegate { return _service.LoadCustomerInstruments(customerId); }); instruments = await _service.LoadCustomerInstrumentsAsync(customerId);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1964,7 +2106,7 @@ namespace XLAB
try try
{ {
IsBusy = true; IsBusy = true;
instrumentTypes = await Task.Run(delegate { return _service.LoadInstrumentTypes(); }); instrumentTypes = await _service.LoadInstrumentTypesAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1992,11 +2134,17 @@ namespace XLAB
return; return;
} }
var pendingKey = GetPendingLinesStorageKey(SelectedDocument);
if (string.IsNullOrWhiteSpace(pendingKey))
{
return;
}
List<PsvDocumentLine> pendingLines; List<PsvDocumentLine> pendingLines;
if (!_pendingLinesByDocumentKey.TryGetValue(SelectedDocument.DocumentKey, out pendingLines)) if (!_pendingLinesByDocumentKey.TryGetValue(pendingKey, out pendingLines))
{ {
pendingLines = new List<PsvDocumentLine>(); pendingLines = new List<PsvDocumentLine>();
_pendingLinesByDocumentKey[SelectedDocument.DocumentKey] = pendingLines; _pendingLinesByDocumentKey[pendingKey] = pendingLines;
} }
var candidateLines = selectedItems var candidateLines = selectedItems
@@ -2058,7 +2206,7 @@ namespace XLAB
if (SelectedDocument.IsDraft) if (SelectedDocument.IsDraft)
{ {
SelectedDocument.ItemCount = pendingLines.Count; UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines);
} }
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
@@ -2109,17 +2257,28 @@ namespace XLAB
return; return;
} }
List<PsvDocumentLine> pendingLines; var pendingKey = GetPendingLinesStorageKey(SelectedDocument);
if (!_pendingLinesByDocumentKey.TryGetValue(SelectedDocument.DocumentKey, out pendingLines)) if (string.IsNullOrWhiteSpace(pendingKey))
{ {
pendingLines = new List<PsvDocumentLine>(); return;
_pendingLinesByDocumentKey[SelectedDocument.DocumentKey] = pendingLines;
} }
var serialNumber = string.IsNullOrWhiteSpace(result.SerialNumber) ? string.Empty : result.SerialNumber.Trim(); List<PsvDocumentLine> pendingLines;
if (string.IsNullOrWhiteSpace(serialNumber)) if (!_pendingLinesByDocumentKey.TryGetValue(pendingKey, out pendingLines))
{ {
_dialogService.ShowWarning("Введите заводской номер."); pendingLines = new List<PsvDocumentLine>();
_pendingLinesByDocumentKey[pendingKey] = pendingLines;
}
var serialNumbers = (result.SerialNumbers ?? Array.Empty<string>())
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Select(delegate(string value) { return value.Trim(); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (serialNumbers.Count == 0)
{
_dialogService.ShowWarning("Введите хотя бы один заводской номер.");
return; return;
} }
@@ -2129,11 +2288,34 @@ namespace XLAB
return; return;
} }
var candidateLine = CreatePendingTypeLine(result.TypeItem, serialNumber); var validSerialNumbers = new List<string>();
var skippedInvalidLengthCount = 0;
foreach (var serialNumber in serialNumbers)
{
if (serialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
skippedInvalidLengthCount++;
continue;
}
validSerialNumbers.Add(serialNumber);
}
if (validSerialNumbers.Count == 0)
{
_dialogService.ShowWarning(string.Format("Каждый заводской номер должен содержать не более {0} символов.", EkzDirectoryRules.SerialNumberMaxLength));
return;
}
var candidateLines = validSerialNumbers
.Select(delegate(string serialNumber) { return CreatePendingTypeLine(result.TypeItem, serialNumber); })
.ToList();
List<OpenDocumentConflictInfo> openDocumentConflicts; List<OpenDocumentConflictInfo> openDocumentConflicts;
try try
{ {
openDocumentConflicts = FindOpenDocumentConflicts(new[] { candidateLine }); openDocumentConflicts = FindOpenDocumentConflicts(candidateLines);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -2141,10 +2323,21 @@ namespace XLAB
return; return;
} }
if (openDocumentConflicts.Count > 0) var openConflictKeys = new HashSet<string>(
openDocumentConflicts.Select(delegate(OpenDocumentConflictInfo conflict) { return conflict.OpenDocumentConflictKey; }),
StringComparer.OrdinalIgnoreCase);
var duplicateKeys = new HashSet<string>(DocumentLines.Select(delegate(PsvDocumentLine line) { return line.DuplicateKey; }), StringComparer.OrdinalIgnoreCase);
var addedCount = 0;
var skippedDuplicateCount = 0;
var skippedOpenDocumentCount = 0;
foreach (var serialNumber in validSerialNumbers)
{ {
_dialogService.ShowWarning(BuildOpenDocumentConflictMessage(openDocumentConflicts)); if (openConflictKeys.Contains(PsvDocumentLine.BuildOpenDocumentConflictKey(result.TypeItem.TypeSizeId, serialNumber)))
return; {
skippedOpenDocumentCount++;
continue;
} }
var duplicateKey = PsvDocumentLine.BuildDuplicateKey( var duplicateKey = PsvDocumentLine.BuildDuplicateKey(
@@ -2153,24 +2346,65 @@ namespace XLAB
result.TypeItem.RegistryNumber, result.TypeItem.RegistryNumber,
serialNumber); serialNumber);
if (DocumentLines.Any(delegate(PsvDocumentLine line) if (duplicateKeys.Contains(duplicateKey))
{ {
return string.Equals(line.DuplicateKey, duplicateKey, StringComparison.OrdinalIgnoreCase); skippedDuplicateCount++;
})) continue;
}
pendingLines.Add(CreatePendingTypeLine(result.TypeItem, serialNumber));
duplicateKeys.Add(duplicateKey);
addedCount++;
}
if (addedCount == 0 && skippedDuplicateCount > 0 && skippedOpenDocumentCount == 0 && skippedInvalidLengthCount == 0)
{ {
_dialogService.ShowWarning("Такой прибор уже есть в ПСВ."); _dialogService.ShowWarning("Такие приборы уже есть в ПСВ.");
return; return;
} }
pendingLines.Add(candidateLine);
if (SelectedDocument.IsDraft) if (SelectedDocument.IsDraft)
{ {
SelectedDocument.ItemCount = pendingLines.Count; UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines);
} }
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
_dialogService.ShowInfo("Прибор по типу добавлен в ПСВ.");
var messages = new List<string>();
if (addedCount > 0)
{
messages.Add(string.Format("Добавлено приборов по типу: {0}.", addedCount));
}
if (skippedDuplicateCount > 0)
{
messages.Add(string.Format("Исключено дублей: {0}.", skippedDuplicateCount));
}
if (skippedInvalidLengthCount > 0)
{
messages.Add(string.Format("Пропущено из-за длины зав. № более {0} символов: {1}.", EkzDirectoryRules.SerialNumberMaxLength, skippedInvalidLengthCount));
}
if (skippedOpenDocumentCount > 0)
{
messages.Add(string.Format("Пропущено из-за других открытых ПСВ: {0}.", skippedOpenDocumentCount));
messages.Add(BuildOpenDocumentConflictMessage(openDocumentConflicts));
}
if (messages.Count > 0)
{
var message = string.Join(" ", messages.ToArray());
if (addedCount == 0)
{
_dialogService.ShowWarning(message);
}
else
{
_dialogService.ShowInfo(message);
}
}
RaiseCommandStates(); RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsCustomerEditable");
} }
@@ -2186,7 +2420,8 @@ namespace XLAB
} }
RebuildDocumentGroupSummaries(DocumentLines); RebuildDocumentGroupSummaries(DocumentLines);
SelectedDocumentGroup = FindMatchingGroup(previousGroup) ?? DocumentGroupSummaries.FirstOrDefault(); SelectedDocumentGroup = FindMatchingVisibleGroup(previousGroup)
?? GetVisibleDocumentGroups().FirstOrDefault();
SelectedDocumentLine = previousLine == null SelectedDocumentLine = previousLine == null
? null ? null
: DocumentLines.FirstOrDefault(delegate(PsvDocumentLine line) : DocumentLines.FirstOrDefault(delegate(PsvDocumentLine line)
@@ -2200,6 +2435,8 @@ namespace XLAB
&& previousLine.IsPendingInsert && previousLine.IsPendingInsert
&& string.Equals(line.DuplicateKey, previousLine.DuplicateKey, StringComparison.OrdinalIgnoreCase); && string.Equals(line.DuplicateKey, previousLine.DuplicateKey, StringComparison.OrdinalIgnoreCase);
}); });
HeaderInstrumentCount = DocumentLines.Count;
RefreshDocumentGroupsView();
RefreshDocumentLinesView(); RefreshDocumentLinesView();
RaiseCommandStates(); RaiseCommandStates();
} }
@@ -2234,20 +2471,26 @@ namespace XLAB
{ {
InstrumentType = line.InstrumentType ?? string.Empty, InstrumentType = line.InstrumentType ?? string.Empty,
RangeText = line.RangeText ?? string.Empty, RangeText = line.RangeText ?? string.Empty,
AccuracyText = line.AccuracyText ?? string.Empty,
RegistryNumber = line.RegistryNumber ?? string.Empty RegistryNumber = line.RegistryNumber ?? string.Empty
}) })
.OrderBy(group => group.Key.InstrumentType) .OrderBy(group => group.Key.InstrumentType)
.ThenBy(group => group.Key.RegistryNumber) .ThenBy(group => group.Key.RegistryNumber)
.ThenBy(group => group.Key.RangeText) .ThenBy(group => group.Key.RangeText)
.ThenBy(group => group.Key.AccuracyText)
.Select(group => new PsvDocumentGroupSummary .Select(group => new PsvDocumentGroupSummary
{ {
InstrumentName = BuildInstrumentNamesText(group),
InstrumentType = group.Key.InstrumentType, InstrumentType = group.Key.InstrumentType,
RangeText = group.Key.RangeText, RangeText = group.Key.RangeText,
AccuracyText = group.Key.AccuracyText,
RegistryNumber = group.Key.RegistryNumber, RegistryNumber = group.Key.RegistryNumber,
SerialNumbersText = BuildSerialNumbersText(group),
IsBatchSelected = checkedGroups.Any(delegate(PsvDocumentGroupSummary previous) IsBatchSelected = checkedGroups.Any(delegate(PsvDocumentGroupSummary previous)
{ {
return string.Equals(previous.InstrumentType ?? string.Empty, group.Key.InstrumentType, StringComparison.OrdinalIgnoreCase) return string.Equals(previous.InstrumentType ?? string.Empty, group.Key.InstrumentType, StringComparison.OrdinalIgnoreCase)
&& string.Equals(previous.RangeText ?? string.Empty, group.Key.RangeText, StringComparison.OrdinalIgnoreCase) && string.Equals(previous.RangeText ?? string.Empty, group.Key.RangeText, StringComparison.OrdinalIgnoreCase)
&& string.Equals(previous.AccuracyText ?? string.Empty, group.Key.AccuracyText, StringComparison.OrdinalIgnoreCase)
&& string.Equals(previous.RegistryNumber ?? string.Empty, group.Key.RegistryNumber, StringComparison.OrdinalIgnoreCase); && string.Equals(previous.RegistryNumber ?? string.Empty, group.Key.RegistryNumber, StringComparison.OrdinalIgnoreCase);
}), }),
InVerificationCount = group.Count(line => !line.IsPassed.HasValue), InVerificationCount = group.Count(line => !line.IsPassed.HasValue),
@@ -2263,6 +2506,36 @@ namespace XLAB
} }
} }
private void RefreshDocumentGroupsView()
{
if (DocumentGroupsView != null)
{
DocumentGroupsView.Refresh();
}
var selectedVisibleGroup = FindMatchingVisibleGroup(SelectedDocumentGroup);
if (selectedVisibleGroup != null)
{
if (!ReferenceEquals(SelectedDocumentGroup, selectedVisibleGroup))
{
SelectedDocumentGroup = selectedVisibleGroup;
return;
}
RefreshDocumentLinesView();
return;
}
var firstVisibleGroup = GetVisibleDocumentGroups().FirstOrDefault();
if (!ReferenceEquals(SelectedDocumentGroup, firstVisibleGroup))
{
SelectedDocumentGroup = firstVisibleGroup;
return;
}
RefreshDocumentLinesView();
}
private void RefreshDocumentLinesView() private void RefreshDocumentLinesView()
{ {
DocumentLinesView.Refresh(); DocumentLinesView.Refresh();
@@ -2279,7 +2552,7 @@ namespace XLAB
{ {
DocumentStatusText = "Загрузка списка ПСВ..."; DocumentStatusText = "Загрузка списка ПСВ...";
var databaseDocuments = await Task.Run(delegate { return _service.LoadDocuments(ShowClosedDocuments); }); var databaseDocuments = await _service.LoadDocumentsAsync(ShowClosedDocuments);
var currentDocumentKey = documentKeyToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentKey : null); var currentDocumentKey = documentKeyToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentKey : null);
var currentDocumentNumber = documentNumberToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentNumber : null); var currentDocumentNumber = documentNumberToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentNumber : null);
@@ -2358,7 +2631,7 @@ namespace XLAB
} }
if (DocumentExistsInCollections(DocumentNumberEditor.Trim(), selectedDocument.DocumentKey) if (DocumentExistsInCollections(DocumentNumberEditor.Trim(), selectedDocument.DocumentKey)
|| _service.DocumentNumberExists(DocumentNumberEditor.Trim(), selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber)) || await _service.DocumentNumberExistsAsync(DocumentNumberEditor.Trim(), selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber))
{ {
_dialogService.ShowWarning("ПСВ с таким номером уже существует."); _dialogService.ShowWarning("ПСВ с таким номером уже существует.");
return; return;
@@ -2395,12 +2668,14 @@ namespace XLAB
var currentDocumentNumber = selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber; var currentDocumentNumber = selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber;
var documentKey = selectedDocument.DocumentKey; var documentKey = selectedDocument.DocumentKey;
var documentPendingKey = GetPendingLinesStorageKey(selectedDocument);
var wasDraft = selectedDocument.IsDraft; var wasDraft = selectedDocument.IsDraft;
var closingNow = !selectedDocument.IssuedOn.HasValue && request.IssuedOn.HasValue; var closingNow = !selectedDocument.IssuedOn.HasValue && request.IssuedOn.HasValue;
var printDocument = closingNow ? CreateSavedDocumentSummaryForPrint(selectedDocument, request) : null; var printDocument = closingNow ? CreateSavedDocumentSummaryForPrint(selectedDocument, request) : null;
var result = await Task.Run(delegate { return _service.SaveDocument(currentDocumentNumber, request, pendingLines); }); var documentLinesSnapshot = DocumentLines.ToList();
var result = await Task.Run(delegate { return _service.SaveDocument(currentDocumentNumber, request, documentLinesSnapshot); });
_pendingLinesByDocumentKey.Remove(documentKey); _pendingLinesByDocumentKey.Remove(documentPendingKey);
if (wasDraft) if (wasDraft)
{ {
_draftDocuments.RemoveAll(delegate(PsvDocumentSummary draft) { return draft.DocumentKey == documentKey; }); _draftDocuments.RemoveAll(delegate(PsvDocumentSummary draft) { return draft.DocumentKey == documentKey; });
@@ -2426,7 +2701,7 @@ namespace XLAB
var prompt = messageText + " ПСВ закрыта. Распечатать приемо-сдаточную ведомость?"; var prompt = messageText + " ПСВ закрыта. Распечатать приемо-сдаточную ведомость?";
if (_dialogService.Confirm(prompt)) if (_dialogService.Confirm(prompt))
{ {
var printLines = await Task.Run(delegate { return _service.LoadDocumentLines(result.DocumentNumber); }); var printLines = await _service.LoadDocumentLinesAsync(result.DocumentNumber);
if (printDocument != null) if (printDocument != null)
{ {
printDocument.DocumentNumber = result.DocumentNumber; printDocument.DocumentNumber = result.DocumentNumber;
@@ -2445,23 +2720,37 @@ namespace XLAB
private void UpdateLineStatus() private void UpdateLineStatus()
{ {
var visibleGroupCount = GetVisibleDocumentGroups().Count;
if (SelectedDocument == null) if (SelectedDocument == null)
{ {
DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = "Документ не выбран."; LineStatusText = "Документ не выбран.";
return; return;
} }
if (DocumentGroupSummaries.Count == 0) if (DocumentGroupSummaries.Count == 0)
{ {
DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = SelectedDocument.IsDraft LineStatusText = SelectedDocument.IsDraft
? "Черновик пуст. Добавьте приборы через контекстное меню таблицы групп." ? "Черновик пуст. Добавьте приборы через контекстное меню таблицы групп."
: "В документе нет групп приборов."; : "В документе нет групп приборов.";
return; return;
} }
if (visibleGroupCount == 0)
{
DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = string.IsNullOrWhiteSpace(GroupFilterText)
? "Выберите группу."
: string.Format("Группы по фильтру \"{0}\" не найдены.", GroupFilterText.Trim());
return;
}
if (SelectedDocumentGroup == null) if (SelectedDocumentGroup == null)
{ {
LineStatusText = string.Format("Групп: {0}. Выберите группу.", DocumentGroupSummaries.Count); DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = string.Format("Групп: {0}/{1}. Выберите группу.", visibleGroupCount, DocumentGroupSummaries.Count);
return; return;
} }
@@ -2472,9 +2761,11 @@ namespace XLAB
&& (string.IsNullOrWhiteSpace(GroupDetailFilterText) || Contains(line.SerialNumber, GroupDetailFilterText)); && (string.IsNullOrWhiteSpace(GroupDetailFilterText) || Contains(line.SerialNumber, GroupDetailFilterText));
}); });
var pendingCount = DocumentLines.Count(delegate(PsvDocumentLine line) { return line.IsPendingInsert; }); var pendingCount = DocumentLines.Count(delegate(PsvDocumentLine line) { return line.IsPendingInsert; });
DetailTableCountText = string.Format("Приборов в таблице: {0}.", filteredCount);
LineStatusText = string.Format( LineStatusText = string.Format(
"Групп: {0}. Приборов в выбранной группе: {1}. Отображено по фильтру: {2}. Не сохранено строк: {3}.", "Групп: {0}/{1}. Приборов в выбранной группе: {2}. Отображено по фильтру: {3}. Не сохранено строк: {4}.",
visibleGroupCount,
DocumentGroupSummaries.Count, DocumentGroupSummaries.Count,
groupLineCount, groupLineCount,
filteredCount, filteredCount,

View File

@@ -0,0 +1,70 @@
using System.Collections.Generic;
using System.Windows;
namespace XLAB
{
internal interface IPlanningDialogService
{
PlanningEditResult ShowPlanningEditDialog(PlanningEditSeed seed, bool isNew, IReadOnlyList<PlanningInstrumentOption> instruments, PlanningService service);
EkzDirectoryItem ShowEkzEditDialog(EkzDirectoryItem seed, bool isNew, IReadOnlyList<EkzDirectoryItem> existingItems, EkzDirectoryService service);
bool Confirm(string message);
void ShowError(string message);
void ShowInfo(string message);
void ShowWarning(string message);
}
internal sealed class PlanningDialogService : IPlanningDialogService
{
private readonly Window _owner;
public PlanningDialogService(Window owner)
{
_owner = owner;
}
public bool Confirm(string message)
{
return MessageBox.Show(_owner, message, "ПСВ", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
}
public EkzDirectoryItem ShowEkzEditDialog(EkzDirectoryItem seed, bool isNew, IReadOnlyList<EkzDirectoryItem> existingItems, EkzDirectoryService service)
{
var viewModel = new EkzEditWindowViewModel(seed, isNew, existingItems, service);
var window = new EkzEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
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 PlanningEditResult ShowPlanningEditDialog(PlanningEditSeed seed, bool isNew, IReadOnlyList<PlanningInstrumentOption> instruments, PlanningService service)
{
var viewModel = new PlanningEditWindowViewModel(seed, isNew, instruments, service);
var window = new PlanningEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public void ShowWarning(string message)
{
MessageBox.Show(_owner, message, "ПСВ", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
}

View File

@@ -0,0 +1,111 @@
<Window x:Class="XLAB.PlanningEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="360"
Width="980"
MinHeight="360"
MinWidth="860"
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="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Прибор (EKZ)" />
<ComboBox Grid.Row="0"
Grid.Column="1"
Margin="0,0,0,8"
ItemsSource="{Binding InstrumentItems}"
SelectedValue="{Binding SelectedInstrumentId}"
SelectedValuePath="Id"
DisplayMemberPath="DisplayName"
IsTextSearchEnabled="True" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Период (TPRMCP)" />
<ComboBox Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,8"
ItemsSource="{Binding TemplateItems}"
SelectedValue="{Binding SelectedTemplateId}"
SelectedValuePath="Id"
DisplayMemberPath="DisplayName"
IsTextSearchEnabled="True" />
<TextBlock Grid.Row="2"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Плановая дата" />
<DatePicker Grid.Row="2"
Grid.Column="1"
Margin="0,0,0,8"
HorizontalAlignment="Left"
SelectedDate="{Binding PlannedOn, Mode=TwoWay}"
SelectedDateFormat="Short" />
<TextBlock Grid.Row="3"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Top"
Text="Выбранный прибор" />
<TextBlock Grid.Row="3"
Grid.Column="1"
Margin="0,0,0,8"
TextWrapping="Wrap"
Text="{Binding SelectedInstrumentDescription}" />
<TextBlock Grid.Row="4"
Grid.Column="0"
Margin="0,0,12,0"
VerticalAlignment="Top"
Text="Пояснение" />
<TextBlock Grid.Row="4"
Grid.Column="1"
Foreground="DimGray"
TextWrapping="Wrap"
Text="{Binding TemplateWarningMessage}" />
</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 PlanningEditWindow : Window
{
internal PlanningEditWindow(PlanningEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,250 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB
{
internal sealed class PlanningEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<PlanningInstrumentOption> _instrumentItems;
private readonly bool _isNew;
private readonly int? _seedTemplateId;
private readonly PlanningService _service;
private int _selectedInstrumentId;
private int? _selectedTemplateId;
private IReadOnlyList<PlanningTemplateOption> _templateItems;
private DateTime? _plannedOn;
private string _validationMessage;
public PlanningEditWindowViewModel(PlanningEditSeed seed, bool isNew, IReadOnlyList<PlanningInstrumentOption> instruments, PlanningService service)
{
if (seed == null)
{
throw new ArgumentNullException("seed");
}
_instrumentItems = instruments ?? Array.Empty<PlanningInstrumentOption>();
_service = service ?? throw new ArgumentNullException("service");
_isNew = isNew;
_seedTemplateId = seed.TemplateId;
PlanId = seed.PlanId;
TargetYear = seed.TargetYear;
InstrumentItems = _instrumentItems
.OrderBy(delegate(PlanningInstrumentOption item) { return item.DisplayName; }, StringComparer.CurrentCultureIgnoreCase)
.ToList();
TemplateItems = Array.Empty<PlanningTemplateOption>();
PlannedOn = seed.PlannedOn;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
if (seed.InstrumentId > 0)
{
SelectedInstrumentId = seed.InstrumentId;
}
else if (InstrumentItems.Count > 0)
{
SelectedInstrumentId = InstrumentItems[0].Id;
}
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public IReadOnlyList<PlanningInstrumentOption> InstrumentItems { get; private set; }
public int? PlanId { get; private set; }
public DateTime? PlannedOn
{
get { return _plannedOn; }
set { SetProperty(ref _plannedOn, value); }
}
public int SelectedInstrumentId
{
get { return _selectedInstrumentId; }
set
{
if (SetProperty(ref _selectedInstrumentId, value))
{
LoadTemplates();
OnPropertyChanged("SelectedInstrumentDescription");
OnPropertyChanged("TemplateWarningMessage");
}
}
}
public PlanningInstrumentOption SelectedInstrument
{
get { return InstrumentItems.FirstOrDefault(delegate(PlanningInstrumentOption item) { return item.Id == SelectedInstrumentId; }); }
}
public string SelectedInstrumentDescription
{
get
{
return SelectedInstrument == null ? string.Empty : SelectedInstrument.DisplayName;
}
}
public int? SelectedTemplateId
{
get { return _selectedTemplateId; }
set { SetProperty(ref _selectedTemplateId, value); }
}
public IReadOnlyList<PlanningTemplateOption> TemplateItems
{
get { return _templateItems; }
private set
{
_templateItems = value ?? Array.Empty<PlanningTemplateOption>();
OnPropertyChanged("TemplateItems");
OnPropertyChanged("SelectedTemplateDescription");
}
}
public string SelectedTemplateDescription
{
get
{
var selected = TemplateItems.FirstOrDefault(delegate(PlanningTemplateOption item) { return item.Id == SelectedTemplateId; });
return selected == null ? string.Empty : selected.DisplayName;
}
}
public string TemplateWarningMessage
{
get
{
if (SelectedInstrument == null || TemplateItems.Count > 0)
{
return string.Empty;
}
return "Для выбранного прибора в TPRMCP не найден период. Расчет можно показать, но запись в EKZMCP сохранить нельзя.";
}
}
public int TargetYear { get; private set; }
public string Title
{
get
{
return _isNew || !PlanId.HasValue
? string.Format("Планирование на {0} год", TargetYear)
: string.Format("Редактирование плана {0}", TargetYear);
}
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public PlanningEditResult ToResult()
{
if (!PlannedOn.HasValue || !SelectedTemplateId.HasValue)
{
throw new InvalidOperationException("Результат плановой записи недоступен без даты и периода.");
}
return new PlanningEditResult
{
PlanId = PlanId,
InstrumentId = SelectedInstrumentId,
TemplateId = SelectedTemplateId.Value,
PlannedOn = PlannedOn.Value.Date
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
if (SelectedInstrument == null)
{
ValidationMessage = "Выберите прибор.";
return;
}
if (TemplateItems.Count == 0)
{
ValidationMessage = "Для выбранного прибора отсутствует период TPRMCP. Сохранить запись в EKZMCP нельзя.";
return;
}
if (!SelectedTemplateId.HasValue || TemplateItems.All(delegate(PlanningTemplateOption item) { return item.Id != SelectedTemplateId.Value; }))
{
ValidationMessage = "Выберите период из TPRMCP.";
return;
}
if (!PlannedOn.HasValue)
{
ValidationMessage = "Укажите плановую дату.";
return;
}
if (PlannedOn.Value.Year != TargetYear)
{
ValidationMessage = string.Format("Плановая дата должна относиться к {0} году.", TargetYear);
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private void LoadTemplates()
{
var instrument = SelectedInstrument;
if (instrument == null)
{
TemplateItems = Array.Empty<PlanningTemplateOption>();
SelectedTemplateId = null;
return;
}
var templates = _service.LoadTemplateOptions(instrument.TypeSizeId)
.OrderBy(delegate(PlanningTemplateOption item) { return item.PeriodMonths; })
.ThenBy(delegate(PlanningTemplateOption item) { return item.DisplayName; }, StringComparer.CurrentCultureIgnoreCase)
.ToList();
TemplateItems = templates;
if (_seedTemplateId.HasValue && templates.Any(delegate(PlanningTemplateOption item) { return item.Id == _seedTemplateId.Value; }))
{
SelectedTemplateId = _seedTemplateId.Value;
return;
}
if (SelectedTemplateId.HasValue && templates.Any(delegate(PlanningTemplateOption item) { return item.Id == SelectedTemplateId.Value; }))
{
return;
}
SelectedTemplateId = templates.Count > 0 ? (int?)templates[0].Id : null;
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
}
}

157
XLAB/PlanningModels.cs Normal file
View File

@@ -0,0 +1,157 @@
using System;
namespace XLAB
{
public sealed class PlanningItem
{
public int InstrumentId { get; set; }
public int TypeSizeId { get; set; }
public int OwnerOrganizationId { get; set; }
public string OwnerOrganizationName { get; set; }
public string MeasurementAreaName { get; set; }
public string InstrumentName { get; set; }
public string TypeName { get; set; }
public string RangeText { get; set; }
public string RegistryNumber { get; set; }
public string SerialNumber { get; set; }
public string InventoryNumber { get; set; }
public int? PlanId { get; set; }
public int? EffectiveTemplateId { get; set; }
public int? PeriodMonths { get; set; }
public DateTime? LastVerificationOn { get; set; }
public DateTime? PlannedOn { get; set; }
public bool IsExplicitPlan { get; set; }
public string PlanSource { get; set; }
public string PeriodSource { get; set; }
public bool CanPersistPlan
{
get { return PlanId.HasValue || EffectiveTemplateId.HasValue; }
}
public string PeriodDisplay
{
get { return PeriodMonths.HasValue ? string.Format("{0} мес.", PeriodMonths.Value) : string.Empty; }
}
public string RecordKindText
{
get { return IsExplicitPlan ? "EKZMCP" : "Расчет"; }
}
public string PersistenceText
{
get { return CanPersistPlan ? string.Empty : "Только расчет"; }
}
}
internal sealed class PlanningInstrumentOption
{
public int Id { get; set; }
public int TypeSizeId { get; set; }
public int OwnerOrganizationId { get; set; }
public string DisplayName { get; set; }
public override string ToString()
{
return DisplayName ?? string.Empty;
}
}
internal sealed class PlanningTemplateOption
{
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 string DisplayName
{
get
{
var parts = new System.Collections.Generic.List<string>
{
string.Format("{0} мес.", PeriodMonths)
};
if (!string.IsNullOrWhiteSpace(CycleName))
{
parts.Add(CycleName.Trim());
}
if (!string.IsNullOrWhiteSpace(GroupName))
{
parts.Add(GroupName.Trim());
}
if (!string.IsNullOrWhiteSpace(Comment))
{
parts.Add(Comment.Trim());
}
return string.Join(" / ", parts.ToArray());
}
}
public override string ToString()
{
return DisplayName;
}
}
internal sealed class PlanningEditSeed
{
public int TargetYear { get; set; }
public int? PlanId { get; set; }
public int InstrumentId { get; set; }
public int? TemplateId { get; set; }
public DateTime? PlannedOn { get; set; }
}
internal sealed class PlanningEditResult
{
public int? PlanId { get; set; }
public int InstrumentId { get; set; }
public int TemplateId { get; set; }
public DateTime PlannedOn { get; set; }
}
}

394
XLAB/PlanningService.cs Normal file
View File

@@ -0,0 +1,394 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
namespace XLAB
{
internal sealed class PlanningService
{
public int AddPlanItem(PlanningEditResult item)
{
if (item == null)
{
throw new InvalidOperationException("Не переданы данные плановой записи.");
}
const string sql = @"
INSERT INTO dbo.EKZMCP
(
IDEKZ,
IDTPRMCP,
DTMKPLO,
PZMCO,
IDSPVDMK
)
VALUES
(
@InstrumentId,
@TemplateId,
@PlannedOn,
NULL,
NULL
);
SELECT CAST(SCOPE_IDENTITY() AS int);";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsurePlanIsUnique(connection, item.InstrumentId, item.TemplateId, null);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = item.InstrumentId;
command.Parameters.Add("@TemplateId", SqlDbType.Int).Value = item.TemplateId;
command.Parameters.Add("@PlannedOn", SqlDbType.DateTime).Value = item.PlannedOn.Date;
try
{
return Convert.ToInt32(command.ExecuteScalar());
}
catch (SqlException ex) when (ReferenceDirectorySqlHelpers.IsDuplicateViolation(ex, "XAK1EKZMCP"))
{
throw CreatePlanDuplicateException(ex);
}
}
}
public void DeletePlanItem(int id)
{
if (id <= 0)
{
throw new InvalidOperationException("Не выбрана запись EKZMCP для удаления.");
}
const string sql = @"
DELETE FROM dbo.EKZMCP
WHERE IDEKZMCP = @Id;
SELECT @@ROWCOUNT;";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись EKZMCP для удаления не найдена.");
}
}
}
public IReadOnlyList<DirectoryLookupItem> LoadOwnerItems()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
fr.IDFRPD AS Id,
fr.NMFRPD AS Name
FROM dbo.FRPD fr
WHERE NULLIF(LTRIM(RTRIM(fr.NMFRPD)), '') IS NOT NULL
ORDER BY fr.NMFRPD, fr.IDFRPD;");
}
public IReadOnlyList<PlanningItem> LoadPlanItems(int year)
{
if (year < 2000 || year > 2100)
{
throw new InvalidOperationException("Год планирования должен быть в диапазоне 2000-2100.");
}
const string sql = @"
SELECT
z.IDEKZ AS InstrumentId,
z.IDTPRZ AS TypeSizeId,
z.IDFRPDV AS OwnerOrganizationId,
ownerOrg.NMFRPD AS OwnerOrganizationName,
areas.NMOI AS MeasurementAreaName,
names.NMTP AS InstrumentName,
tips.TP AS TypeName,
tprz.DPZN AS RangeText,
CONVERT(nvarchar(50), tprz.NNGSRS) AS RegistryNumber,
z.NNZV AS SerialNumber,
z.NNIN AS InventoryNumber,
lastMk.LastVerificationOn,
selectedPlan.IDEKZMCP AS PlanId,
CASE
WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN selectedPlan.IDTPRMCP
ELSE COALESCE(periodByInstrument.IDTPRMCP, periodByType.IDTPRMCP)
END AS EffectiveTemplateId,
CASE
WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN selectedPlan.PRMK
ELSE COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR)
END AS PeriodMonths,
CASE
WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN selectedPlan.DTMKPLO
ELSE DATEADD(month, COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR), lastMk.LastVerificationOn)
END AS PlannedOn,
CASE
WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS IsExplicitPlan,
CASE
WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN N'План из EKZMCP'
WHEN periodByInstrument.IDTPRMCP IS NOT NULL THEN N'Расчет по TPRMCP экземпляра'
WHEN periodByType.IDTPRMCP IS NOT NULL THEN N'Расчет по TPRMCP типоразмера'
WHEN tips.PRMKGR IS NOT NULL THEN N'Расчет по TIPS.PRMKGR'
ELSE N''
END AS PlanSource,
CASE
WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN N'EKZMCP / TPRMCP'
WHEN periodByInstrument.IDTPRMCP IS NOT NULL THEN N'TPRMCP экземпляра'
WHEN periodByType.IDTPRMCP IS NOT NULL THEN N'TPRMCP типоразмера'
WHEN tips.PRMKGR IS NOT NULL THEN N'TIPS.PRMKGR'
ELSE N''
END AS PeriodSource
FROM dbo.EKZ z
JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
JOIN dbo.FRPD ownerOrg ON ownerOrg.IDFRPD = z.IDFRPDV
OUTER APPLY
(
SELECT TOP (1)
COALESCE(m.DTMKFK, m.DTVDM, m.DTPRM) AS LastVerificationOn
FROM dbo.EKZMK m
WHERE m.IDEKZ = z.IDEKZ
AND COALESCE(m.DTMKFK, m.DTVDM, m.DTPRM) IS NOT NULL
ORDER BY COALESCE(m.DTMKFK, m.DTVDM, m.DTPRM) DESC, m.IDEKZMK DESC
) lastMk
OUTER APPLY
(
SELECT TOP (1)
e.IDEKZMCP,
e.IDTPRMCP,
e.DTMKPLO,
t.PRMK
FROM dbo.EKZMCP e
JOIN dbo.TPRMCP t ON t.IDTPRMCP = e.IDTPRMCP
WHERE e.IDEKZ = z.IDEKZ
AND e.DTMKPLO >= @YearStart
AND e.DTMKPLO < @YearEnd
ORDER BY e.DTMKPLO, e.IDEKZMCP DESC
) selectedPlan
OUTER APPLY
(
SELECT TOP (1)
e.IDEKZMCP AS PlanId
FROM dbo.EKZMCP e
WHERE e.IDEKZ = z.IDEKZ
AND e.DTMKPLO >= @YearEnd
ORDER BY e.DTMKPLO, e.IDEKZMCP DESC
) futurePlan
OUTER APPLY
(
SELECT TOP (1)
e.IDTPRMCP,
t.PRMK
FROM dbo.EKZMCP e
JOIN dbo.TPRMCP t ON t.IDTPRMCP = e.IDTPRMCP
WHERE e.IDEKZ = z.IDEKZ
ORDER BY e.IDEKZMCP DESC, t.IDTPRMCP DESC
) periodByInstrument
OUTER APPLY
(
SELECT TOP (1)
t.IDTPRMCP,
t.PRMK
FROM dbo.TPRMCP t
WHERE t.IDTPRZ = z.IDTPRZ
ORDER BY t.IDTPRMCP DESC
) periodByType
WHERE ISNULL(z.IsDeleted, 0) = 0
AND
(
selectedPlan.IDEKZMCP IS NOT NULL
OR
(
futurePlan.PlanId IS NULL
AND lastMk.LastVerificationOn IS NOT NULL
AND COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR) IS NOT NULL
AND DATEADD(month, COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR), lastMk.LastVerificationOn) >= @YearStart
AND DATEADD(month, COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR), lastMk.LastVerificationOn) < @YearEnd
)
)
ORDER BY ownerOrg.NMFRPD, areas.NMOI, names.NMTP, tips.TP, tprz.DPZN, z.NNZV, z.IDEKZ;";
var yearStart = new DateTime(year, 1, 1);
var yearEnd = yearStart.AddYears(1);
var items = new List<PlanningItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@YearStart", SqlDbType.DateTime).Value = yearStart;
command.Parameters.Add("@YearEnd", SqlDbType.DateTime).Value = yearEnd;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new PlanningItem
{
InstrumentId = ReferenceDirectorySqlHelpers.GetInt32(reader, "InstrumentId"),
TypeSizeId = ReferenceDirectorySqlHelpers.GetInt32(reader, "TypeSizeId"),
OwnerOrganizationId = ReferenceDirectorySqlHelpers.GetInt32(reader, "OwnerOrganizationId"),
OwnerOrganizationName = ReferenceDirectorySqlHelpers.GetString(reader, "OwnerOrganizationName"),
MeasurementAreaName = ReferenceDirectorySqlHelpers.GetString(reader, "MeasurementAreaName"),
InstrumentName = ReferenceDirectorySqlHelpers.GetString(reader, "InstrumentName"),
TypeName = ReferenceDirectorySqlHelpers.GetString(reader, "TypeName"),
RangeText = ReferenceDirectorySqlHelpers.GetString(reader, "RangeText"),
RegistryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "RegistryNumber"),
SerialNumber = ReferenceDirectorySqlHelpers.GetString(reader, "SerialNumber"),
InventoryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "InventoryNumber"),
LastVerificationOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "LastVerificationOn"),
PlanId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "PlanId"),
EffectiveTemplateId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "EffectiveTemplateId"),
PeriodMonths = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "PeriodMonths"),
PlannedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "PlannedOn"),
IsExplicitPlan = Convert.ToBoolean(reader.GetValue(reader.GetOrdinal("IsExplicitPlan"))),
PlanSource = ReferenceDirectorySqlHelpers.GetString(reader, "PlanSource"),
PeriodSource = ReferenceDirectorySqlHelpers.GetString(reader, "PeriodSource")
});
}
}
}
return items;
}
public IReadOnlyList<PlanningTemplateOption> LoadTemplateOptions(int typeSizeId)
{
if (typeSizeId <= 0)
{
return Array.Empty<PlanningTemplateOption>();
}
const string sql = @"
SELECT
t.IDTPRMCP AS Id,
t.IDTPRZ AS TypeSizeId,
t.IDSPVDMC AS CycleId,
cycle.NMVDMC AS CycleName,
t.IDGRSI AS GroupId,
groups.NMGRSI AS GroupName,
t.PRMK AS PeriodMonths,
CAST(t.KM AS nvarchar(max)) AS Comment
FROM dbo.TPRMCP t
LEFT JOIN dbo.SPVDMC cycle ON cycle.IDSPVDMC = t.IDSPVDMC
LEFT JOIN dbo.GRSI groups ON groups.IDGRSI = t.IDGRSI
WHERE t.IDTPRZ = @TypeSizeId
ORDER BY t.PRMK, cycle.NMVDMC, groups.NMGRSI, t.IDTPRMCP;";
var items = new List<PlanningTemplateOption>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new PlanningTemplateOption
{
Id = ReferenceDirectorySqlHelpers.GetInt32(reader, "Id"),
TypeSizeId = ReferenceDirectorySqlHelpers.GetInt32(reader, "TypeSizeId"),
CycleId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "CycleId"),
CycleName = ReferenceDirectorySqlHelpers.GetString(reader, "CycleName"),
GroupId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "GroupId"),
GroupName = ReferenceDirectorySqlHelpers.GetString(reader, "GroupName"),
PeriodMonths = ReferenceDirectorySqlHelpers.GetInt32(reader, "PeriodMonths"),
Comment = ReferenceDirectorySqlHelpers.GetString(reader, "Comment")
});
}
}
}
return items;
}
public void UpdatePlanItem(PlanningEditResult item)
{
if (item == null || !item.PlanId.HasValue || item.PlanId.Value <= 0)
{
throw new InvalidOperationException("Не выбрана запись EKZMCP для изменения.");
}
const string sql = @"
UPDATE dbo.EKZMCP
SET IDEKZ = @InstrumentId,
IDTPRMCP = @TemplateId,
DTMKPLO = @PlannedOn
WHERE IDEKZMCP = @Id;
SELECT @@ROWCOUNT;";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsurePlanIsUnique(connection, item.InstrumentId, item.TemplateId, item.PlanId.Value);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = item.PlanId.Value;
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = item.InstrumentId;
command.Parameters.Add("@TemplateId", SqlDbType.Int).Value = item.TemplateId;
command.Parameters.Add("@PlannedOn", SqlDbType.DateTime).Value = item.PlannedOn.Date;
try
{
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись EKZMCP для изменения не найдена.");
}
}
catch (SqlException ex) when (ReferenceDirectorySqlHelpers.IsDuplicateViolation(ex, "XAK1EKZMCP"))
{
throw CreatePlanDuplicateException(ex);
}
}
}
private static Exception CreatePlanDuplicateException(SqlException ex)
{
var suffix = ex == null || string.IsNullOrWhiteSpace(ex.Message)
? string.Empty
: " " + ex.Message.Trim();
return new InvalidOperationException("Для выбранного прибора и периода TPRMCP запись EKZMCP уже существует." + suffix, ex);
}
private static void EnsurePlanIsUnique(SqlConnection connection, int instrumentId, int templateId, int? excludeId)
{
const string sql = @"
SELECT TOP (1) IDEKZMCP
FROM dbo.EKZMCP
WHERE IDEKZ = @InstrumentId
AND IDTPRMCP = @TemplateId
AND (@ExcludeId IS NULL OR IDEKZMCP <> @ExcludeId);";
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = instrumentId;
command.Parameters.Add("@TemplateId", SqlDbType.Int).Value = templateId;
command.Parameters.Add("@ExcludeId", SqlDbType.Int).Value = (object)excludeId ?? DBNull.Value;
if (command.ExecuteScalar() != null)
{
throw CreatePlanDuplicateException(null);
}
}
}
}
}

161
XLAB/PlanningWindow.xaml Normal file
View File

@@ -0,0 +1,161 @@
<Window x:Class="XLAB.PlanningWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Планирование"
Height="860"
Width="1540"
MinHeight="720"
MinWidth="1240"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox Grid.Row="0"
Header="Параметры плана-графика">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<StackPanel DockPanel.Dock="Right"
Orientation="Horizontal">
</StackPanel>
<WrapPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Год" />
<ComboBox Width="120"
ItemsSource="{Binding YearItems}"
SelectedItem="{Binding SelectedYear}" />
</StackPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Организация-владелец" />
<ComboBox Width="360"
ItemsSource="{Binding OwnerFilterItems}"
SelectedValue="{Binding SelectedOwnerFilterId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Поиск" />
<TextBox Width="360"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</WrapPanel>
</DockPanel>
<TextBlock Grid.Row="1"
Foreground="DimGray"
Text="Явный план берётся из EKZMCP.DTMKPLO; при отсутствии записи строка рассчитывается по EKZMK и периоду поверки." />
</Grid>
</GroupBox>
<GroupBox Grid.Row="1"
Margin="0,12,0,0"
Header="План-график поверки">
<DataGrid ItemsSource="{Binding PlanItems}"
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 AddInstrumentCommand}" />
<MenuItem Header="Изменить прибор"
Command="{Binding EditInstrumentCommand}" />
<Separator />
<MenuItem Header="Добавить план"
Command="{Binding AddPlanCommand}" />
<MenuItem Header="Изменить план"
Command="{Binding EditPlanCommand}" />
<MenuItem Header="Удалить план"
Command="{Binding DeletePlanCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="План"
Width="95"
Binding="{Binding PlannedOn, StringFormat=d}" />
<DataGridTextColumn Header="Тип записи"
Width="95"
Binding="{Binding RecordKindText}" />
<DataGridTextColumn Header="Источник"
Width="210"
Binding="{Binding PlanSource}" />
<DataGridTextColumn Header="Основание периода"
Width="170"
Binding="{Binding PeriodSource}" />
<DataGridTextColumn Header="Период"
Width="90"
Binding="{Binding PeriodDisplay}" />
<DataGridTextColumn Header="Последняя поверка"
Width="105"
Binding="{Binding LastVerificationOn, StringFormat=d}" />
<DataGridTextColumn Header="Организация-владелец"
Width="210"
Binding="{Binding OwnerOrganizationName}" />
<DataGridTextColumn Header="Область измерений"
Width="160"
Binding="{Binding MeasurementAreaName}" />
<DataGridTextColumn Header="Наименование"
Width="210"
Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип"
Width="170"
Binding="{Binding TypeName}" />
<DataGridTextColumn Header="Диапазон"
Width="220"
Binding="{Binding RangeText}" />
<DataGridTextColumn Header="№ Госреестра"
Width="110"
Binding="{Binding RegistryNumber}" />
<DataGridTextColumn Header="Заводской номер"
Width="130"
Binding="{Binding SerialNumber}" />
<DataGridTextColumn Header="Инвентарный номер"
Width="130"
Binding="{Binding InventoryNumber}" />
<DataGridTextColumn Header="Сохранение"
Width="110"
Binding="{Binding PersistenceText}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<TextBlock Grid.Row="2"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<StackPanel Grid.Row="3"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
IsCancel="True"
Content="Закрыть" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,33 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB
{
public partial class PlanningWindow : Window
{
private readonly PlanningWindowViewModel _viewModel;
public PlanningWindow()
{
InitializeComponent();
_viewModel = new PlanningWindowViewModel(new PlanningService(), new EkzDirectoryService(), new PlanningDialogService(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,546 @@
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 PlanningWindowViewModel : ObservableObject
{
private readonly IPlanningDialogService _dialogService;
private readonly EkzDirectoryService _ekzService;
private readonly PlanningService _service;
private List<EkzDirectoryItem> _ekzCache;
private bool _isBusy;
private List<PlanningItem> _planCache;
private string _searchText;
private PlanningItem _selectedItem;
private int _selectedOwnerFilterId;
private int _selectedYear;
private string _statusText;
public PlanningWindowViewModel(PlanningService service, EkzDirectoryService ekzService, IPlanningDialogService dialogService)
{
_service = service;
_ekzService = ekzService;
_dialogService = dialogService;
_planCache = new List<PlanningItem>();
_ekzCache = new List<EkzDirectoryItem>();
PlanItems = new ObservableCollection<PlanningItem>();
OwnerFilterItems = new ObservableCollection<DirectoryLookupItem>();
YearItems = new ObservableCollection<int>();
var currentYear = DateTime.Today.Year;
for (var year = currentYear; year <= currentYear + 5; year++)
{
YearItems.Add(year);
}
_selectedYear = currentYear + 1;
OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" });
AddInstrumentCommand = new RelayCommand(delegate { AddInstrumentAsync(); }, delegate { return !IsBusy; });
EditInstrumentCommand = new RelayCommand(delegate { EditInstrumentAsync(); }, delegate { return !IsBusy && SelectedItem != null; });
AddPlanCommand = new RelayCommand(delegate { AddPlanAsync(); }, delegate { return !IsBusy && _ekzCache.Count > 0; });
EditPlanCommand = new RelayCommand(delegate { EditPlanAsync(); }, delegate { return !IsBusy && SelectedItem != null && SelectedItem.CanPersistPlan; });
DeletePlanCommand = new RelayCommand(delegate { DeletePlanAsync(); }, delegate { return !IsBusy && SelectedItem != null && SelectedItem.PlanId.HasValue; });
RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });
UpdateStatus();
}
public ICommand AddInstrumentCommand { get; private set; }
public ICommand AddPlanCommand { get; private set; }
public ICommand DeletePlanCommand { get; private set; }
public ICommand EditInstrumentCommand { get; private set; }
public ICommand EditPlanCommand { get; private set; }
public bool IsBusy
{
get { return _isBusy; }
private set
{
if (SetProperty(ref _isBusy, value))
{
RaiseCommandStates();
}
}
}
public ObservableCollection<DirectoryLookupItem> OwnerFilterItems { get; private set; }
public ObservableCollection<PlanningItem> PlanItems { get; private set; }
public ICommand RefreshCommand { get; private set; }
public string SearchText
{
get { return _searchText; }
set
{
if (SetProperty(ref _searchText, value))
{
ApplyFilter(GetPreferredRowKey(SelectedItem));
}
}
}
public PlanningItem SelectedItem
{
get { return _selectedItem; }
set
{
if (SetProperty(ref _selectedItem, value))
{
RaiseCommandStates();
UpdateStatus();
}
}
}
public int SelectedOwnerFilterId
{
get { return _selectedOwnerFilterId; }
set
{
if (SetProperty(ref _selectedOwnerFilterId, value))
{
ApplyFilter(GetPreferredRowKey(SelectedItem));
}
}
}
public int SelectedYear
{
get { return _selectedYear; }
set
{
if (SetProperty(ref _selectedYear, value) && !IsBusy)
{
RefreshAsync();
}
}
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public ObservableCollection<int> YearItems { get; private set; }
public async Task InitializeAsync()
{
await ExecuteBusyOperationAsync(delegate { return RefreshCoreAsync(null); });
}
private async void AddInstrumentAsync()
{
var result = _dialogService.ShowEkzEditDialog(new EkzDirectoryItem(), true, _ekzCache.ToList(), _ekzService);
if (result == null)
{
return;
}
await RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _ekzService.AddEkzItem(result); });
await RefreshCoreAsync(CreatePreferredRowKey(createdId, null));
_dialogService.ShowInfo("Запись EKZ добавлена.");
});
}
private async void AddPlanAsync()
{
if (_ekzCache.Count == 0)
{
_dialogService.ShowWarning("Список приборов EKZ пуст.");
return;
}
var seed = new PlanningEditSeed
{
TargetYear = SelectedYear,
InstrumentId = SelectedItem == null ? 0 : SelectedItem.InstrumentId,
TemplateId = SelectedItem == null ? (int?)null : SelectedItem.EffectiveTemplateId,
PlannedOn = SelectedItem != null && SelectedItem.PlannedOn.HasValue && SelectedItem.PlannedOn.Value.Year == SelectedYear
? SelectedItem.PlannedOn
: (DateTime?)null
};
var result = _dialogService.ShowPlanningEditDialog(seed, true, BuildInstrumentOptions(), _service);
if (result == null)
{
return;
}
await RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _service.AddPlanItem(result); });
await RefreshCoreAsync(CreatePreferredRowKey(result.InstrumentId, createdId));
_dialogService.ShowInfo("Плановая запись EKZMCP добавлена.");
});
}
private void ApplyFilter(string preferredKey)
{
var filteredItems = _planCache.Where(delegate(PlanningItem item)
{
return MatchesOwnerFilter(item) && MatchesSearch(item);
}).ToList();
PlanItems.Clear();
foreach (var item in filteredItems)
{
PlanItems.Add(item);
}
SelectedItem = string.IsNullOrWhiteSpace(preferredKey)
? PlanItems.FirstOrDefault()
: PlanItems.FirstOrDefault(delegate(PlanningItem item) { return string.Equals(GetPreferredRowKey(item), preferredKey, StringComparison.OrdinalIgnoreCase); })
?? PlanItems.FirstOrDefault();
UpdateStatus();
}
private List<PlanningInstrumentOption> BuildInstrumentOptions()
{
return _ekzCache
.Select(delegate(EkzDirectoryItem item)
{
var tail = string.IsNullOrWhiteSpace(item.InventoryNumber)
? string.Empty
: string.Format(" / инв.№ {0}", item.InventoryNumber);
return new PlanningInstrumentOption
{
Id = item.Id,
TypeSizeId = item.TypeSizeId,
OwnerOrganizationId = item.OwnerOrganizationId,
DisplayName = string.Format(
"{0} / {1} / {2} / {3} / {4} / зав.№ {5}{6}",
item.OwnerOrganizationName,
item.MeasurementAreaName,
item.InstrumentName,
item.TypeName,
item.RangeText,
item.SerialNumber,
tail)
};
})
.OrderBy(delegate(PlanningInstrumentOption item) { return item.DisplayName; }, StringComparer.CurrentCultureIgnoreCase)
.ToList();
}
private static string CloneValue(string value)
{
return value == null ? string.Empty : string.Copy(value);
}
private static EkzDirectoryItem CloneEkz(EkzDirectoryItem source)
{
return new EkzDirectoryItem
{
Id = source.Id,
TypeSizeId = source.TypeSizeId,
MeasurementAreaName = CloneValue(source.MeasurementAreaName),
InstrumentName = CloneValue(source.InstrumentName),
TypeName = CloneValue(source.TypeName),
RangeText = CloneValue(source.RangeText),
AccuracyText = CloneValue(source.AccuracyText),
RegistryNumber = CloneValue(source.RegistryNumber),
OwnerOrganizationId = source.OwnerOrganizationId,
OwnerOrganizationName = CloneValue(source.OwnerOrganizationName),
SerialNumber = CloneValue(source.SerialNumber),
InventoryNumber = CloneValue(source.InventoryNumber),
Notes = CloneValue(source.Notes)
};
}
private static string CreatePreferredRowKey(int instrumentId, int? planId)
{
return string.Format(
"{0}:{1}",
instrumentId,
planId.HasValue ? planId.Value.ToString() : "0");
}
private async void DeletePlanAsync()
{
if (SelectedItem == null || !SelectedItem.PlanId.HasValue)
{
return;
}
var selected = SelectedItem;
var plannedDateText = selected.PlannedOn.HasValue
? selected.PlannedOn.Value.ToString("d")
: "без даты";
var question = string.Format(
"Удалить плановую запись EKZMCP для прибора \"{0}\" с датой {1}?",
selected.SerialNumber,
plannedDateText);
if (!_dialogService.Confirm(question))
{
return;
}
await RunMutationOperation(async delegate
{
await Task.Run(delegate { _service.DeletePlanItem(selected.PlanId.Value); });
await RefreshCoreAsync(CreatePreferredRowKey(selected.InstrumentId, null));
_dialogService.ShowInfo("Плановая запись EKZMCP удалена.");
});
}
private async void EditInstrumentAsync()
{
if (SelectedItem == null)
{
return;
}
var current = _ekzCache.FirstOrDefault(delegate(EkzDirectoryItem item) { return item.Id == SelectedItem.InstrumentId; });
if (current == null)
{
_dialogService.ShowWarning("Выбранный прибор не найден в EKZ.");
return;
}
var result = _dialogService.ShowEkzEditDialog(CloneEkz(current), false, _ekzCache.ToList(), _ekzService);
if (result == null)
{
return;
}
var preferredKey = GetPreferredRowKey(SelectedItem);
await RunMutationOperation(async delegate
{
await Task.Run(delegate { _ekzService.UpdateEkzItem(result); });
await RefreshCoreAsync(preferredKey);
_dialogService.ShowInfo("Запись EKZ обновлена.");
});
}
private async void EditPlanAsync()
{
if (SelectedItem == null)
{
return;
}
if (!SelectedItem.CanPersistPlan)
{
_dialogService.ShowWarning("Для выбранного прибора найден только расчетный период без TPRMCP. Сохранить запись в EKZMCP нельзя.");
return;
}
var seed = new PlanningEditSeed
{
TargetYear = SelectedYear,
PlanId = SelectedItem.PlanId,
InstrumentId = SelectedItem.InstrumentId,
TemplateId = SelectedItem.EffectiveTemplateId,
PlannedOn = SelectedItem.PlannedOn
};
var isNew = !SelectedItem.PlanId.HasValue;
var result = _dialogService.ShowPlanningEditDialog(seed, isNew, BuildInstrumentOptions(), _service);
if (result == null)
{
return;
}
await RunMutationOperation(async delegate
{
if (result.PlanId.HasValue)
{
await Task.Run(delegate { _service.UpdatePlanItem(result); });
await RefreshCoreAsync(CreatePreferredRowKey(result.InstrumentId, result.PlanId));
_dialogService.ShowInfo("Плановая запись EKZMCP обновлена.");
}
else
{
var createdId = await Task.Run(delegate { return _service.AddPlanItem(result); });
await RefreshCoreAsync(CreatePreferredRowKey(result.InstrumentId, createdId));
_dialogService.ShowInfo("Плановая запись EKZMCP добавлена.");
}
});
}
private async Task ExecuteBusyOperationAsync(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 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 static string GetPreferredRowKey(PlanningItem item)
{
return item == null ? null : CreatePreferredRowKey(item.InstrumentId, item.PlanId);
}
private bool MatchesOwnerFilter(PlanningItem item)
{
return SelectedOwnerFilterId <= 0
|| (item != null && item.OwnerOrganizationId == SelectedOwnerFilterId);
}
private bool MatchesSearch(PlanningItem item)
{
var tokens = GetSearchTokens();
if (tokens.Length == 0)
{
return true;
}
var haystack = string.Join(
" ",
new[]
{
item == null ? null : item.OwnerOrganizationName,
item == null ? null : item.MeasurementAreaName,
item == null ? null : item.InstrumentName,
item == null ? null : item.TypeName,
item == null ? null : item.RangeText,
item == null ? null : item.RegistryNumber,
item == null ? null : item.SerialNumber,
item == null ? null : item.InventoryNumber,
item == null ? null : item.PlanSource,
item == null ? null : item.PeriodSource
})
.ToUpperInvariant();
return tokens.All(delegate(string token) { return haystack.Contains(token); });
}
private void RaiseCommandStates()
{
var addInstrumentCommand = AddInstrumentCommand as RelayCommand;
if (addInstrumentCommand != null)
{
addInstrumentCommand.RaiseCanExecuteChanged();
}
var editInstrumentCommand = EditInstrumentCommand as RelayCommand;
if (editInstrumentCommand != null)
{
editInstrumentCommand.RaiseCanExecuteChanged();
}
var addPlanCommand = AddPlanCommand as RelayCommand;
if (addPlanCommand != null)
{
addPlanCommand.RaiseCanExecuteChanged();
}
var editPlanCommand = EditPlanCommand as RelayCommand;
if (editPlanCommand != null)
{
editPlanCommand.RaiseCanExecuteChanged();
}
var deletePlanCommand = DeletePlanCommand as RelayCommand;
if (deletePlanCommand != null)
{
deletePlanCommand.RaiseCanExecuteChanged();
}
var refreshCommand = RefreshCommand as RelayCommand;
if (refreshCommand != null)
{
refreshCommand.RaiseCanExecuteChanged();
}
}
private async Task RefreshCoreAsync(string preferredKey)
{
var planTask = Task.Run(delegate { return _service.LoadPlanItems(SelectedYear); });
var ownerTask = Task.Run(delegate { return _service.LoadOwnerItems(); });
var ekzTask = Task.Run(delegate { return _ekzService.LoadEkzItems(); });
await Task.WhenAll(planTask, ownerTask, ekzTask);
_planCache = planTask.Result.ToList();
_ekzCache = ekzTask.Result.ToList();
ApplyOwnerItems(ownerTask.Result);
ApplyFilter(preferredKey);
}
private void ApplyOwnerItems(IReadOnlyList<DirectoryLookupItem> items)
{
OwnerFilterItems.Clear();
OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" });
foreach (var item in items ?? Array.Empty<DirectoryLookupItem>())
{
OwnerFilterItems.Add(item);
}
if (!OwnerFilterItems.Any(delegate(DirectoryLookupItem item) { return item.Id == SelectedOwnerFilterId; }))
{
SelectedOwnerFilterId = 0;
}
}
private async void RefreshAsync()
{
await ExecuteBusyOperationAsync(delegate { return RefreshCoreAsync(GetPreferredRowKey(SelectedItem)); });
}
private async Task RunMutationOperation(Func<Task> operation)
{
await ExecuteBusyOperationAsync(operation);
}
private void UpdateStatus()
{
var explicitCount = PlanItems.Count(delegate(PlanningItem item) { return item.IsExplicitPlan; });
var calculatedCount = PlanItems.Count - explicitCount;
var nonPersistableCount = PlanItems.Count(delegate(PlanningItem item) { return !item.CanPersistPlan; });
StatusText = string.Format(
"Год: {0}. Позиций: {1}. Явных планов EKZMCP: {2}. Расчетных позиций: {3}. Без TPRMCP: {4}.",
SelectedYear,
PlanItems.Count,
explicitCount,
calculatedCount,
nonPersistableCount);
}
}
}

View File

@@ -4,11 +4,15 @@ using System.Configuration;
using System.Data; using System.Data;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace XLAB namespace XLAB
{ {
internal sealed class PsvDataService internal sealed class PsvDataService
{ {
private const int EkzMkCompletenessMaxLength = 600;
public bool DocumentNumberExists(string documentNumber, string excludeDocumentNumber) public bool DocumentNumberExists(string documentNumber, string excludeDocumentNumber)
{ {
var normalizedNumber = NormalizeDocumentNumber(documentNumber); var normalizedNumber = NormalizeDocumentNumber(documentNumber);
@@ -34,6 +38,15 @@ WHERE NNZVPV = @DocumentNumber
} }
} }
public Task<bool> DocumentNumberExistsAsync(string documentNumber, string excludeDocumentNumber, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return DocumentNumberExists(documentNumber, excludeDocumentNumber);
}, cancellationToken);
}
public IReadOnlyList<CustomerReference> LoadCustomers() public IReadOnlyList<CustomerReference> LoadCustomers()
{ {
const string sql = @" const string sql = @"
@@ -558,11 +571,84 @@ ORDER BY " + (loadClosedDocumentsForCurrentYear
}); });
} }
} }
var serialNumbersByDocument = LoadDocumentSerialNumbers(connection, loadClosedDocumentsForCurrentYear, currentYearStart, nextYearStart);
foreach (var document in documents)
{
string serialNumbersText;
if (serialNumbersByDocument.TryGetValue(document.DocumentNumber, out serialNumbersText))
{
document.SerialNumbersText = serialNumbersText;
}
}
} }
return documents; return documents;
} }
private Dictionary<string, string> LoadDocumentSerialNumbers(SqlConnection connection, bool loadClosedDocumentsForCurrentYear, DateTime currentYearStart, DateTime nextYearStart)
{
var sql = @"
WITH filteredDocuments AS
(
SELECT m.NNZVPV
FROM dbo.EKZMK m
WHERE NULLIF(LTRIM(RTRIM(m.NNZVPV)), N'') IS NOT NULL
GROUP BY m.NNZVPV
HAVING " + (loadClosedDocumentsForCurrentYear
? "MAX(m.DTVDM) >= @IssuedFrom AND MAX(m.DTVDM) < @IssuedTo"
: "MAX(m.DTVDM) IS NULL") + @"
)
SELECT
m.NNZVPV AS DocumentNumber,
z.NNZV AS SerialNumber
FROM dbo.EKZMK m
JOIN filteredDocuments documents ON documents.NNZVPV = m.NNZVPV
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
WHERE NULLIF(LTRIM(RTRIM(z.NNZV)), N'') IS NOT NULL
ORDER BY m.NNZVPV, z.NNZV;";
var serialNumbersByDocument = new Dictionary<string, SortedSet<string>>(StringComparer.OrdinalIgnoreCase);
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60;
if (loadClosedDocumentsForCurrentYear)
{
command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart;
command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart;
}
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var documentNumber = GetString(reader, "DocumentNumber");
var serialNumber = GetString(reader, "SerialNumber");
if (string.IsNullOrWhiteSpace(documentNumber) || string.IsNullOrWhiteSpace(serialNumber))
{
continue;
}
SortedSet<string> serialNumbers;
if (!serialNumbersByDocument.TryGetValue(documentNumber, out serialNumbers))
{
serialNumbers = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
serialNumbersByDocument[documentNumber] = serialNumbers;
}
serialNumbers.Add(serialNumber.Trim());
}
}
}
return serialNumbersByDocument.ToDictionary(
delegate(KeyValuePair<string, SortedSet<string>> pair) { return pair.Key; },
delegate(KeyValuePair<string, SortedSet<string>> pair) { return string.Join(", ", pair.Value); },
StringComparer.OrdinalIgnoreCase);
}
public IReadOnlyList<PsvDocumentLine> LoadDocumentLines(string documentNumber) public IReadOnlyList<PsvDocumentLine> LoadDocumentLines(string documentNumber)
{ {
const string sql = @" const string sql = @"
@@ -1025,7 +1111,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
} }
} }
public DocumentSaveResult SaveDocument(string currentDocumentNumber, DocumentEditorResult document, IEnumerable<PsvDocumentLine> pendingLines) public DocumentSaveResult SaveDocument(string currentDocumentNumber, DocumentEditorResult document, IEnumerable<PsvDocumentLine> documentLines)
{ {
if (document == null) if (document == null)
{ {
@@ -1040,13 +1126,22 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
document.DocumentNumber = normalizedNumber; document.DocumentNumber = normalizedNumber;
var distinctPendingLines = pendingLines == null var materializedDocumentLines = documentLines == null
? new List<PsvDocumentLine>() ? new List<PsvDocumentLine>()
: pendingLines : documentLines
.Where(delegate(PsvDocumentLine line) { return line != null; })
.ToList();
var distinctPendingLines = materializedDocumentLines
.Where(IsPendingLineReadyForSave) .Where(IsPendingLineReadyForSave)
.GroupBy(GetPendingLineSaveKey, StringComparer.OrdinalIgnoreCase) .GroupBy(GetPendingLineSaveKey, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, PsvDocumentLine> group) { return group.First(); }) .Select(delegate(IGrouping<string, PsvDocumentLine> group) { return group.First(); })
.ToList(); .ToList();
var persistedLines = materializedDocumentLines
.Where(delegate(PsvDocumentLine line) { return !line.IsPendingInsert && line.CardId > 0; })
.GroupBy(delegate(PsvDocumentLine line) { return line.CardId; })
.Select(delegate(IGrouping<int, PsvDocumentLine> group) { return group.First(); })
.ToList();
using (var connection = CreateConnection()) using (var connection = CreateConnection())
{ {
@@ -1081,6 +1176,12 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
{ {
throw new InvalidOperationException("Строки EKZMK для выбранного ПСВ не найдены."); throw new InvalidOperationException("Строки EKZMK для выбранного ПСВ не найдены.");
} }
var updatedCompletenessCount = UpdateDocumentLineCompleteness(connection, transaction, normalizedNumber, persistedLines);
if (updatedCompletenessCount > updatedEkzMkCount)
{
updatedEkzMkCount = updatedCompletenessCount;
}
} }
var insertedEkzMkCount = 0; var insertedEkzMkCount = 0;
@@ -1122,13 +1223,19 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
continue; continue;
} }
var effectiveVerificationTypeId = template.IdSpvdmk;
if (!effectiveVerificationTypeId.HasValue || effectiveVerificationTypeId.Value <= 0)
{
if (!verificationTypeLoaded) if (!verificationTypeLoaded)
{ {
verificationTypeId = LoadVerificationTypeId(connection, transaction); verificationTypeId = LoadVerificationTypeId(connection, transaction);
verificationTypeLoaded = true; verificationTypeLoaded = true;
} }
var cardId = InsertEkzMk(connection, transaction, verificationTypeId, document, normalizedNumber, instrumentId, template, pendingLine); effectiveVerificationTypeId = verificationTypeId;
}
var cardId = InsertEkzMk(connection, transaction, effectiveVerificationTypeId.Value, document, normalizedNumber, instrumentId, template, pendingLine);
if (!string.IsNullOrWhiteSpace(pendingLine.VerificationDocumentNumber)) if (!string.IsNullOrWhiteSpace(pendingLine.VerificationDocumentNumber))
{ {
if (!pendingLine.VerificationDocumentFormId.HasValue || !pendingLine.VerificationDocumentLinkTypeId.HasValue) if (!pendingLine.VerificationDocumentFormId.HasValue || !pendingLine.VerificationDocumentLinkTypeId.HasValue)
@@ -1277,6 +1384,123 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
} }
} }
public Task<IReadOnlyList<CustomerReference>> LoadCustomersAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<CustomerReference>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadCustomers();
}, cancellationToken);
}
public Task<IReadOnlyList<PersonReference>> LoadVerifiersAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<PersonReference>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadVerifiers();
}, cancellationToken);
}
public Task<IReadOnlyList<DocumentFormReference>> LoadVerificationDocumentFormsAsync(bool isPassed, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<DocumentFormReference>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadVerificationDocumentForms(isPassed);
}, cancellationToken);
}
public Task<IReadOnlyList<PsvDocumentSummary>> LoadDocumentsAsync(bool loadClosedDocumentsForCurrentYear, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<PsvDocumentSummary>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadDocuments(loadClosedDocumentsForCurrentYear);
}, cancellationToken);
}
public Task<IReadOnlyList<PsvDocumentLine>> LoadDocumentLinesAsync(string documentNumber, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<PsvDocumentLine>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadDocumentLines(documentNumber);
}, cancellationToken);
}
public Task<IReadOnlyList<AvailableInstrumentItem>> LoadCustomerInstrumentsAsync(int customerId, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<AvailableInstrumentItem>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadCustomerInstruments(customerId);
}, cancellationToken);
}
public Task<IReadOnlyList<AvailableInstrumentItem>> LoadInstrumentTypesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<AvailableInstrumentItem>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadInstrumentTypes();
}, cancellationToken);
}
public Task ResetLineVerificationAsync(int cardId, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
ResetLineVerification(cardId);
}, cancellationToken);
}
public Task SaveLineVerificationAsync(int cardId, VerificationEditResult result, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
SaveLineVerification(cardId, result);
}, cancellationToken);
}
public Task ResetLineVerificationAsync(IEnumerable<int> cardIds, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
ResetLineVerification(cardIds);
}, cancellationToken);
}
public Task SaveLineVerificationAsync(IEnumerable<int> cardIds, VerificationEditResult result, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
SaveLineVerification(cardIds, result);
}, cancellationToken);
}
public Task<DocumentDeleteResult> DeleteDocumentAsync(string documentNumber, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return DeleteDocument(documentNumber);
}, cancellationToken);
}
public Task<DocumentGroupDeleteResult> DeleteDocumentGroupsAsync(string documentNumber, IEnumerable<int> cardIds, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return DeleteDocumentGroups(documentNumber, cardIds);
}, cancellationToken);
}
private static string GetPendingLineSaveKey(PsvDocumentLine line) private static string GetPendingLineSaveKey(PsvDocumentLine line)
{ {
if (line == null) if (line == null)
@@ -1292,6 +1516,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
private static bool IsPendingLineReadyForSave(PsvDocumentLine line) private static bool IsPendingLineReadyForSave(PsvDocumentLine line)
{ {
return line != null return line != null
&& line.IsPendingInsert
&& (line.InstrumentId > 0 && (line.InstrumentId > 0
|| (line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber))); || (line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber)));
} }
@@ -1319,6 +1544,11 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
throw new InvalidOperationException("Для новой строки ПСВ не указан заводской номер."); throw new InvalidOperationException("Для новой строки ПСВ не указан заводской номер.");
} }
if (serialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength));
}
if (!document.CustomerId.HasValue) if (!document.CustomerId.HasValue)
{ {
throw new InvalidOperationException("Для добавления прибора по типу должен быть выбран заказчик ПСВ."); throw new InvalidOperationException("Для добавления прибора по типу должен быть выбран заказчик ПСВ.");
@@ -1934,7 +2164,7 @@ VALUES
@idsptsmp, @idsptsmp,
@idspssmp, @idspssmp,
@GUIDEKZMK, @GUIDEKZMK,
NULL, @DSEKZMK,
NULL, NULL,
NULL, NULL,
NULL, NULL,
@@ -1988,6 +2218,9 @@ SELECT CAST(SCOPE_IDENTITY() AS int);";
command.Parameters.Add("@idsptsmp", SqlDbType.Int).Value = (object)template.IdSptsmp ?? DBNull.Value; command.Parameters.Add("@idsptsmp", SqlDbType.Int).Value = (object)template.IdSptsmp ?? DBNull.Value;
command.Parameters.Add("@idspssmp", SqlDbType.Int).Value = (object)template.IdSpssmp ?? DBNull.Value; command.Parameters.Add("@idspssmp", SqlDbType.Int).Value = (object)template.IdSpssmp ?? DBNull.Value;
command.Parameters.Add("@GUIDEKZMK", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid(); command.Parameters.Add("@GUIDEKZMK", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
command.Parameters.Add("@DSEKZMK", SqlDbType.NVarChar, EkzMkCompletenessMaxLength).Value = pendingLine == null
? DBNull.Value
: (object)NormalizeOptionalCompleteness(pendingLine.Notes) ?? DBNull.Value;
command.Parameters.Add("@NRVRMNDmp", SqlDbType.Decimal).Value = (object)template.Nrvrmndmp ?? DBNull.Value; command.Parameters.Add("@NRVRMNDmp", SqlDbType.Decimal).Value = (object)template.Nrvrmndmp ?? DBNull.Value;
command.Parameters["@NRVRMNDmp"].Precision = 10; command.Parameters["@NRVRMNDmp"].Precision = 10;
command.Parameters["@NRVRMNDmp"].Scale = 2; command.Parameters["@NRVRMNDmp"].Scale = 2;
@@ -2246,8 +2479,17 @@ WHERE z.IDEKZ = @InstrumentId;";
const string sql = @" const string sql = @"
SELECT TOP (1) IDSPVDMK SELECT TOP (1) IDSPVDMK
FROM dbo.SPVDMK FROM dbo.SPVDMK
WHERE OBVDMK = N'П' OR NMVDMK = N'Поверка' WHERE UPPER(LTRIM(RTRIM(ISNULL(OBVDMK, N'')))) = N'П'
ORDER BY CASE WHEN OBVDMK = N'П' THEN 0 ELSE 1 END, IDSPVDMK;"; OR UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) = N'ПОВЕРКА'
OR UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) LIKE N'ПОВЕРК%'
OR UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) LIKE N'%ПОВЕРК%'
ORDER BY CASE
WHEN UPPER(LTRIM(RTRIM(ISNULL(OBVDMK, N'')))) = N'П' THEN 0
WHEN UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) = N'ПОВЕРКА' THEN 1
WHEN UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) LIKE N'ПОВЕРК%' THEN 2
ELSE 3
END,
IDSPVDMK;";
using (var command = new SqlCommand(sql, connection, transaction)) using (var command = new SqlCommand(sql, connection, transaction))
{ {
@@ -2277,6 +2519,7 @@ WITH TemplateCandidates AS
m.IDSPMU, m.IDSPMU,
m.IDGRSI, m.IDGRSI,
m.IDKSPRL, m.IDKSPRL,
m.IDSPVDMK,
m.IDSPVDMC, m.IDSPVDMC,
m.IDFRPD, m.IDFRPD,
m.IDSPMPOB, m.IDSPMPOB,
@@ -2315,6 +2558,7 @@ WITH TemplateCandidates AS
m.IDSPMU, m.IDSPMU,
m.IDGRSI, m.IDGRSI,
m.IDKSPRL, m.IDKSPRL,
m.IDSPVDMK,
m.IDSPVDMC, m.IDSPVDMC,
m.IDFRPD, m.IDFRPD,
m.IDSPMPOB, m.IDSPMPOB,
@@ -2363,6 +2607,7 @@ ORDER BY Priority;";
IdSpmu = GetNullableInt32(reader, "IDSPMU"), IdSpmu = GetNullableInt32(reader, "IDSPMU"),
IdGrsi = GetNullableInt32(reader, "IDGRSI"), IdGrsi = GetNullableInt32(reader, "IDGRSI"),
IdKsprl = GetNullableInt32(reader, "IDKSPRL"), IdKsprl = GetNullableInt32(reader, "IDKSPRL"),
IdSpvdmk = GetNullableInt32(reader, "IDSPVDMK"),
IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"), IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"),
IdFrpd = GetInt32(reader, "IDFRPD"), IdFrpd = GetInt32(reader, "IDFRPD"),
IdSpmpob = GetNullableInt32(reader, "IDSPMPOB"), IdSpmpob = GetNullableInt32(reader, "IDSPMPOB"),
@@ -2464,6 +2709,7 @@ WHERE z.IDEKZ = @InstrumentId
IdSpmu = null, IdSpmu = null,
IdGrsi = GetNullableInt32(reader, "IDGRSI"), IdGrsi = GetNullableInt32(reader, "IDGRSI"),
IdKsprl = null, IdKsprl = null,
IdSpvdmk = null,
IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"), IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"),
IdFrpd = GetInt32(reader, "IDFRPD"), IdFrpd = GetInt32(reader, "IDFRPD"),
IdSpmpob = null, IdSpmpob = null,
@@ -2494,6 +2740,75 @@ WHERE z.IDEKZ = @InstrumentId
} }
} }
private static int UpdateDocumentLineCompleteness(
SqlConnection connection,
SqlTransaction transaction,
string documentNumber,
IEnumerable<PsvDocumentLine> persistedLines)
{
var materializedLines = persistedLines == null
? new List<PsvDocumentLine>()
: persistedLines
.Where(delegate(PsvDocumentLine line) { return line != null && line.CardId > 0; })
.GroupBy(delegate(PsvDocumentLine line) { return line.CardId; })
.Select(delegate(IGrouping<int, PsvDocumentLine> group) { return group.First(); })
.ToList();
var updatedCount = 0;
foreach (var line in materializedLines)
{
updatedCount += UpdateDocumentLineCompletenessCore(
connection,
transaction,
documentNumber,
line.CardId,
NormalizeOptionalCompleteness(line.Notes));
}
return updatedCount;
}
private static int UpdateDocumentLineCompletenessCore(
SqlConnection connection,
SqlTransaction transaction,
string documentNumber,
int cardId,
string completeness)
{
const string sql = @"
UPDATE dbo.EKZMK
SET DSEKZMK = @Completeness
WHERE NNZVPV = @DocumentNumber
AND IDEKZMK = @CardId;
SELECT @@ROWCOUNT;";
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = 60;
command.Parameters.Add("@DocumentNumber", SqlDbType.NVarChar, 60).Value = documentNumber;
command.Parameters.Add("@CardId", SqlDbType.Int).Value = cardId;
command.Parameters.Add("@Completeness", SqlDbType.NVarChar, EkzMkCompletenessMaxLength).Value = (object)completeness ?? DBNull.Value;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static string NormalizeOptionalCompleteness(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
var normalizedValue = value.Trim();
if (normalizedValue.Length > EkzMkCompletenessMaxLength)
{
throw new InvalidOperationException(string.Format("Комплектность не должна превышать {0} символов.", EkzMkCompletenessMaxLength));
}
return normalizedValue;
}
private static string NormalizeDocumentNumber(string value) private static string NormalizeDocumentNumber(string value)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))

View File

@@ -1,4 +1,4 @@
using System; using System;
namespace XLAB namespace XLAB
{ {
@@ -40,6 +40,7 @@ namespace XLAB
private DateTime? _issuedOn; private DateTime? _issuedOn;
private int _itemCount; private int _itemCount;
private int _passedCount; private int _passedCount;
private string _serialNumbersText;
public DateTime? AcceptedOn public DateTime? AcceptedOn
{ {
@@ -89,6 +90,12 @@ namespace XLAB
set { SetProperty(ref _documentNumber, value); } set { SetProperty(ref _documentNumber, value); }
} }
public string SerialNumbersText
{
get { return _serialNumbersText; }
set { SetProperty(ref _serialNumbersText, value); }
}
public int FailedCount public int FailedCount
{ {
get { return _failedCount; } get { return _failedCount; }
@@ -136,6 +143,21 @@ namespace XLAB
get { return AcceptedOn.HasValue ? AcceptedOn.Value.Date.AddDays(30) : (DateTime?)null; } get { return AcceptedOn.HasValue ? AcceptedOn.Value.Date.AddDays(30) : (DateTime?)null; }
} }
public string TimelineDisplay
{
get
{
if (IssuedOn.HasValue)
{
return string.Format("Выдача: {0:d}", IssuedOn.Value);
}
return DueOn.HasValue
? string.Format("Срок: {0:d}", DueOn.Value)
: string.Empty;
}
}
public bool IsOpenDocumentOverdue public bool IsOpenDocumentOverdue
{ {
get get
@@ -172,6 +194,7 @@ namespace XLAB
private void RaiseOpenDocumentTimelinePropertiesChanged() private void RaiseOpenDocumentTimelinePropertiesChanged()
{ {
OnPropertyChanged("DueOn"); OnPropertyChanged("DueOn");
OnPropertyChanged("TimelineDisplay");
OnPropertyChanged("IsOpenDocumentOverdue"); OnPropertyChanged("IsOpenDocumentOverdue");
OnPropertyChanged("IsOpenDocumentAtTwentyDays"); OnPropertyChanged("IsOpenDocumentAtTwentyDays");
OnPropertyChanged("IsOpenDocumentAtTenDays"); OnPropertyChanged("IsOpenDocumentAtTenDays");
@@ -181,6 +204,7 @@ namespace XLAB
public sealed class PsvDocumentLine : ObservableObject public sealed class PsvDocumentLine : ObservableObject
{ {
private bool _isBatchSelected; private bool _isBatchSelected;
private string _notes;
public int CardId { get; set; } public int CardId { get; set; }
@@ -234,7 +258,23 @@ namespace XLAB
public string RejectionReason { get; set; } public string RejectionReason { get; set; }
public string Notes { get; set; } public string Notes
{
get { return _notes; }
set
{
if (SetProperty(ref _notes, value))
{
OnPropertyChanged("Completeness");
}
}
}
public string Completeness
{
get { return Notes; }
set { Notes = value; }
}
public bool IsPendingInsert { get; set; } public bool IsPendingInsert { get; set; }
@@ -324,12 +364,18 @@ namespace XLAB
{ {
private bool _isBatchSelected; private bool _isBatchSelected;
public string InstrumentName { get; set; }
public string InstrumentType { get; set; } public string InstrumentType { get; set; }
public string RangeText { get; set; } public string RangeText { get; set; }
public string AccuracyText { get; set; }
public string RegistryNumber { get; set; } public string RegistryNumber { get; set; }
public string SerialNumbersText { get; set; }
public int InVerificationCount { get; set; } public int InVerificationCount { get; set; }
public int VerifiedCount { get; set; } public int VerifiedCount { get; set; }
@@ -349,6 +395,7 @@ namespace XLAB
return line != null return line != null
&& string.Equals(InstrumentType ?? string.Empty, line.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(InstrumentType ?? string.Empty, line.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(RangeText ?? string.Empty, line.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(RangeText ?? string.Empty, line.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(AccuracyText ?? string.Empty, line.AccuracyText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(RegistryNumber ?? string.Empty, line.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase); && string.Equals(RegistryNumber ?? string.Empty, line.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
} }
} }
@@ -483,7 +530,7 @@ namespace XLAB
{ {
public AvailableInstrumentItem TypeItem { get; set; } public AvailableInstrumentItem TypeItem { get; set; }
public string SerialNumber { get; set; } public System.Collections.Generic.IReadOnlyList<string> SerialNumbers { get; set; }
} }
public sealed class VerificationEditSeed public sealed class VerificationEditSeed
@@ -615,6 +662,8 @@ namespace XLAB
public int? IdSptsmp { get; set; } public int? IdSptsmp { get; set; }
public int? IdSpvdmk { get; set; }
public int? IdSpvdkl { get; set; } public int? IdSpvdkl { get; set; }
public int? IdSpvdsbmk { get; set; } public int? IdSpvdsbmk { get; set; }

View File

@@ -3,15 +3,21 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace XLAB namespace XLAB
{ {
internal sealed class PsvPrintService internal sealed class PsvPrintService
{ {
private const int ClosePsvTableColumnCount = 12;
private const int OpenPsvTableColumnCount = 7;
private const int PrintDialogId = 88; private const int PrintDialogId = 88;
private const int WordActiveEndAdjustedPageNumber = 1;
private const int WordAlertsNone = 0; private const int WordAlertsNone = 0;
private const int WordCloseDoNotSaveChanges = 0; private const int WordCloseDoNotSaveChanges = 0;
private const int WordParagraphTrue = -1;
private const int WordStatisticPages = 2;
public void PrintDocument(PsvDocumentSummary document, IReadOnlyList<PsvDocumentLine> lines) public void PrintDocument(PsvDocumentSummary document, IReadOnlyList<PsvDocumentLine> lines)
{ {
@@ -165,7 +171,7 @@ namespace XLAB
ReplacePlaceholder(document, "next", FormatDate(dueDate)); ReplacePlaceholder(document, "next", FormatDate(dueDate));
} }
FillTable(document, groupedLines); FillTable(document, groupedLines, summary.IssuedOn.HasValue);
} }
private static void PopulateVerificationTemplate(dynamic document, PsvDocumentLine line) private static void PopulateVerificationTemplate(dynamic document, PsvDocumentLine line)
@@ -187,13 +193,14 @@ namespace XLAB
ReplacePlaceholder(document, "date", FormatDate(verificationDate)); ReplacePlaceholder(document, "date", FormatDate(verificationDate));
} }
private static void FillTable(dynamic document, IReadOnlyList<PrintedGroupRow> rowsToPrint) private static void FillTable(dynamic document, IReadOnlyList<PrintedGroupRow> rowsToPrint, bool isClosedDocument)
{ {
dynamic table = null; dynamic table = null;
try try
{ {
table = document.Tables.Item(1); table = document.Tables.Item(1);
EnsurePsvTableLayout(table, isClosedDocument ? ClosePsvTableColumnCount : OpenPsvTableColumnCount);
for (var index = 0; index < rowsToPrint.Count; index++) for (var index = 0; index < rowsToPrint.Count; index++)
{ {
@@ -209,15 +216,28 @@ namespace XLAB
SetCellText(row, 4, rowData.RangeText, false); SetCellText(row, 4, rowData.RangeText, false);
SetCellText(row, 5, rowData.SerialNumberText, false); SetCellText(row, 5, rowData.SerialNumberText, false);
SetCellText(row, 6, rowData.GroupCount.ToString(CultureInfo.InvariantCulture), true); SetCellText(row, 6, rowData.GroupCount.ToString(CultureInfo.InvariantCulture), true);
if (isClosedDocument)
{
SetCellText(row, 7, rowData.PassedCount > 0 ? rowData.PassedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true); SetCellText(row, 7, rowData.PassedCount > 0 ? rowData.PassedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true);
SetCellText(row, 8, rowData.FailedCount > 0 ? rowData.FailedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true); SetCellText(row, 8, rowData.FailedCount > 0 ? rowData.FailedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true);
SetCellText(row, 9, rowData.Notes, false); SetCellText(row, 9, rowData.VerificationDocumentText, false);
SetCellText(row, 10, rowData.StickerNumberText, false);
SetCellText(row, 11, rowData.VerifierNameText, false);
SetCellText(row, 12, rowData.Notes, false);
}
else
{
SetCellText(row, 7, rowData.Notes, false);
}
} }
finally finally
{ {
ReleaseComObject(row); ReleaseComObject(row);
} }
} }
EnsureTableSpillsToNextPage(document, table);
} }
finally finally
{ {
@@ -225,6 +245,99 @@ namespace XLAB
} }
} }
private static void EnsureTableSpillsToNextPage(dynamic document, dynamic table)
{
if (document == null || table == null)
{
return;
}
var rowCount = Convert.ToInt32(table.Rows.Count, CultureInfo.InvariantCulture);
if (rowCount <= 1)
{
return;
}
RepaginateDocument(document);
var totalPages = InvokeComIntMethod(document, "ComputeStatistics", WordStatisticPages);
if (totalPages <= 1)
{
return;
}
dynamic lastRow = null;
dynamic firstCell = null;
dynamic lastRowRange = null;
dynamic firstCellRange = null;
dynamic paragraphFormat = null;
try
{
lastRow = table.Rows.Item(rowCount);
lastRowRange = lastRow.Range;
var lastRowPage = GetRangePageNumber(lastRowRange);
if (lastRowPage >= totalPages)
{
return;
}
firstCell = lastRow.Cells.Item(1);
firstCellRange = firstCell.Range;
paragraphFormat = firstCellRange.ParagraphFormat;
paragraphFormat.PageBreakBefore = WordParagraphTrue;
RepaginateDocument(document);
}
finally
{
ReleaseComObject(paragraphFormat);
ReleaseComObject(firstCellRange);
ReleaseComObject(lastRowRange);
ReleaseComObject(firstCell);
ReleaseComObject(lastRow);
}
}
private static void EnsurePsvTableLayout(dynamic table, int expectedColumnCount)
{
if (table == null)
{
throw new InvalidOperationException("В шаблоне ПСВ не найдена таблица для печати.");
}
int actualColumnCount;
try
{
actualColumnCount = Convert.ToInt32(table.Columns.Count, CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
throw new InvalidOperationException("Не удалось определить структуру таблицы шаблона ПСВ.", ex);
}
if (actualColumnCount != expectedColumnCount)
{
throw new InvalidOperationException(
string.Format(
CultureInfo.InvariantCulture,
"Шаблон ПСВ имеет неверную структуру таблицы: ожидается {0} колонок, найдено {1}.",
expectedColumnCount,
actualColumnCount));
}
}
private static int GetRangePageNumber(object range)
{
return InvokeComIndexedIntProperty(range, "Information", WordActiveEndAdjustedPageNumber);
}
private static void RepaginateDocument(object document)
{
InvokeComMethod(document, "Repaginate");
}
private static void SetCellText(dynamic row, int columnIndex, string value, bool centerAlign) private static void SetCellText(dynamic row, int columnIndex, string value, bool centerAlign)
{ {
dynamic cell = null; dynamic cell = null;
@@ -274,7 +387,56 @@ namespace XLAB
} }
} }
private static IReadOnlyList<PrintedGroupRow> BuildPrintedGroups(IEnumerable<PsvDocumentLine> lines, bool includeClosedNotes) private static int InvokeComIndexedIntProperty(object target, string propertyName, object argument)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
return Convert.ToInt32(
target.GetType().InvokeMember(
propertyName,
BindingFlags.GetProperty,
null,
target,
new[] { argument }),
CultureInfo.InvariantCulture);
}
private static int InvokeComIntMethod(object target, string methodName, object argument)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
return Convert.ToInt32(
target.GetType().InvokeMember(
methodName,
BindingFlags.InvokeMethod,
null,
target,
new[] { argument }),
CultureInfo.InvariantCulture);
}
private static void InvokeComMethod(object target, string methodName)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
target.GetType().InvokeMember(
methodName,
BindingFlags.InvokeMethod,
null,
target,
Array.Empty<object>());
}
private static IReadOnlyList<PrintedGroupRow> BuildPrintedGroups(IEnumerable<PsvDocumentLine> lines, bool includeClosedDetails)
{ {
return (lines ?? Enumerable.Empty<PsvDocumentLine>()) return (lines ?? Enumerable.Empty<PsvDocumentLine>())
.GroupBy(delegate(PsvDocumentLine line) .GroupBy(delegate(PsvDocumentLine line)
@@ -302,7 +464,10 @@ namespace XLAB
GroupCount = materializedLines.Count, GroupCount = materializedLines.Count,
PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }), PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }),
FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }), FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }),
Notes = includeClosedNotes ? BuildClosedNotesText(materializedLines) : string.Empty VerificationDocumentText = includeClosedDetails ? BuildVerificationDocumentText(materializedLines) : string.Empty,
StickerNumberText = includeClosedDetails ? BuildStickerNumberText(materializedLines) : string.Empty,
VerifierNameText = includeClosedDetails ? BuildVerifierNameText(materializedLines) : string.Empty,
Notes = BuildNotesText(materializedLines)
}; };
}) })
.ToList(); .ToList();
@@ -332,7 +497,7 @@ namespace XLAB
return string.Empty; return string.Empty;
} }
if (serialNumbers.Count > 3) if (serialNumbers.Count > 10)
{ {
return string.Format(CultureInfo.InvariantCulture, "{0} зав. номеров", serialNumbers.Count); return string.Format(CultureInfo.InvariantCulture, "{0} зав. номеров", serialNumbers.Count);
} }
@@ -371,6 +536,46 @@ namespace XLAB
return string.Join(". ", parts.ToArray()); return string.Join(". ", parts.ToArray());
} }
private static string BuildNotesText(IReadOnlyList<PsvDocumentLine> lines)
{
return string.Join("; ", lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line == null ? null : line.Notes); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
private static string BuildVerificationDocumentText(IReadOnlyList<PsvDocumentLine> lines)
{
return string.Join("; ", lines
.Select(FormatVerificationDocument)
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
private static string BuildStickerNumberText(IReadOnlyList<PsvDocumentLine> lines)
{
return string.Join(", ", lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line == null ? null : line.StickerNumber); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
private static string BuildVerifierNameText(IReadOnlyList<PsvDocumentLine> lines)
{
return string.Join("; ", lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line == null ? null : line.VerifierName); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
private static string FormatVerificationDocument(PsvDocumentLine line) private static string FormatVerificationDocument(PsvDocumentLine line)
{ {
if (line == null) if (line == null)
@@ -445,6 +650,7 @@ namespace XLAB
var candidates = new[] var candidates = new[]
{ {
Path.Combine(baseDirectory, fileName), Path.Combine(baseDirectory, fileName),
Path.GetFullPath(Path.Combine(baseDirectory, "..", fileName)),
Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", fileName)), Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", fileName)),
Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", fileName)) Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", fileName))
}; };
@@ -551,6 +757,12 @@ namespace XLAB
public string RangeText { get; set; } public string RangeText { get; set; }
public string SerialNumberText { get; set; } public string SerialNumberText { get; set; }
public string StickerNumberText { get; set; }
public string VerificationDocumentText { get; set; }
public string VerifierNameText { get; set; }
} }
private sealed class PrintGroupKey : IEquatable<PrintGroupKey> private sealed class PrintGroupKey : IEquatable<PrintGroupKey>

View File

@@ -27,6 +27,11 @@ namespace XLAB
return new SqlConnection(connectionString.ConnectionString); return new SqlConnection(connectionString.ConnectionString);
} }
public static int GetCommandTimeoutSeconds()
{
return 60;
}
public static IReadOnlyList<DirectoryLookupItem> LoadLookupItems(string sql) public static IReadOnlyList<DirectoryLookupItem> LoadLookupItems(string sql)
{ {
var items = new List<DirectoryLookupItem>(); var items = new List<DirectoryLookupItem>();
@@ -35,7 +40,7 @@ namespace XLAB
using (var command = new SqlCommand(sql, connection)) using (var command = new SqlCommand(sql, connection))
{ {
connection.Open(); connection.Open();
command.CommandTimeout = 60; command.CommandTimeout = GetCommandTimeoutSeconds();
using (var reader = command.ExecuteReader()) using (var reader = command.ExecuteReader())
{ {
@@ -54,9 +59,14 @@ namespace XLAB
} }
public static List<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, string parentTableName, int id, IEnumerable<string> excludedChildTables = null) public static List<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, string parentTableName, int id, IEnumerable<string> excludedChildTables = null)
{
return LoadDeleteBlockersFromForeignKeys(connection, null, parentTableName, id, excludedChildTables);
}
public static List<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, SqlTransaction transaction, string parentTableName, int id, IEnumerable<string> excludedChildTables = null)
{ {
var excluded = new HashSet<string>(excludedChildTables ?? Enumerable.Empty<string>(), StringComparer.OrdinalIgnoreCase); var excluded = new HashSet<string>(excludedChildTables ?? Enumerable.Empty<string>(), StringComparer.OrdinalIgnoreCase);
var metadata = LoadForeignKeyMetadata(connection, parentTableName) var metadata = LoadForeignKeyMetadata(connection, transaction, parentTableName)
.Where(delegate(ForeignKeyMetadata item) { return !excluded.Contains(item.TableName); }) .Where(delegate(ForeignKeyMetadata item) { return !excluded.Contains(item.TableName); })
.GroupBy(delegate(ForeignKeyMetadata item) { return item.SchemaName + "." + item.TableName; }) .GroupBy(delegate(ForeignKeyMetadata item) { return item.SchemaName + "." + item.TableName; })
.Select(delegate(IGrouping<string, ForeignKeyMetadata> group) .Select(delegate(IGrouping<string, ForeignKeyMetadata> group)
@@ -86,7 +96,8 @@ namespace XLAB
using (var command = new SqlCommand(sql, connection)) using (var command = new SqlCommand(sql, connection))
{ {
command.CommandTimeout = 60; command.Transaction = transaction;
command.CommandTimeout = GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = id; command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
var rowCount = Convert.ToInt32(command.ExecuteScalar()); var rowCount = Convert.ToInt32(command.ExecuteScalar());
if (rowCount > 0) if (rowCount > 0)
@@ -183,7 +194,7 @@ namespace XLAB
command.Parameters.Add(name, SqlDbType.VarChar, -1).Value = (object)value ?? DBNull.Value; command.Parameters.Add(name, SqlDbType.VarChar, -1).Value = (object)value ?? DBNull.Value;
} }
private static IReadOnlyList<ForeignKeyMetadata> LoadForeignKeyMetadata(SqlConnection connection, string parentTableName) private static IReadOnlyList<ForeignKeyMetadata> LoadForeignKeyMetadata(SqlConnection connection, SqlTransaction transaction, string parentTableName)
{ {
const string sql = @" const string sql = @"
SELECT SELECT
@@ -200,7 +211,8 @@ ORDER BY TableName, ColumnName;";
using (var command = new SqlCommand(sql, connection)) using (var command = new SqlCommand(sql, connection))
{ {
command.CommandTimeout = 60; command.Transaction = transaction;
command.CommandTimeout = GetCommandTimeoutSeconds();
command.Parameters.Add("@ParentTableName", SqlDbType.VarChar, 128).Value = parentTableName; command.Parameters.Add("@ParentTableName", SqlDbType.VarChar, 128).Value = parentTableName;
using (var reader = command.ExecuteReader()) using (var reader = command.ExecuteReader())

View File

@@ -61,14 +61,17 @@
</DataGrid> </DataGrid>
<StackPanel Grid.Row="3" <StackPanel Grid.Row="3"
Margin="0,10,0,0" Margin="0,10,0,0">
Orientation="Horizontal"> <TextBlock Text="Заводские номера" />
<TextBlock Width="150" <TextBlock Margin="0,4,0,0"
Margin="0,0,8,0" Foreground="DimGray"
VerticalAlignment="Center" Text="Перечислите номера через запятую, точку с запятой или с новой строки." />
Text="Заводской номер" /> <TextBox Margin="0,6,0,0"
<TextBox Width="320" Height="96"
Text="{Binding SerialNumber, UpdateSourceTrigger=PropertyChanged}" /> AcceptsReturn="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
Text="{Binding SerialNumbersText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel> </StackPanel>
<TextBlock Grid.Row="4" <TextBlock Grid.Row="4"

View File

@@ -12,7 +12,7 @@ namespace XLAB
{ {
private string _searchText; private string _searchText;
private AvailableInstrumentItem _selectedType; private AvailableInstrumentItem _selectedType;
private string _serialNumber; private string _serialNumbersText;
private string _statusText; private string _statusText;
public SelectInstrumentTypeWindowViewModel(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes) public SelectInstrumentTypeWindowViewModel(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes)
@@ -65,12 +65,12 @@ namespace XLAB
} }
} }
public string SerialNumber public string SerialNumbersText
{ {
get { return _serialNumber; } get { return _serialNumbersText; }
set set
{ {
if (SetProperty(ref _serialNumber, value)) if (SetProperty(ref _serialNumbersText, value))
{ {
((RelayCommand)ConfirmCommand).RaiseCanExecuteChanged(); ((RelayCommand)ConfirmCommand).RaiseCanExecuteChanged();
UpdateStatus(); UpdateStatus();
@@ -86,12 +86,13 @@ namespace XLAB
public InstrumentTypeSelectionResult GetResult() public InstrumentTypeSelectionResult GetResult()
{ {
var serialNumbers = ParseSerialNumbers(SerialNumbersText);
return SelectedType == null return SelectedType == null
? null ? null
: new InstrumentTypeSelectionResult : new InstrumentTypeSelectionResult
{ {
TypeItem = SelectedType, TypeItem = SelectedType,
SerialNumber = string.IsNullOrWhiteSpace(SerialNumber) ? string.Empty : SerialNumber.Trim() SerialNumbers = serialNumbers
}; };
} }
@@ -103,7 +104,7 @@ namespace XLAB
private bool CanConfirm(object parameter) private bool CanConfirm(object parameter)
{ {
return SelectedType != null return SelectedType != null
&& !string.IsNullOrWhiteSpace(SerialNumber); && ParseSerialNumbers(SerialNumbersText).Count > 0;
} }
private void Confirm(object parameter) private void Confirm(object parameter)
@@ -138,6 +139,30 @@ namespace XLAB
&& source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0; && source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
} }
private static List<string> ParseSerialNumbers(string value)
{
var serialNumbers = new List<string>();
var unique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(value))
{
return serialNumbers;
}
var separators = new[] { '\r', '\n', '\t', ',', ';' };
foreach (var token in value.Split(separators, StringSplitOptions.RemoveEmptyEntries))
{
var serialNumber = token.Trim();
if (serialNumber.Length == 0 || !unique.Add(serialNumber))
{
continue;
}
serialNumbers.Add(serialNumber);
}
return serialNumbers;
}
private void RaiseCloseRequested(bool? dialogResult) private void RaiseCloseRequested(bool? dialogResult)
{ {
var handler = CloseRequested; var handler = CloseRequested;
@@ -150,12 +175,13 @@ namespace XLAB
private void UpdateStatus() private void UpdateStatus()
{ {
var visibleCount = InstrumentTypesView.Cast<object>().Count(); var visibleCount = InstrumentTypesView.Cast<object>().Count();
var serialCount = ParseSerialNumbers(SerialNumbersText).Count;
StatusText = string.Format( StatusText = string.Format(
"Всего типов: {0}. По фильтру: {1}. Выбран тип: {2}. Заводской номер: {3}.", "Всего типов: {0}. По фильтру: {1}. Выбран тип: {2}. Уникальных зав. №: {3}.",
InstrumentTypes.Count, InstrumentTypes.Count,
visibleCount, visibleCount,
SelectedType == null ? "нет" : "да", SelectedType == null ? "нет" : "да",
string.IsNullOrWhiteSpace(SerialNumber) ? "не указан" : "указан"); serialCount);
} }
} }
} }

View File

@@ -30,7 +30,8 @@
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" /> Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel> </StackPanel>
<DataGrid Grid.Row="2" <DataGrid x:Name="InstrumentsGrid"
Grid.Row="2"
ItemsSource="{Binding InstrumentsView}" ItemsSource="{Binding InstrumentsView}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"

View File

@@ -1,4 +1,5 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
namespace XLAB namespace XLAB
{ {
@@ -13,6 +14,12 @@ namespace XLAB
private void ViewModelOnCloseRequested(object sender, bool? dialogResult) private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{ {
if (dialogResult.GetValueOrDefault())
{
InstrumentsGrid.CommitEdit(DataGridEditingUnit.Cell, true);
InstrumentsGrid.CommitEdit(DataGridEditingUnit.Row, true);
}
DialogResult = dialogResult; DialogResult = dialogResult;
Close(); Close();
} }

View File

@@ -44,6 +44,22 @@
CanUserAddRows="False" CanUserAddRows="False"
IsReadOnly="True" IsReadOnly="True"
HeadersVisibility="Column"> 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> <DataGrid.Columns>
<DataGridTextColumn Header="ID" <DataGridTextColumn Header="ID"
Width="90" Width="90"
@@ -66,18 +82,6 @@
Margin="0,12,0,0" Margin="0,12,0,0"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Right"> 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" <Button Width="90"
IsCancel="True" IsCancel="True"
Content="Закрыть" /> Content="Закрыть" />

View File

@@ -1,4 +1,6 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB namespace XLAB
{ {
@@ -13,6 +15,16 @@ namespace XLAB
DataContext = _viewModel; 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) private async void Window_Loaded(object sender, RoutedEventArgs e)
{ {
await _viewModel.InitializeAsync(); await _viewModel.InitializeAsync();

View File

@@ -44,6 +44,22 @@
CanUserAddRows="False" CanUserAddRows="False"
IsReadOnly="True" IsReadOnly="True"
HeadersVisibility="Column"> 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> <DataGrid.Columns>
<DataGridTextColumn Header="ID" <DataGridTextColumn Header="ID"
Width="90" Width="90"
@@ -66,18 +82,6 @@
Margin="0,12,0,0" Margin="0,12,0,0"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Right"> 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" <Button Width="90"
IsCancel="True" IsCancel="True"
Content="Закрыть" /> Content="Закрыть" />

View File

@@ -1,4 +1,6 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB namespace XLAB
{ {
@@ -13,6 +15,16 @@ namespace XLAB
DataContext = _viewModel; 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) private async void Window_Loaded(object sender, RoutedEventArgs e)
{ {
await _viewModel.InitializeAsync(); await _viewModel.InitializeAsync();

View File

@@ -2,9 +2,9 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}" Title="{Binding Title}"
Height="620" Height="360"
Width="760" Width="760"
MinHeight="580" MinHeight="360"
MinWidth="700" MinWidth="700"
WindowStartupLocation="CenterOwner"> WindowStartupLocation="CenterOwner">
<Grid Margin="16"> <Grid Margin="16">
@@ -41,18 +41,6 @@
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Тип СИ" /> <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}" /> <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="№ по Госреестру" /> <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}" /> <TextBox Grid.Row="7" Grid.Column="1" Margin="0,0,0,8" Text="{Binding RegistryTypeNumber, UpdateSourceTrigger=PropertyChanged}" />
@@ -60,21 +48,12 @@
<TextBox Grid.Row="8" Grid.Column="1" Margin="0,0,0,8" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" TextWrapping="Wrap" Text="{Binding Notes, UpdateSourceTrigger=PropertyChanged}" /> <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 Grid.Row="9" Grid.ColumnSpan="2" Orientation="Horizontal">
<StackPanel Width="360" Orientation="Horizontal"> <StackPanel Width="360" Orientation="Horizontal"/>
<TextBlock Width="220" VerticalAlignment="Center" Text="Код ВНИИМС" /> <StackPanel Width="220" Margin="12,0,0,0" Orientation="Horizontal"/>
<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> </StackPanel>
</Grid> </Grid>
<StackPanel Grid.Row="1" Margin="0,12,0,0" Orientation="Horizontal"> <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"> <DockPanel Grid.Row="2" Margin="0,12,0,0">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Center" Foreground="Firebrick" Text="{Binding ValidationMessage}" /> <TextBlock DockPanel.Dock="Left" VerticalAlignment="Center" Foreground="Firebrick" Text="{Binding ValidationMessage}" />

View File

@@ -2,9 +2,9 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}" Title="{Binding Title}"
Height="320" Height="160"
Width="620" Width="620"
MinHeight="300" MinHeight="160"
MinWidth="580" MinWidth="580"
WindowStartupLocation="CenterOwner"> WindowStartupLocation="CenterOwner">
<Grid Margin="16"> <Grid Margin="16">
@@ -24,17 +24,8 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </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="Период МК, мес." /> <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}" /> <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> </Grid>
<DockPanel Grid.Row="1" Margin="0,12,0,0"> <DockPanel Grid.Row="1" Margin="0,12,0,0">

View File

@@ -2,9 +2,9 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}" Title="{Binding Title}"
Height="620" Height="240"
Width="760" Width="760"
MinHeight="580" MinHeight="240"
MinWidth="700" MinWidth="700"
WindowStartupLocation="CenterOwner"> WindowStartupLocation="CenterOwner">
<Grid Margin="16"> <Grid Margin="16">
@@ -35,36 +35,11 @@
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Организация / подразделение" /> <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" /> <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="Место МК" /> <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" /> <ComboBox Grid.Row="4" Grid.Column="1" Margin="0,0,0,8" ItemsSource="{Binding Places}" SelectedValue="{Binding PlaceId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<StackPanel Grid.Row="5" Grid.Column="1" Orientation="Horizontal"/>
<TextBlock Grid.Row="5" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Стоимость / доп. / срочность" /> <StackPanel Grid.Row="6" Grid.Column="1" Orientation="Horizontal"/>
<StackPanel Grid.Row="5" Grid.Column="1" Orientation="Horizontal"> <StackPanel Grid.Row="7" 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> </Grid>
<DockPanel Grid.Row="1" Margin="0,12,0,0"> <DockPanel Grid.Row="1" Margin="0,12,0,0">

View File

@@ -2,9 +2,9 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}" Title="{Binding Title}"
Height="340" Height="240"
Width="620" Width="620"
MinHeight="320" MinHeight="220"
MinWidth="580" MinWidth="580"
WindowStartupLocation="CenterOwner"> WindowStartupLocation="CenterOwner">
<Grid Margin="16"> <Grid Margin="16">
@@ -31,14 +31,8 @@
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Характеристика точности" /> <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}" /> <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="№ типоразмера по Госреестру" /> <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}" /> <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> </Grid>
<DockPanel Grid.Row="1" Margin="0,12,0,0"> <DockPanel Grid.Row="1" Margin="0,12,0,0">

View File

@@ -33,7 +33,7 @@
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Margin="0,0,8,0" <TextBlock Margin="0,0,8,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Поиск по TIPS и TPRZ:" /> Text="Поиск по типам и типоразмерам:" />
<TextBox Width="360" <TextBox Width="360"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" /> Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
@@ -47,7 +47,7 @@
</Grid> </Grid>
<GroupBox Grid.Row="1" <GroupBox Grid.Row="1"
Header="Типы СИ (TIPS)"> Header="Типы СИ">
<DataGrid ItemsSource="{Binding TipsItems}" <DataGrid ItemsSource="{Binding TipsItems}"
SelectedItem="{Binding SelectedTips, Mode=TwoWay}" SelectedItem="{Binding SelectedTips, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
@@ -74,19 +74,16 @@
</Style> </Style>
</DataGrid.RowStyle> </DataGrid.RowStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="70" Binding="{Binding Id}" />
<DataGridTextColumn Header="Область измерений" Width="260" Binding="{Binding MeasurementAreaName}" /> <DataGridTextColumn Header="Область измерений" Width="260" Binding="{Binding MeasurementAreaName}" />
<DataGridTextColumn Header="Наименование типа СИ" Width="260" Binding="{Binding InstrumentName}" /> <DataGridTextColumn Header="Наименование типа СИ" Width="260" Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип СИ" Width="*" Binding="{Binding TypeName}" /> <DataGridTextColumn Header="Тип СИ" Width="*" Binding="{Binding TypeName}" />
<DataGridTextColumn Header="Категория" Width="180" Binding="{Binding CategoryName}" />
<DataGridTextColumn Header="Исполнение" Width="160" Binding="{Binding DesignName}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</GroupBox> </GroupBox>
<GroupBox Grid.Row="2" <GroupBox Grid.Row="2"
Margin="0,12,0,0" Margin="0,12,0,0"
Header="Типоразмеры СИ (TPRZ)"> Header="Типоразмеры СИ">
<DataGrid ItemsSource="{Binding TprzItems}" <DataGrid ItemsSource="{Binding TprzItems}"
SelectedItem="{Binding SelectedTprz, Mode=TwoWay}" SelectedItem="{Binding SelectedTprz, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
@@ -107,10 +104,8 @@
</Style> </Style>
</DataGrid.RowStyle> </DataGrid.RowStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="70" Binding="{Binding Id}" />
<DataGridTextColumn Header="Диапазон" Width="*" Binding="{Binding RangeText}" /> <DataGridTextColumn Header="Диапазон" Width="*" Binding="{Binding RangeText}" />
<DataGridTextColumn Header="Х-ка точности" Width="260" Binding="{Binding AccuracyText}" /> <DataGridTextColumn Header="Х-ка точности" Width="260" Binding="{Binding AccuracyText}" />
<DataGridTextColumn Header="Комплектность МК" Width="220" Binding="{Binding CompletenessName}" />
<DataGridTextColumn Header="№ Госреестра" Width="130" Binding="{Binding RegistryTypeSizeNumber}" /> <DataGridTextColumn Header="№ Госреестра" Width="130" Binding="{Binding RegistryTypeSizeNumber}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
@@ -125,7 +120,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<GroupBox Grid.Column="0" <GroupBox Grid.Column="0"
Header="Регламент МК для типоразмера СИ (TPRMK)"> Header="Регламент МК для типоразмера СИ">
<DataGrid ItemsSource="{Binding TprmkItems}" <DataGrid ItemsSource="{Binding TprmkItems}"
SelectedItem="{Binding SelectedTprmk, Mode=TwoWay}" SelectedItem="{Binding SelectedTprmk, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
@@ -146,18 +141,15 @@
</Style> </Style>
</DataGrid.RowStyle> </DataGrid.RowStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="70" Binding="{Binding Id}" />
<DataGridTextColumn Header="Вид МК" Width="130" Binding="{Binding VerificationTypeName}" /> <DataGridTextColumn Header="Вид МК" Width="130" Binding="{Binding VerificationTypeName}" />
<DataGridTextColumn Header="Организация" Width="*" Binding="{Binding OrganizationName}" /> <DataGridTextColumn Header="Организация" Width="*" Binding="{Binding OrganizationName}" />
<DataGridTextColumn Header="Место МК" Width="170" Binding="{Binding PlaceName}" /> <DataGridTextColumn Header="Место МК" Width="170" Binding="{Binding PlaceName}" />
<DataGridTextColumn Header="Группа СИ" Width="220" Binding="{Binding GroupName}" />
<DataGridTextColumn Header="Кол-во повер." Width="110" Binding="{Binding VerifierCount}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</GroupBox> </GroupBox>
<GroupBox Grid.Column="2" <GroupBox Grid.Column="2"
Header="Циклы и периоды МК (TPRMCP)"> Header="Циклы и периоды МК">
<DataGrid ItemsSource="{Binding TprmcpItems}" <DataGrid ItemsSource="{Binding TprmcpItems}"
SelectedItem="{Binding SelectedTprmcp, Mode=TwoWay}" SelectedItem="{Binding SelectedTprmcp, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
@@ -178,10 +170,7 @@
</Style> </Style>
</DataGrid.RowStyle> </DataGrid.RowStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="70" Binding="{Binding Id}" /> <DataGridTextColumn Header="Период, мес." Width="*" Binding="{Binding PeriodMonths}" />
<DataGridTextColumn Header="Цикл МК" Width="180" Binding="{Binding CycleName}" />
<DataGridTextColumn Header="Группа СИ" Width="*" Binding="{Binding GroupName}" />
<DataGridTextColumn Header="Период, мес." Width="110" Binding="{Binding PeriodMonths}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</GroupBox> </GroupBox>

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
namespace XLAB
{
internal sealed class VerificationReportFilter
{
public int? CustomerId { get; set; }
public int? MeasurementAreaId { get; set; }
public DateTime? DateFrom { get; set; }
public DateTime? DateTo { get; set; }
}
internal sealed class VerificationReportSummary
{
public int TotalCount { get; set; }
public int GoodCount { get; set; }
public int RejectedCount { get; set; }
public int WithoutResultCount { get; set; }
public int IssuedCount { get; set; }
}
internal sealed class VerificationReportCustomerRow
{
public int? CustomerId { get; set; }
public string CustomerName { get; set; }
public int TotalCount { get; set; }
public int GoodCount { get; set; }
public int RejectedCount { get; set; }
public int WithoutResultCount { get; set; }
public int IssuedCount { get; set; }
}
internal sealed class VerificationReportMeasurementAreaRow
{
public int? MeasurementAreaId { get; set; }
public string MeasurementAreaName { get; set; }
public int TotalCount { get; set; }
public int GoodCount { get; set; }
public int RejectedCount { get; set; }
public int WithoutResultCount { get; set; }
public int IssuedCount { get; set; }
}
internal sealed class VerificationReportData
{
public VerificationReportData()
{
Summary = new VerificationReportSummary();
CustomerRows = Array.Empty<VerificationReportCustomerRow>();
MeasurementAreaRows = Array.Empty<VerificationReportMeasurementAreaRow>();
}
public VerificationReportSummary Summary { get; set; }
public IReadOnlyList<VerificationReportCustomerRow> CustomerRows { get; set; }
public IReadOnlyList<VerificationReportMeasurementAreaRow> MeasurementAreaRows { get; set; }
}
}

View File

@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
namespace XLAB
{
internal sealed class VerificationReportsService
{
private const string ReportSourceSql = @"
FROM dbo.EKZMK m
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
LEFT JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
LEFT JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
LEFT JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
LEFT JOIN dbo.FRPD customers ON customers.IDFRPD = z.IDFRPDV
WHERE m.DTMKFK IS NOT NULL
AND (@DateFrom IS NULL OR m.DTMKFK >= @DateFrom)
AND (@DateToExclusive IS NULL OR m.DTMKFK < @DateToExclusive)
AND (@CustomerId IS NULL OR z.IDFRPDV = @CustomerId)
AND (@MeasurementAreaId IS NULL OR tips.IDSPOI = @MeasurementAreaId)";
public VerificationReportData LoadReport(VerificationReportFilter filter)
{
var normalizedFilter = NormalizeFilter(filter);
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
return new VerificationReportData
{
Summary = LoadSummary(connection, normalizedFilter),
CustomerRows = LoadCustomerRows(connection, normalizedFilter),
MeasurementAreaRows = LoadMeasurementAreaRows(connection, normalizedFilter)
};
}
}
public IReadOnlyList<DirectoryLookupItem> LoadCustomerItems()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT DISTINCT
customers.IDFRPD AS Id,
customers.NMFRPD AS Name
FROM dbo.EKZMK m
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
JOIN dbo.FRPD customers ON customers.IDFRPD = z.IDFRPDV
WHERE m.DTMKFK IS NOT NULL
AND NULLIF(LTRIM(RTRIM(customers.NMFRPD)), N'') IS NOT NULL
ORDER BY customers.NMFRPD, customers.IDFRPD;");
}
public IReadOnlyList<DirectoryLookupItem> LoadMeasurementAreaItems()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT DISTINCT
areas.IDSPOI AS Id,
areas.NMOI AS Name
FROM dbo.EKZMK m
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
LEFT JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
LEFT JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
WHERE m.DTMKFK IS NOT NULL
AND NULLIF(LTRIM(RTRIM(areas.NMOI)), N'') IS NOT NULL
ORDER BY areas.NMOI, areas.IDSPOI;");
}
private static void AddFilterParameters(SqlCommand command, VerificationReportFilter filter)
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@CustomerId", filter.CustomerId);
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@MeasurementAreaId", filter.MeasurementAreaId);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@DateFrom", filter.DateFrom);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@DateToExclusive", GetDateToExclusive(filter.DateTo));
}
private static DateTime? GetDateToExclusive(DateTime? dateTo)
{
return dateTo.HasValue ? dateTo.Value.Date.AddDays(1) : (DateTime?)null;
}
private static VerificationReportSummary LoadSummary(SqlConnection connection, VerificationReportFilter filter)
{
var sql = @"
SELECT
COUNT(*) AS TotalCount,
COALESCE(SUM(CASE WHEN m.GDN = 1 THEN 1 ELSE 0 END), 0) AS GoodCount,
COALESCE(SUM(CASE WHEN m.GDN = 0 THEN 1 ELSE 0 END), 0) AS RejectedCount,
COALESCE(SUM(CASE WHEN m.GDN IS NULL THEN 1 ELSE 0 END), 0) AS WithoutResultCount,
COALESCE(SUM(CASE WHEN m.DTVDM IS NOT NULL THEN 1 ELSE 0 END), 0) AS IssuedCount
" + ReportSourceSql + ";";
using (var command = new SqlCommand(sql, connection))
{
AddFilterParameters(command, filter);
using (var reader = command.ExecuteReader())
{
if (!reader.Read())
{
return new VerificationReportSummary();
}
return new VerificationReportSummary
{
TotalCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "TotalCount"),
GoodCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "GoodCount"),
RejectedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "RejectedCount"),
WithoutResultCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "WithoutResultCount"),
IssuedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "IssuedCount")
};
}
}
}
private static IReadOnlyList<VerificationReportCustomerRow> LoadCustomerRows(SqlConnection connection, VerificationReportFilter filter)
{
var sql = @"
SELECT
z.IDFRPDV AS CustomerId,
COALESCE(NULLIF(LTRIM(RTRIM(customers.NMFRPD)), N''), N'Не указано') AS CustomerName,
COUNT(*) AS TotalCount,
COALESCE(SUM(CASE WHEN m.GDN = 1 THEN 1 ELSE 0 END), 0) AS GoodCount,
COALESCE(SUM(CASE WHEN m.GDN = 0 THEN 1 ELSE 0 END), 0) AS RejectedCount,
COALESCE(SUM(CASE WHEN m.GDN IS NULL THEN 1 ELSE 0 END), 0) AS WithoutResultCount,
COALESCE(SUM(CASE WHEN m.DTVDM IS NOT NULL THEN 1 ELSE 0 END), 0) AS IssuedCount
" + ReportSourceSql + @"
GROUP BY
z.IDFRPDV,
COALESCE(NULLIF(LTRIM(RTRIM(customers.NMFRPD)), N''), N'Не указано')
ORDER BY
CustomerName;";
var rows = new List<VerificationReportCustomerRow>();
using (var command = new SqlCommand(sql, connection))
{
AddFilterParameters(command, filter);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
rows.Add(new VerificationReportCustomerRow
{
CustomerId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "CustomerId"),
CustomerName = ReferenceDirectorySqlHelpers.GetString(reader, "CustomerName"),
TotalCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "TotalCount"),
GoodCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "GoodCount"),
RejectedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "RejectedCount"),
WithoutResultCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "WithoutResultCount"),
IssuedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "IssuedCount")
});
}
}
}
return rows;
}
private static IReadOnlyList<VerificationReportMeasurementAreaRow> LoadMeasurementAreaRows(SqlConnection connection, VerificationReportFilter filter)
{
var sql = @"
SELECT
tips.IDSPOI AS MeasurementAreaId,
COALESCE(NULLIF(LTRIM(RTRIM(areas.NMOI)), N''), N'Не указано') AS MeasurementAreaName,
COUNT(*) AS TotalCount,
COALESCE(SUM(CASE WHEN m.GDN = 1 THEN 1 ELSE 0 END), 0) AS GoodCount,
COALESCE(SUM(CASE WHEN m.GDN = 0 THEN 1 ELSE 0 END), 0) AS RejectedCount,
COALESCE(SUM(CASE WHEN m.GDN IS NULL THEN 1 ELSE 0 END), 0) AS WithoutResultCount,
COALESCE(SUM(CASE WHEN m.DTVDM IS NOT NULL THEN 1 ELSE 0 END), 0) AS IssuedCount
" + ReportSourceSql + @"
GROUP BY
tips.IDSPOI,
COALESCE(NULLIF(LTRIM(RTRIM(areas.NMOI)), N''), N'Не указано')
ORDER BY
MeasurementAreaName;";
var rows = new List<VerificationReportMeasurementAreaRow>();
using (var command = new SqlCommand(sql, connection))
{
AddFilterParameters(command, filter);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
rows.Add(new VerificationReportMeasurementAreaRow
{
MeasurementAreaId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "MeasurementAreaId"),
MeasurementAreaName = ReferenceDirectorySqlHelpers.GetString(reader, "MeasurementAreaName"),
TotalCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "TotalCount"),
GoodCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "GoodCount"),
RejectedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "RejectedCount"),
WithoutResultCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "WithoutResultCount"),
IssuedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "IssuedCount")
});
}
}
}
return rows;
}
private static VerificationReportFilter NormalizeFilter(VerificationReportFilter filter)
{
var normalizedFilter = filter ?? new VerificationReportFilter();
var dateFrom = normalizedFilter.DateFrom.HasValue ? normalizedFilter.DateFrom.Value.Date : (DateTime?)null;
var dateTo = normalizedFilter.DateTo.HasValue ? normalizedFilter.DateTo.Value.Date : (DateTime?)null;
if (dateFrom.HasValue && dateTo.HasValue && dateFrom.Value > dateTo.Value)
{
throw new InvalidOperationException("Дата \"с\" не может быть позже даты \"по\".");
}
return new VerificationReportFilter
{
CustomerId = normalizedFilter.CustomerId,
MeasurementAreaId = normalizedFilter.MeasurementAreaId,
DateFrom = dateFrom,
DateTo = dateTo
};
}
}
}

View File

@@ -0,0 +1,238 @@
<Window x:Class="XLAB.VerificationReportsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Отчеты"
Height="760"
Width="1280"
MinHeight="640"
MinWidth="1100"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox Grid.Row="0"
Header="Параметры отчета">
<Grid Margin="8">
<DockPanel LastChildFill="False">
<Button DockPanel.Dock="Right"
Width="130"
Margin="12,0,0,0"
Command="{Binding RefreshCommand}"
Content="Сформировать" />
<WrapPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Заказчик" />
<ComboBox Width="280"
ItemsSource="{Binding CustomerItems}"
SelectedValue="{Binding SelectedCustomerId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Область измерений" />
<ComboBox Width="280"
ItemsSource="{Binding MeasurementAreaItems}"
SelectedValue="{Binding SelectedMeasurementAreaId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Дата поверки с" />
<DatePicker Width="145"
SelectedDate="{Binding DateFrom, Mode=TwoWay}"
SelectedDateFormat="Short" />
</StackPanel>
<StackPanel Margin="0,0,0,8">
<TextBlock Margin="0,0,0,4"
Text="Дата поверки по" />
<DatePicker Width="145"
SelectedDate="{Binding DateTo, Mode=TwoWay}"
SelectedDateFormat="Short" />
</StackPanel>
</WrapPanel>
</DockPanel>
</Grid>
</GroupBox>
<GroupBox Grid.Row="1"
Margin="0,12,0,0"
Header="Итоги">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Margin="0,0,0,8"
Foreground="DimGray"
Text="{Binding PeriodText}" />
<UniformGrid Grid.Row="1"
Columns="5">
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Поверено" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.TotalCount}" />
</StackPanel>
</Border>
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Годен" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.GoodCount}" />
</StackPanel>
</Border>
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Забракован" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.RejectedCount}" />
</StackPanel>
</Border>
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Без результата" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.WithoutResultCount}" />
</StackPanel>
</Border>
<Border Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="С выдачей" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.IssuedCount}" />
</StackPanel>
</Border>
</UniformGrid>
</Grid>
</GroupBox>
<GroupBox Grid.Row="2"
Margin="0,12,0,0"
Header="Сводные таблицы">
<TabControl Margin="8">
<TabItem Header="По заказчикам">
<DataGrid ItemsSource="{Binding CustomerRows}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="Заказчик"
Width="360"
Binding="{Binding CustomerName}" />
<DataGridTextColumn Header="Поверено"
Width="110"
Binding="{Binding TotalCount}" />
<DataGridTextColumn Header="Годен"
Width="110"
Binding="{Binding GoodCount}" />
<DataGridTextColumn Header="Забракован"
Width="120"
Binding="{Binding RejectedCount}" />
<DataGridTextColumn Header="Без результата"
Width="125"
Binding="{Binding WithoutResultCount}" />
<DataGridTextColumn Header="С выдачей"
Width="110"
Binding="{Binding IssuedCount}" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="По видам измерений">
<DataGrid ItemsSource="{Binding MeasurementAreaRows}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="Область измерений"
Width="360"
Binding="{Binding MeasurementAreaName}" />
<DataGridTextColumn Header="Поверено"
Width="110"
Binding="{Binding TotalCount}" />
<DataGridTextColumn Header="Годен"
Width="110"
Binding="{Binding GoodCount}" />
<DataGridTextColumn Header="Забракован"
Width="120"
Binding="{Binding RejectedCount}" />
<DataGridTextColumn Header="Без результата"
Width="125"
Binding="{Binding WithoutResultCount}" />
<DataGridTextColumn Header="С выдачей"
Width="110"
Binding="{Binding IssuedCount}" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
</GroupBox>
<TextBlock Grid.Row="3"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<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,21 @@
using System.Windows;
namespace XLAB
{
public partial class VerificationReportsWindow : Window
{
private readonly VerificationReportsWindowViewModel _viewModel;
public VerificationReportsWindow()
{
InitializeComponent();
_viewModel = new VerificationReportsWindowViewModel(new VerificationReportsService(), new DialogService(this));
DataContext = _viewModel;
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await _viewModel.InitializeAsync();
}
}
}

View File

@@ -0,0 +1,249 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace XLAB
{
internal sealed class VerificationReportsWindowViewModel : ObservableObject
{
private readonly IDialogService _dialogService;
private readonly VerificationReportsService _service;
private DateTime? _dateFrom;
private DateTime? _dateTo;
private bool _isBusy;
private int _selectedCustomerId;
private int _selectedMeasurementAreaId;
private VerificationReportSummary _summary;
private string _statusText;
public VerificationReportsWindowViewModel(VerificationReportsService service, IDialogService dialogService)
{
_service = service;
_dialogService = dialogService;
CustomerItems = new ObservableCollection<DirectoryLookupItem>();
MeasurementAreaItems = new ObservableCollection<DirectoryLookupItem>();
CustomerRows = new ObservableCollection<VerificationReportCustomerRow>();
MeasurementAreaRows = new ObservableCollection<VerificationReportMeasurementAreaRow>();
Summary = new VerificationReportSummary();
CustomerItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все заказчики" });
MeasurementAreaItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все области измерений" });
RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });
UpdateStatus();
}
public ObservableCollection<DirectoryLookupItem> CustomerItems { get; private set; }
public ObservableCollection<VerificationReportCustomerRow> CustomerRows { get; private set; }
public DateTime? DateFrom
{
get { return _dateFrom; }
set
{
if (SetProperty(ref _dateFrom, value))
{
OnPropertyChanged("PeriodText");
}
}
}
public DateTime? DateTo
{
get { return _dateTo; }
set
{
if (SetProperty(ref _dateTo, value))
{
OnPropertyChanged("PeriodText");
}
}
}
public bool IsBusy
{
get { return _isBusy; }
private set
{
if (SetProperty(ref _isBusy, value))
{
((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
}
}
}
public ObservableCollection<DirectoryLookupItem> MeasurementAreaItems { get; private set; }
public ObservableCollection<VerificationReportMeasurementAreaRow> MeasurementAreaRows { get; private set; }
public string PeriodText
{
get
{
if (!DateFrom.HasValue && !DateTo.HasValue)
{
return "Период: все время.";
}
if (DateFrom.HasValue && DateTo.HasValue)
{
return string.Format("Период: с {0:d} по {1:d}.", DateFrom.Value, DateTo.Value);
}
if (DateFrom.HasValue)
{
return string.Format("Период: с {0:d}.", DateFrom.Value);
}
return string.Format("Период: по {0:d}.", DateTo.Value);
}
}
public ICommand RefreshCommand { get; private set; }
public int SelectedCustomerId
{
get { return _selectedCustomerId; }
set { SetProperty(ref _selectedCustomerId, value); }
}
public int SelectedMeasurementAreaId
{
get { return _selectedMeasurementAreaId; }
set { SetProperty(ref _selectedMeasurementAreaId, value); }
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public VerificationReportSummary Summary
{
get { return _summary; }
private set { SetProperty(ref _summary, value); }
}
public async Task InitializeAsync()
{
await ExecuteBusyOperationAsync(async delegate
{
await LoadFiltersAsync();
await RefreshCoreAsync();
});
}
private VerificationReportFilter BuildFilter()
{
if (DateFrom.HasValue && DateTo.HasValue && DateFrom.Value.Date > DateTo.Value.Date)
{
throw new InvalidOperationException("Дата \"с\" не может быть позже даты \"по\".");
}
return new VerificationReportFilter
{
CustomerId = SelectedCustomerId > 0 ? SelectedCustomerId : (int?)null,
MeasurementAreaId = SelectedMeasurementAreaId > 0 ? SelectedMeasurementAreaId : (int?)null,
DateFrom = DateFrom,
DateTo = DateTo
};
}
private async Task ExecuteBusyOperationAsync(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 LoadFiltersAsync()
{
var customersTask = Task.Run(delegate { return _service.LoadCustomerItems(); });
var measurementAreasTask = Task.Run(delegate { return _service.LoadMeasurementAreaItems(); });
await Task.WhenAll(customersTask, measurementAreasTask);
ApplyLookupItems(CustomerItems, customersTask.Result, "Все заказчики");
ApplyLookupItems(MeasurementAreaItems, measurementAreasTask.Result, "Все области измерений");
if (!CustomerItems.Any(delegate(DirectoryLookupItem item) { return item.Id == SelectedCustomerId; }))
{
SelectedCustomerId = 0;
}
if (!MeasurementAreaItems.Any(delegate(DirectoryLookupItem item) { return item.Id == SelectedMeasurementAreaId; }))
{
SelectedMeasurementAreaId = 0;
}
}
private static void ApplyLookupItems(ObservableCollection<DirectoryLookupItem> target, System.Collections.Generic.IReadOnlyList<DirectoryLookupItem> source, string allItemText)
{
target.Clear();
target.Add(new DirectoryLookupItem { Id = 0, Name = allItemText });
foreach (var item in source)
{
target.Add(item);
}
}
private async Task RefreshCoreAsync()
{
var filter = BuildFilter();
var report = await Task.Run(delegate { return _service.LoadReport(filter); });
Summary = report.Summary ?? new VerificationReportSummary();
CustomerRows.Clear();
foreach (var row in report.CustomerRows ?? Array.Empty<VerificationReportCustomerRow>())
{
CustomerRows.Add(row);
}
MeasurementAreaRows.Clear();
foreach (var row in report.MeasurementAreaRows ?? Array.Empty<VerificationReportMeasurementAreaRow>())
{
MeasurementAreaRows.Add(row);
}
UpdateStatus();
}
private async void RefreshAsync()
{
await ExecuteBusyOperationAsync(RefreshCoreAsync);
}
private void UpdateStatus()
{
StatusText = string.Format(
"Заказчиков в своде: {0}. Видов измерений в своде: {1}. Поверено: {2}. Годен: {3}. Забракован: {4}.",
CustomerRows.Count,
MeasurementAreaRows.Count,
Summary == null ? 0 : Summary.TotalCount,
Summary == null ? 0 : Summary.GoodCount,
Summary == null ? 0 : Summary.RejectedCount);
}
}
}

View File

@@ -78,9 +78,30 @@
</Compile> </Compile>
<Compile Include="CloneVerificationWindowViewModel.cs" /> <Compile Include="CloneVerificationWindowViewModel.cs" />
<Compile Include="DialogService.cs" /> <Compile Include="DialogService.cs" />
<Compile Include="EkzDirectoryDialogService.cs" />
<Compile Include="EkzDirectoryModels.cs" />
<Compile Include="EkzDirectoryService.cs" />
<Compile Include="FrpdDirectoryDialogService.cs" /> <Compile Include="FrpdDirectoryDialogService.cs" />
<Compile Include="FrpdDirectoryModels.cs" /> <Compile Include="FrpdDirectoryModels.cs" />
<Compile Include="FrpdDirectoryService.cs" /> <Compile Include="FrpdDirectoryService.cs" />
<Page Include="EkzDirectoryWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="EkzDirectoryWindow.xaml.cs">
<DependentUpon>EkzDirectoryWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="EkzDirectoryWindowViewModel.cs" />
<Page Include="EkzEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="EkzEditWindow.xaml.cs">
<DependentUpon>EkzEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="EkzEditWindowViewModel.cs" />
<Page Include="MainWindow.xaml"> <Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
@@ -91,6 +112,9 @@
</Compile> </Compile>
<Compile Include="MainWindowViewModel.cs" /> <Compile Include="MainWindowViewModel.cs" />
<Compile Include="MvvmInfrastructure.cs" /> <Compile Include="MvvmInfrastructure.cs" />
<Compile Include="PlanningDialogService.cs" />
<Compile Include="PlanningModels.cs" />
<Compile Include="PlanningService.cs" />
<Compile Include="PsvDataService.cs" /> <Compile Include="PsvDataService.cs" />
<Compile Include="PsvPrintService.cs" /> <Compile Include="PsvPrintService.cs" />
<Compile Include="PsvModels.cs" /> <Compile Include="PsvModels.cs" />
@@ -263,6 +287,24 @@
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="PrdspvEditWindowViewModel.cs" /> <Compile Include="PrdspvEditWindowViewModel.cs" />
<Page Include="PlanningEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="PlanningEditWindow.xaml.cs">
<DependentUpon>PlanningEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="PlanningEditWindowViewModel.cs" />
<Page Include="PlanningWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="PlanningWindow.xaml.cs">
<DependentUpon>PlanningWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="PlanningWindowViewModel.cs" />
<Page Include="TprmcpEditWindow.xaml"> <Page Include="TprmcpEditWindow.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
@@ -299,6 +341,17 @@
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="VerificationEditWindowViewModel.cs" /> <Compile Include="VerificationEditWindowViewModel.cs" />
<Compile Include="VerificationReportsModels.cs" />
<Compile Include="VerificationReportsService.cs" />
<Page Include="VerificationReportsWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="VerificationReportsWindow.xaml.cs">
<DependentUpon>VerificationReportsWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="VerificationReportsWindowViewModel.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs"> <Compile Include="Properties\AssemblyInfo.cs">

View File

@@ -0,0 +1,118 @@
<Window x:Class="XLAB2.AccountingBookDirectoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Книги учета"
Height="680"
Width="980"
MinHeight="520"
MinWidth="820"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0"
Margin="0,0,0,8">
<StackPanel DockPanel.Dock="Left"
Orientation="Horizontal">
<TextBlock Width="210"
VerticalAlignment="Center"
Text="Поиск по ключу или книге учета" />
<TextBox Width="320"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel DockPanel.Dock="Right"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="110"
Margin="8,0,0,0"
Command="{Binding RefreshCommand}"
Content="Обновить" />
</StackPanel>
</DockPanel>
<DataGrid Grid.Row="1"
ItemsSource="{Binding ItemsView}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить"
Command="{Binding EditCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить"
Command="{Binding DeleteCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Ключ"
Width="240"
Binding="{Binding Key}" />
<DataGridTextColumn Header="Номер книги учета"
Width="*"
Binding="{Binding Title}" />
</DataGrid.Columns>
</DataGrid>
<TextBlock Grid.Row="2"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<StackPanel Grid.Row="3"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
IsCancel="True"
Content="Закрыть" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,35 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB2
{
public partial class AccountingBookDirectoryWindow : Window
{
private readonly AccountingBookDirectoryWindowViewModel _viewModel;
internal AccountingBookDirectoryWindow(DocumentNumberDirectoryService documentNumberDirectoryService)
{
InitializeComponent();
_viewModel = new AccountingBookDirectoryWindowViewModel(
documentNumberDirectoryService,
new DialogService(this, documentNumberDirectoryService));
DataContext = _viewModel;
}
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row != null)
{
row.IsSelected = true;
row.Focus();
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_viewModel.Initialize();
}
}
}

View File

@@ -0,0 +1,266 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class AccountingBookDirectoryWindowViewModel : ObservableObject
{
private readonly IDialogService _dialogService;
private readonly DocumentNumberDirectoryService _service;
private string _searchText;
private GroupOption _selectedItem;
private string _statusText;
public AccountingBookDirectoryWindowViewModel(DocumentNumberDirectoryService service, IDialogService dialogService)
{
_service = service ?? throw new ArgumentNullException("service");
_dialogService = dialogService ?? throw new ArgumentNullException("dialogService");
Items = new ObservableCollection<GroupOption>();
ItemsView = CollectionViewSource.GetDefaultView(Items);
ItemsView.Filter = FilterItems;
AddCommand = new RelayCommand(delegate { AddItem(); });
DeleteCommand = new RelayCommand(delegate { DeleteSelectedItem(); }, delegate { return SelectedItem != null; });
EditCommand = new RelayCommand(delegate { EditSelectedItem(); }, delegate { return SelectedItem != null; });
RefreshCommand = new RelayCommand(delegate { RefreshItems(null); });
UpdateStatus();
}
public ICommand AddCommand { get; private set; }
public ICommand DeleteCommand { get; private set; }
public ICommand EditCommand { get; private set; }
public ObservableCollection<GroupOption> Items { get; private set; }
public ICollectionView ItemsView { get; private set; }
public ICommand RefreshCommand { get; private set; }
public string SearchText
{
get { return _searchText; }
set
{
if (SetProperty(ref _searchText, value))
{
ItemsView.Refresh();
UpdateStatus();
}
}
}
public GroupOption SelectedItem
{
get { return _selectedItem; }
set
{
if (SetProperty(ref _selectedItem, value))
{
RaiseCommandStates();
}
}
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public void Initialize()
{
RunOperation(delegate { RefreshItems(null); }, false);
}
private void AddItem()
{
var result = _dialogService.ShowAccountingBookEditDialog(new GroupOption(), true, Items.ToList());
if (result == null)
{
return;
}
RunOperation(delegate
{
var items = CloneItems(Items);
items.Add(CloneItem(result));
_service.SaveAccountingBooks(items);
RefreshItems(result.Key);
_dialogService.ShowInfo("Запись справочника добавлена.");
}, true);
}
private bool Contains(string source, string searchText)
{
return !string.IsNullOrWhiteSpace(source)
&& source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
}
private void DeleteSelectedItem()
{
if (SelectedItem == null)
{
return;
}
var selectedItem = SelectedItem;
if (!_dialogService.Confirm(string.Format("Удалить книгу учета \"{0}\"?", selectedItem.Title)))
{
return;
}
RunOperation(delegate
{
var items = CloneItems(Items);
items.RemoveAll(delegate(GroupOption item)
{
return string.Equals(item.Key, selectedItem.Key, StringComparison.OrdinalIgnoreCase);
});
_service.SaveAccountingBooks(items);
RefreshItems(null);
_dialogService.ShowInfo("Запись справочника удалена.");
}, true);
}
private void EditSelectedItem()
{
if (SelectedItem == null)
{
return;
}
var seed = CloneItem(SelectedItem);
var result = _dialogService.ShowAccountingBookEditDialog(seed, false, Items.ToList());
if (result == null)
{
return;
}
RunOperation(delegate
{
var items = CloneItems(Items);
var index = items.FindIndex(delegate(GroupOption item)
{
return string.Equals(item.Key, seed.Key, StringComparison.OrdinalIgnoreCase);
});
if (index < 0)
{
throw new InvalidOperationException("Не удалось найти книгу учета для обновления.");
}
items[index] = CloneItem(result);
_service.SaveAccountingBooks(items);
RefreshItems(result.Key);
_dialogService.ShowInfo("Запись справочника обновлена.");
}, true);
}
private bool FilterItems(object item)
{
var directoryItem = item as GroupOption;
if (directoryItem == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(SearchText))
{
return true;
}
return Contains(directoryItem.Key, SearchText)
|| Contains(directoryItem.Title, SearchText);
}
private void RaiseCommandStates()
{
((RelayCommand)AddCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteCommand).RaiseCanExecuteChanged();
((RelayCommand)EditCommand).RaiseCanExecuteChanged();
((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
}
private void RefreshItems(string keyToSelect)
{
var items = _service.LoadAccountingBooks();
var currentKey = string.IsNullOrWhiteSpace(keyToSelect)
? (SelectedItem == null ? null : SelectedItem.Key)
: keyToSelect;
Items.Clear();
foreach (var item in items)
{
Items.Add(CloneItem(item));
}
ItemsView.Refresh();
SelectedItem = string.IsNullOrWhiteSpace(currentKey)
? Items.FirstOrDefault()
: Items.FirstOrDefault(delegate(GroupOption item)
{
return string.Equals(item.Key, currentKey, StringComparison.OrdinalIgnoreCase);
}) ?? Items.FirstOrDefault();
UpdateStatus();
}
private void RunOperation(Action action, bool showWarningForInvalidOperation)
{
try
{
action();
}
catch (InvalidOperationException ex)
{
if (showWarningForInvalidOperation)
{
_dialogService.ShowWarning(ex.Message);
return;
}
_dialogService.ShowError(ex.Message);
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
}
}
private static GroupOption CloneItem(GroupOption item)
{
return new GroupOption
{
Key = item == null ? string.Empty : item.Key,
Title = item == null ? string.Empty : item.Title
};
}
private static List<GroupOption> CloneItems(IEnumerable<GroupOption> items)
{
var result = new List<GroupOption>();
foreach (var item in items ?? Array.Empty<GroupOption>())
{
result.Add(CloneItem(item));
}
return result;
}
private void UpdateStatus()
{
var visibleCount = ItemsView.Cast<object>().Count();
StatusText = string.Format("Всего записей: {0}. По фильтру: {1}.", Items.Count, visibleCount);
}
}
}

View File

@@ -0,0 +1,64 @@
<Window x:Class="XLAB2.AccountingBookEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="250"
Width="560"
MinHeight="240"
MinWidth="520"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Номер книги учета" />
<TextBox Grid.Row="0"
Grid.Column="1"
Margin="0,0,0,8"
Text="{Binding BookNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Ключ" />
<TextBox Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,8"
Text="{Binding Key, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="2"
Grid.ColumnSpan="2"
Margin="0,8,0,0"
Foreground="Firebrick"
Text="{Binding ValidationMessage}" />
<StackPanel Grid.Row="3"
Grid.ColumnSpan="2"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="100"
Margin="0,0,8,0"
IsDefault="True"
Command="{Binding ConfirmCommand}"
Content="Сохранить" />
<Button Width="90"
Command="{Binding CancelCommand}"
Content="Отмена" />
</StackPanel>
</Grid>
</Window>

View File

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

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class AccountingBookEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<GroupOption> _existingItems;
private readonly string _originalKey;
private string _bookNumber;
private string _key;
private string _validationMessage;
public AccountingBookEditWindowViewModel(GroupOption seed, bool isNew, IReadOnlyList<GroupOption> existingItems)
{
var source = seed ?? new GroupOption();
_existingItems = existingItems ?? Array.Empty<GroupOption>();
_originalKey = Normalize(source.Key);
IsNew = isNew;
BookNumber = source.Title ?? string.Empty;
Key = source.Key ?? string.Empty;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public string BookNumber
{
get { return _bookNumber; }
set { SetProperty(ref _bookNumber, value); }
}
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public bool IsNew { get; private set; }
public string Key
{
get { return _key; }
set { SetProperty(ref _key, value); }
}
public string Title
{
get { return IsNew ? "Новая книга учета" : "Редактирование книги учета"; }
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public GroupOption ToResult()
{
return new GroupOption
{
Key = Normalize(Key) ?? string.Empty,
Title = Normalize(BookNumber) ?? string.Empty
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
var normalizedKey = Normalize(Key);
if (normalizedKey == null)
{
ValidationMessage = "Укажите ключ книги учета.";
return;
}
var normalizedBookNumber = Normalize(BookNumber);
if (normalizedBookNumber == null)
{
ValidationMessage = "Укажите номер книги учета.";
return;
}
var duplicateKey = _existingItems.FirstOrDefault(delegate(GroupOption item)
{
return item != null
&& !IsCurrentItem(item)
&& string.Equals(Normalize(item.Key), normalizedKey, StringComparison.OrdinalIgnoreCase);
});
if (duplicateKey != null)
{
ValidationMessage = string.Format("Ключ книги учета \"{0}\" уже существует.", normalizedKey);
return;
}
var duplicateBookNumber = _existingItems.FirstOrDefault(delegate(GroupOption item)
{
return item != null
&& !IsCurrentItem(item)
&& string.Equals(Normalize(item.Title), normalizedBookNumber, StringComparison.OrdinalIgnoreCase);
});
if (duplicateBookNumber != null)
{
ValidationMessage = string.Format("Книга учета \"{0}\" уже существует.", normalizedBookNumber);
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private bool IsCurrentItem(GroupOption item)
{
return !IsNew
&& string.Equals(Normalize(item == null ? null : item.Key), _originalKey, StringComparison.OrdinalIgnoreCase);
}
private static string Normalize(string value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
}
}

406
XLAB2/App.xaml Normal file
View File

@@ -0,0 +1,406 @@
<Application x:Class="XLAB2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShutdownMode="OnMainWindowClose">
<Application.Resources>
<LinearGradientBrush x:Key="AppWindowBackgroundBrush" StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#FFDCE5EE" Offset="0" />
<GradientStop Color="#FFC9D6E3" Offset="1" />
</LinearGradientBrush>
<SolidColorBrush x:Key="AppPanelBrush" Color="#FFF1F6FB" />
<SolidColorBrush x:Key="AppSurfaceBrush" Color="#FFFCFEFF" />
<SolidColorBrush x:Key="AppAccentBrush" Color="#FF5C7FA8" />
<SolidColorBrush x:Key="AppAccentSoftBrush" Color="#FFD6E3F0" />
<SolidColorBrush x:Key="AppBorderBrush" Color="#FFAABBCD" />
<SolidColorBrush x:Key="AppTextBrush" Color="#FF263645" />
<SolidColorBrush x:Key="AppMutedTextBrush" Color="#FF6B7B88" />
<SolidColorBrush x:Key="AppButtonBrush" Color="#FFF7FAFD" />
<SolidColorBrush x:Key="AppButtonHoverBrush" Color="#FFE7F0F9" />
<SolidColorBrush x:Key="AppPrimaryButtonBrush" Color="#FF5B8DB8" />
<SolidColorBrush x:Key="AppPrimaryButtonHoverBrush" Color="#FF6A9BC4" />
<SolidColorBrush x:Key="AppPrimaryButtonPressedBrush" Color="#FF4C799E" />
<SolidColorBrush x:Key="AppPrimaryButtonDisabledBrush" Color="#FF9EB3C4" />
<SolidColorBrush x:Key="AppPrimaryButtonBorderBrush" Color="#FF426E91" />
<SolidColorBrush x:Key="AppMenuIconAccentBrush" Color="#FF4F7FA7" />
<SolidColorBrush x:Key="AppMenuIconSuccessBrush" Color="#FF47A772" />
<SolidColorBrush x:Key="AppMenuIconDangerBrush" Color="#FFC76868" />
<SolidColorBrush x:Key="AppMenuIconWarningBrush" Color="#FFCC9B52" />
<SolidColorBrush x:Key="AppSelectionBrush" Color="#FFD8E6F4" />
<SolidColorBrush x:Key="AppSelectionTextBrush" Color="#FF17324A" />
<SolidColorBrush x:Key="OpenDocumentTenDaysBrush" Color="#FFF2F8EA" />
<SolidColorBrush x:Key="OpenDocumentTwentyDaysBrush" Color="#FFFCF4E3" />
<SolidColorBrush x:Key="OpenDocumentOverdueBrush" Color="#FFFBE7E7" />
<SolidColorBrush x:Key="OpenDocumentTenDaysIndicatorBrush" Color="#FFB7D79F" />
<SolidColorBrush x:Key="OpenDocumentTwentyDaysIndicatorBrush" Color="#FFF0C87A" />
<SolidColorBrush x:Key="OpenDocumentOverdueIndicatorBrush" Color="#FFE2A0A0" />
<Style TargetType="{x:Type Window}">
<Setter Property="Background" Value="{StaticResource AppWindowBackgroundBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
<Setter Property="Icon" Value="pack://application:,,,/Assets/XlabApp.ico" />
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
</Style>
<Style TargetType="{x:Type GroupBox}">
<Setter Property="Background" Value="{StaticResource AppPanelBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppAccentBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupBox}">
<Grid SnapsToDevicePixels="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="1"
Margin="0,2,0,0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
CornerRadius="3">
<ContentPresenter ContentSource="Content" />
</Border>
<Border Grid.Row="0"
Margin="12,0,12,0"
Padding="8,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="{StaticResource AppWindowBackgroundBrush}">
<ContentPresenter ContentSource="Header"
RecognizesAccessKey="True" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{StaticResource AppButtonBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
<Setter Property="Padding" Value="10,4" />
</Style>
<Style x:Key="PrimaryActionButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{StaticResource AppPrimaryButtonBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppPrimaryButtonBorderBrush}" />
<Setter Property="Foreground" Value="#FFFAFCFE" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Padding" Value="12,5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="RootBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
CornerRadius="4"
SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="RootBorder" Property="Background" Value="{StaticResource AppPrimaryButtonHoverBrush}" />
<Setter TargetName="RootBorder" Property="BorderBrush" Value="#FF4F7FA7" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="RootBorder" Property="Background" Value="{StaticResource AppPrimaryButtonPressedBrush}" />
<Setter TargetName="RootBorder" Property="BorderBrush" Value="#FF3E6687" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="RootBorder" Property="Background" Value="{StaticResource AppPrimaryButtonDisabledBrush}" />
<Setter TargetName="RootBorder" Property="BorderBrush" Value="#FF8CA0B1" />
<Setter Property="Foreground" Value="#FFF5F8FB" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{StaticResource AppPrimaryButtonDisabledBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppPrimaryButtonDisabledBrush}" />
<Setter Property="Foreground" Value="#FFF4F7FA" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
<Setter Property="Padding" Value="6,3" />
</Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
</Style>
<Style TargetType="{x:Type DatePicker}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
</Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
</Style>
<Style TargetType="{x:Type RadioButton}">
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
</Style>
<Style TargetType="{x:Type Menu}">
<Setter Property="Background" Value="{StaticResource AppPanelBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
</Style>
<Style TargetType="{x:Type ContextMenu}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
</Style>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="Header" Value="Добавить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Изменить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Удалить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Распечатать">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="1.5" Width="8" Height="4" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="5" Width="12" Height="5" RadiusX="1.5" RadiusY="1.5" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="4" Canvas.Top="8.5" Width="8" Height="5" Fill="#FFF9FCFE" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Добавить по заводским номерам">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="2" Canvas.Top="3" Width="8" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="7" Width="8" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="11" Width="6" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Ellipse Canvas.Left="10.5" Canvas.Top="6" Width="4" Height="4" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="12" Canvas.Top="4.5" Width="1" Height="7" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="9.5" Canvas.Top="7" Width="6" Height="1" Fill="{StaticResource AppMenuIconSuccessBrush}" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Добавить по типу">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="2" Canvas.Top="3" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="9" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="8" Canvas.Top="6" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="12" Canvas.Top="1.5" Width="1.5" Height="5" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="10.25" Canvas.Top="3.25" Width="5" Height="1.5" Fill="{StaticResource AppMenuIconSuccessBrush}" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Клонировать поверку в выбранные строки">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="3" Canvas.Top="4" Width="7" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="2" Width="7" Height="8" RadiusX="1" RadiusY="1" Fill="#FFEAF3FB" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Распечатать документ о поверке">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="1.5" Width="8" Height="4" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="5" Width="12" Height="5" RadiusX="1.5" RadiusY="1.5" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="4" Canvas.Top="8.5" Width="8" Height="5" Fill="#FFF9FCFE" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
<Ellipse Canvas.Left="10.5" Canvas.Top="9.5" Width="4" Height="4" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Path Fill="White" Data="M12.1,10.4 L12.9,11.2 L14.2,9.6 L14.8,10.1 L12.9,12.4 L11.5,11 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Годен">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Path Fill="White" Data="M5.1,8.2 L7.2,10.3 L11.6,5.6 L12.8,6.6 L7.3,12.3 L3.9,8.9 Z" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Забракован">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Path Stroke="White" StrokeThickness="1.8" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Data="M5.1,5.1 L10.9,10.9 M10.9,5.1 L5.1,10.9" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Отменить проверку">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconWarningBrush}" Data="M7.8,2.2 C10.8,2.2 13.2,4.6 13.2,7.6 C13.2,10.6 10.8,13 7.8,13 C5.5,13 3.6,11.6 2.8,9.5 L4.5,9.5 C5.2,10.8 6.4,11.5 7.8,11.5 C10,11.5 11.7,9.8 11.7,7.6 C11.7,5.4 10,3.7 7.8,3.7 C6.5,3.7 5.3,4.3 4.6,5.4 L6.7,5.4 L3.8,8.2 L1.1,5.4 L3.1,5.4 C4,3.4 5.8,2.2 7.8,2.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Виды клейм...">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Ellipse Canvas.Left="2.5" Canvas.Top="2.5" Width="11" Height="11" Fill="{StaticResource AppMenuIconWarningBrush}" />
<Path Fill="White" Data="M8,4 L8.9,6.2 L11.3,6.3 L9.4,7.8 L10.1,10.1 L8,8.8 L5.9,10.1 L6.6,7.8 L4.7,6.3 L7.1,6.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
<Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="True" />
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" />
</Style>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Padding" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource AppSelectionBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppSelectionTextBrush}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="HorizontalGridLinesBrush" Value="#FFD9E3ED" />
<Setter Property="VerticalGridLinesBrush" Value="#FFD9E3ED" />
<Setter Property="RowBackground" Value="#FFFEFFFF" />
<Setter Property="AlternatingRowBackground" Value="#FFF6FAFD" />
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
<Setter Property="EnableRowVirtualization" Value="True" />
<Setter Property="EnableColumnVirtualization" Value="True" />
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
<Setter Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="True" />
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" />
<Setter Property="ColumnHeaderStyle">
<Setter.Value>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="#FFE4EDF6" />
<Setter Property="Foreground" Value="{StaticResource AppAccentBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="FontWeight" Value="SemiBold" />
</Style>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource AppSelectionBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppSelectionTextBrush}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderBrush" Value="#FFDCE5EE" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource AppSelectionBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppSelectionTextBrush}" />
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
</Application>

156
XLAB2/App.xaml.cs Normal file
View File

@@ -0,0 +1,156 @@
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using XLAB2.Infrastructure;
namespace XLAB2
{
public partial class App : Application
{
private IHost _host;
public App()
{
ApplyRussianCulture();
RegisterGlobalExceptionHandlers();
}
protected override async void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
try
{
_host = AppHost.Create();
await _host.StartAsync().ConfigureAwait(true);
MainWindow = _host.Services.GetRequiredService<MainWindow>();
MainWindow.Show();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "XLAB2", MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown(-1);
}
}
protected override async void OnExit(ExitEventArgs e)
{
try
{
if (_host != null)
{
try
{
await _host.StopAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(true);
}
finally
{
_host.Dispose();
}
}
}
catch (Exception ex)
{
ShowUnhandledException(ex, true);
}
finally
{
UnregisterGlobalExceptionHandlers();
base.OnExit(e);
}
}
private static void ApplyRussianCulture()
{
var culture = new CultureInfo("ru-RU");
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(culture.IetfLanguageTag)));
}
private void RegisterGlobalExceptionHandlers()
{
DispatcherUnhandledException += OnDispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += OnCurrentDomainUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
}
private void UnregisterGlobalExceptionHandlers()
{
DispatcherUnhandledException -= OnDispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException -= OnCurrentDomainUnhandledException;
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
}
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
ShowUnhandledException(e.Exception, false);
e.Handled = true;
}
private void OnCurrentDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = e.ExceptionObject as Exception;
if (exception != null)
{
ShowUnhandledException(exception, e.IsTerminating);
return;
}
MessageBox.Show(
e.ExceptionObject == null ? "Произошла необработанная ошибка." : e.ExceptionObject.ToString(),
e.IsTerminating ? "XLAB2 - критическая ошибка" : "XLAB2",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
ShowUnhandledException(e.Exception, false);
e.SetObserved();
}
private static void ShowUnhandledException(Exception exception, bool isCritical)
{
var actualException = UnwrapException(exception);
var message = string.IsNullOrWhiteSpace(actualException.Message)
? actualException.ToString()
: actualException.Message;
MessageBox.Show(
message,
isCritical ? "XLAB2 - критическая ошибка" : "XLAB2",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
private static Exception UnwrapException(Exception exception)
{
if (exception is AggregateException aggregateException)
{
var flattened = aggregateException.Flatten();
if (flattened.InnerExceptions.Count == 1)
{
return UnwrapException(flattened.InnerExceptions[0]);
}
return flattened;
}
return exception;
}
}
}

39
XLAB2/AppHost.cs Normal file
View File

@@ -0,0 +1,39 @@
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using XLAB2.Infrastructure;
namespace XLAB2
{
internal static class AppHost
{
public static IHost Create()
{
return Host.CreateDefaultBuilder()
.UseContentRoot(AppContext.BaseDirectory)
.ConfigureAppConfiguration((context, config) =>
{
config.Sources.Clear();
config.SetBasePath(AppContext.BaseDirectory);
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
config.AddEnvironmentVariables();
})
.ConfigureServices((_, services) =>
{
services.AddSingleton<IDatabaseConnectionFactory>(_ => SqlServerConnectionFactory.Current);
services.AddSingleton(new DocumentNumberDirectoryService(System.IO.Path.Combine(AppContext.BaseDirectory, "Assets", "document-number-directory.json")));
services.AddTransient<PsvDataService>();
services.AddTransient<MainWindow>(provider => new MainWindow(
provider.GetRequiredService<PsvDataService>(),
provider.GetRequiredService<DocumentNumberDirectoryService>()));
})
.UseDefaultServiceProvider((_, options) =>
{
options.ValidateOnBuild = true;
options.ValidateScopes = true;
})
.Build();
}
}
}

10
XLAB2/AssemblyInfo.cs Normal file
View File

@@ -0,0 +1,10 @@
using System.Windows;
[assembly:ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

BIN
XLAB2/Assets/XlabApp.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

View File

@@ -0,0 +1,18 @@
{
"documentTypes": [
{
"key": "PSV",
"title": "ПСВ"
},
{
"key": "ACT_REFERENCE",
"title": "Акт-справка"
}
],
"accountingBooks": [
{
"key": "219/С5/15",
"title": "219/С5/15"
}
]
}

View File

@@ -0,0 +1,57 @@
<Window x:Class="XLAB2.CloneVerificationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Клонирование поверки"
Height="420"
Width="620"
MinHeight="360"
MinWidth="540"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock FontWeight="SemiBold"
Text="{Binding SourceSerialNumber, StringFormat=Источник: зав. № {0}}" />
<TextBlock Grid.Row="1"
Margin="0,8,0,0"
TextWrapping="Wrap"
Text="{Binding VerificationSummary}" />
<TextBlock Grid.Row="2"
Margin="0,12,0,6"
Text="Введите заводские номера строк, в которые нужно скопировать поверку. Поддерживаются разделители: новая строка, табуляция, запятая, точка с запятой." />
<TextBox Grid.Row="3"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
Text="{Binding SerialNumbersText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="4"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<StackPanel Grid.Row="5"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="110"
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 XLAB2
{
public partial class CloneVerificationWindow : Window
{
internal CloneVerificationWindow(CloneVerificationWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class CloneVerificationWindowViewModel : ObservableObject
{
private string _serialNumbersText;
private string _statusText;
public CloneVerificationWindowViewModel(CloneVerificationSeed seed)
{
if (seed == null)
{
throw new ArgumentNullException("seed");
}
SourceSerialNumber = string.IsNullOrWhiteSpace(seed.SourceSerialNumber) ? string.Empty : seed.SourceSerialNumber.Trim();
VerificationSummary = seed.VerificationSummary ?? string.Empty;
ConfirmCommand = new RelayCommand(Confirm, CanConfirm);
CancelCommand = new RelayCommand(Cancel);
UpdateStatus();
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public string SourceSerialNumber { get; private set; }
public string SerialNumbersText
{
get { return _serialNumbersText; }
set
{
if (SetProperty(ref _serialNumbersText, value))
{
UpdateStatus();
((RelayCommand)ConfirmCommand).RaiseCanExecuteChanged();
}
}
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public string VerificationSummary { get; private set; }
public IReadOnlyList<string> GetSerialNumbers()
{
return ParseSerialNumbers(SerialNumbersText);
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private bool CanConfirm(object parameter)
{
return ParseSerialNumbers(SerialNumbersText).Count > 0;
}
private void Confirm(object parameter)
{
RaiseCloseRequested(true);
}
private static List<string> ParseSerialNumbers(string value)
{
var serialNumbers = new List<string>();
var unique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(value))
{
return serialNumbers;
}
var separators = new[] { '\r', '\n', '\t', ',', ';' };
foreach (var token in value.Split(separators, StringSplitOptions.RemoveEmptyEntries))
{
var serialNumber = token.Trim();
if (serialNumber.Length == 0 || !unique.Add(serialNumber))
{
continue;
}
serialNumbers.Add(serialNumber);
}
return serialNumbers;
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
private void UpdateStatus()
{
var serialCount = ParseSerialNumbers(SerialNumbersText).Count;
StatusText = string.Format("Уникальных заводских номеров: {0}.", serialCount);
}
}
}

BIN
XLAB2/ClosePsv.docx Normal file

Binary file not shown.

View File

@@ -0,0 +1,109 @@
<Window x:Class="XLAB2.CreateDocumentWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Новый документ"
Height="360"
Width="520"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<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" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<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"
DisplayMemberPath="Title"
ItemsSource="{Binding DocumentTypes}"
SelectedItem="{Binding SelectedDocumentType, Mode=TwoWay}" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Книга учета" />
<ComboBox Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,8"
DisplayMemberPath="Title"
ItemsSource="{Binding AccountingBooks}"
SelectedItem="{Binding SelectedAccountingBook, Mode=TwoWay}" />
<TextBlock Grid.Row="2"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Номер документа" />
<TextBox Grid.Row="2"
Grid.Column="1"
MinWidth="180"
Margin="0,0,0,8"
Text="{Binding DocumentSequenceNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="3"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Итоговый номер" />
<TextBox Grid.Row="3"
Grid.Column="1"
Margin="0,0,0,8"
IsReadOnly="True"
Text="{Binding DocumentNumber, Mode=OneWay}" />
<TextBlock Grid.Row="4"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Дата приемки" />
<DatePicker Grid.Row="4"
Grid.Column="1"
Margin="0,0,0,8"
SelectedDate="{Binding AcceptedOn, Mode=TwoWay}"
SelectedDateFormat="Short" />
<TextBlock Grid.Row="5"
Grid.ColumnSpan="2"
Margin="0,8,0,4"
Foreground="DimGray"
TextWrapping="Wrap"
Text="Документ без строк EKZMK не хранится в базе. Команда «Создать» создаёт только черновик текущего сеанса." />
<TextBlock Grid.Row="6"
Grid.ColumnSpan="2"
Foreground="Firebrick"
Text="{Binding ValidationMessage}" />
<StackPanel Grid.Row="7"
Grid.ColumnSpan="2"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
Margin="0,12,8,0"
IsDefault="True"
Command="{Binding ConfirmCommand}"
Content="Создать" />
<Button Width="90"
Margin="0,12,0,0"
Command="{Binding CancelCommand}"
Content="Отмена" />
</StackPanel>
</Grid>
</Window>

View File

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

View File

@@ -0,0 +1,212 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class CreateDocumentWindowViewModel : ObservableObject
{
private DateTime? _acceptedOn;
private string _documentSequenceNumber;
private GroupOption _selectedAccountingBook;
private GroupOption _selectedDocumentType;
private string _validationMessage;
public CreateDocumentWindowViewModel(DocumentEditorResult seed, DocumentNumberDirectory directory)
{
if (directory == null)
{
throw new ArgumentNullException("directory");
}
AccountingBooks = new ObservableCollection<GroupOption>(directory.AccountingBooks ?? Array.Empty<GroupOption>());
DocumentTypes = new ObservableCollection<GroupOption>(directory.DocumentTypes ?? Array.Empty<GroupOption>());
_acceptedOn = seed != null ? seed.AcceptedOn : DateTime.Today;
_documentSequenceNumber = seed == null ? string.Empty : seed.DocumentSequenceNumber ?? string.Empty;
_selectedAccountingBook = ResolveSelectedOption(AccountingBooks, seed == null ? null : seed.AccountingBookKey);
_selectedDocumentType = ResolveSelectedOption(DocumentTypes, seed == null ? null : seed.DocumentTypeKey);
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public DateTime? AcceptedOn
{
get { return _acceptedOn; }
set
{
if (SetProperty(ref _acceptedOn, value))
{
ClearValidationMessage();
}
}
}
public ObservableCollection<GroupOption> AccountingBooks { get; private set; }
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public string DocumentNumber
{
get
{
return DocumentNumberFormatter.Build(
SelectedDocumentType == null ? null : SelectedDocumentType.Title,
SelectedAccountingBook == null ? null : SelectedAccountingBook.Title,
DocumentSequenceNumber);
}
}
public string DocumentSequenceNumber
{
get { return _documentSequenceNumber; }
set
{
if (SetProperty(ref _documentSequenceNumber, value))
{
ClearValidationMessage();
OnPropertyChanged("DocumentNumber");
}
}
}
public ObservableCollection<GroupOption> DocumentTypes { get; private set; }
public GroupOption SelectedAccountingBook
{
get { return _selectedAccountingBook; }
set
{
if (SetProperty(ref _selectedAccountingBook, value))
{
ClearValidationMessage();
OnPropertyChanged("DocumentNumber");
}
}
}
public GroupOption SelectedDocumentType
{
get { return _selectedDocumentType; }
set
{
if (SetProperty(ref _selectedDocumentType, value))
{
ClearValidationMessage();
OnPropertyChanged("DocumentNumber");
}
}
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public DocumentEditorResult ToResult()
{
return new DocumentEditorResult
{
AccountingBookKey = SelectedAccountingBook == null ? string.Empty : SelectedAccountingBook.Key,
AccountingBookTitle = SelectedAccountingBook == null ? string.Empty : SelectedAccountingBook.Title,
DocumentNumber = DocumentNumber,
DocumentSequenceNumber = DocumentSequenceNumber == null ? string.Empty : DocumentSequenceNumber.Trim(),
DocumentTypeKey = SelectedDocumentType == null ? string.Empty : SelectedDocumentType.Key,
DocumentTypeTitle = SelectedDocumentType == null ? string.Empty : SelectedDocumentType.Title,
AcceptedOn = AcceptedOn.HasValue ? AcceptedOn.Value : DateTime.Today,
IssuedOn = null
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void ClearValidationMessage()
{
if (!string.IsNullOrEmpty(ValidationMessage))
{
ValidationMessage = string.Empty;
}
}
private void Confirm(object parameter)
{
if (SelectedDocumentType == null)
{
ValidationMessage = "Выберите тип документа.";
return;
}
if (SelectedAccountingBook == null)
{
ValidationMessage = "Выберите книгу учета.";
return;
}
if (string.IsNullOrWhiteSpace(DocumentSequenceNumber))
{
ValidationMessage = "Введите номер документа.";
return;
}
if (!AcceptedOn.HasValue)
{
ValidationMessage = "Укажите дату приемки.";
return;
}
if (DocumentNumber.Length > DocumentNumberFormatter.MaxLength)
{
ValidationMessage = string.Format(
"Итоговый номер документа не должен превышать {0} символов.",
DocumentNumberFormatter.MaxLength);
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private static GroupOption ResolveSelectedOption(ObservableCollection<GroupOption> items, string preferredKey)
{
if (items == null || items.Count == 0)
{
return null;
}
if (!string.IsNullOrWhiteSpace(preferredKey))
{
var matchingItem = items.FirstOrDefault(delegate(GroupOption item)
{
return item != null
&& string.Equals(item.Key, preferredKey.Trim(), StringComparison.OrdinalIgnoreCase);
});
if (matchingItem != null)
{
return matchingItem;
}
}
return items[0];
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
}
}

188
XLAB2/DialogService.cs Normal file
View File

@@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.Windows;
namespace XLAB2
{
internal interface IDialogService
{
DocumentEditorResult ShowCreateDocumentDialog(DocumentEditorResult seed);
IReadOnlyList<AvailableInstrumentItem> ShowInstrumentPickerDialog(string customerName, IReadOnlyList<AvailableInstrumentItem> instruments);
InstrumentTypeSelectionResult ShowInstrumentTypeDialog(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes);
IReadOnlyList<string> ShowCloneVerificationDialog(CloneVerificationSeed seed);
GroupOption ShowAccountingBookEditDialog(GroupOption seed, bool isNew, IReadOnlyList<GroupOption> existingItems);
SpoiDirectoryItem ShowSpoiEditDialog(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems);
SpnmtpDirectoryItem ShowSpnmtpEditDialog(SpnmtpDirectoryItem seed, bool isNew, IReadOnlyList<SpnmtpDirectoryItem> existingItems);
VerificationEditResult ShowVerificationDialog(
VerificationEditSeed seed,
IReadOnlyList<PersonReference> verifiers,
IReadOnlyList<DocumentFormReference> documentForms);
bool Confirm(string message);
void ShowError(string message);
void ShowInfo(string message);
void ShowWarning(string message);
}
internal sealed class DialogService : IDialogService
{
private readonly DocumentNumberDirectoryService _documentNumberDirectoryService;
public DialogService()
{
}
public DialogService(Window owner)
{
Owner = owner;
}
public DialogService(Window owner, DocumentNumberDirectoryService documentNumberDirectoryService)
{
Owner = owner;
_documentNumberDirectoryService = documentNumberDirectoryService;
}
public Window Owner { get; set; }
public DocumentEditorResult ShowCreateDocumentDialog(DocumentEditorResult seed)
{
var directory = (_documentNumberDirectoryService ?? CreateDefaultDocumentNumberDirectoryService()).LoadDirectory();
var viewModel = new CreateDocumentWindowViewModel(seed, directory);
var window = new CreateDocumentWindow(viewModel);
AttachOwner(window);
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public IReadOnlyList<AvailableInstrumentItem> ShowInstrumentPickerDialog(string customerName, IReadOnlyList<AvailableInstrumentItem> instruments)
{
var viewModel = new SelectInstrumentsWindowViewModel(customerName, instruments);
var window = new SelectInstrumentsWindow(viewModel);
AttachOwner(window);
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.GetSelectedItems() : null;
}
public InstrumentTypeSelectionResult ShowInstrumentTypeDialog(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes)
{
var viewModel = new SelectInstrumentTypeWindowViewModel(customerName, instrumentTypes);
var window = new SelectInstrumentTypeWindow(viewModel);
AttachOwner(window);
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.GetResult() : null;
}
public IReadOnlyList<string> ShowCloneVerificationDialog(CloneVerificationSeed seed)
{
var viewModel = new CloneVerificationWindowViewModel(seed);
var window = new CloneVerificationWindow(viewModel);
AttachOwner(window);
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.GetSerialNumbers() : null;
}
public GroupOption ShowAccountingBookEditDialog(GroupOption seed, bool isNew, IReadOnlyList<GroupOption> existingItems)
{
var viewModel = new AccountingBookEditWindowViewModel(seed, isNew, existingItems);
var window = new AccountingBookEditWindow(viewModel);
AttachOwner(window);
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public SpoiDirectoryItem ShowSpoiEditDialog(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems)
{
var viewModel = new SpoiEditWindowViewModel(seed, isNew, existingItems);
var window = new SpoiEditWindow(viewModel);
AttachOwner(window);
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);
var window = new SpnmtpEditWindow(viewModel);
AttachOwner(window);
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public VerificationEditResult ShowVerificationDialog(
VerificationEditSeed seed,
IReadOnlyList<PersonReference> verifiers,
IReadOnlyList<DocumentFormReference> documentForms)
{
var viewModel = new VerificationEditWindowViewModel(seed, verifiers, documentForms);
var window = new VerificationEditWindow(viewModel);
AttachOwner(window);
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public bool Confirm(string message)
{
return Owner == null
? MessageBox.Show(message, "ПСВ", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes
: MessageBox.Show(Owner, message, "ПСВ", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
}
public void ShowError(string message)
{
ShowMessage(message, MessageBoxImage.Error);
}
public void ShowInfo(string message)
{
ShowMessage(message, MessageBoxImage.Information);
}
public void ShowWarning(string message)
{
ShowMessage(message, MessageBoxImage.Warning);
}
private void AttachOwner(Window window)
{
if (Owner != null)
{
window.Owner = Owner;
}
}
private void ShowMessage(string message, MessageBoxImage image)
{
if (Owner == null)
{
MessageBox.Show(message, "ПСВ", MessageBoxButton.OK, image);
return;
}
MessageBox.Show(Owner, message, "ПСВ", MessageBoxButton.OK, image);
}
private static DocumentNumberDirectoryService CreateDefaultDocumentNumberDirectoryService()
{
return new DocumentNumberDirectoryService(System.IO.Path.Combine(AppContext.BaseDirectory, "Assets", "document-number-directory.json"));
}
}
}

View File

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

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Windows;
namespace XLAB2
{
internal interface IEkzDirectoryDialogService
{
EkzDirectoryItem ShowEkzEditDialog(EkzDirectoryItem seed, bool isNew, IReadOnlyList<EkzDirectoryItem> existingItems, EkzDirectoryService service);
bool Confirm(string message);
void ShowError(string message);
void ShowInfo(string message);
void ShowWarning(string message);
}
internal sealed class EkzDirectoryDialogService : IEkzDirectoryDialogService
{
private readonly Window _owner;
public EkzDirectoryDialogService(Window owner)
{
_owner = owner;
}
public bool Confirm(string message)
{
return MessageBox.Show(_owner, message, "ПСВ", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
}
public EkzDirectoryItem ShowEkzEditDialog(EkzDirectoryItem seed, bool isNew, IReadOnlyList<EkzDirectoryItem> existingItems, EkzDirectoryService service)
{
var viewModel = new EkzEditWindowViewModel(seed, isNew, existingItems, service);
var window = new EkzEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
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);
}
}
}

138
XLAB2/EkzDirectoryModels.cs Normal file
View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
namespace XLAB2
{
public sealed class EkzDirectoryItem
{
public int Id { get; set; }
public int TypeSizeId { get; set; }
public string MeasurementAreaName { get; set; }
public string InstrumentName { get; set; }
public string TypeName { get; set; }
public string RangeText { get; set; }
public string AccuracyText { get; set; }
public string RegistryNumber { get; set; }
public int OwnerOrganizationId { get; set; }
public string OwnerOrganizationName { get; set; }
public string SerialNumber { get; set; }
public string InventoryNumber { get; set; }
public string StickerNumbers { get; set; }
public string Notes { get; set; }
}
public sealed class EkzMkDirectoryItem
{
public int CardId { get; set; }
public int InstrumentId { get; set; }
public string VerificationTypeName { get; set; }
public string VerificationOrganizationName { get; set; }
public string DocumentNumber { get; set; }
public string VerificationDocumentNumber { get; set; }
public DateTime? VerificationDocumentDate { get; set; }
public string StickerNumber { get; set; }
public string VerifierName { get; set; }
public int PeriodMonths { get; set; }
public DateTime? AcceptedOn { get; set; }
public DateTime? PlannedOn { get; set; }
public DateTime? PerformedOn { get; set; }
public DateTime? IssuedOn { get; set; }
public bool? IsPassed { get; set; }
public string Notes { get; set; }
public string ResultText
{
get
{
if (!IsPassed.HasValue)
{
return string.Empty;
}
return IsPassed.Value ? "Годен" : "Не годен";
}
}
public string VerificationDocumentDisplay
{
get
{
if (string.IsNullOrWhiteSpace(VerificationDocumentNumber))
{
return VerificationDocumentDate.HasValue ? VerificationDocumentDate.Value.ToString("d") : string.Empty;
}
if (!VerificationDocumentDate.HasValue)
{
return VerificationDocumentNumber;
}
return string.Format("{0} от {1:d}", VerificationDocumentNumber, VerificationDocumentDate.Value);
}
}
}
internal sealed class EkzDeleteImpactItem
{
public string TableName { get; set; }
public int RowCount { get; set; }
}
internal sealed class EkzDeletePreview
{
public bool CanDelete { get; set; }
public IReadOnlyList<EkzDeleteImpactItem> ImpactItems { get; set; }
public string ConfirmationMessage { get; set; }
public string WarningMessage { get; set; }
}
internal sealed class EkzDeleteResult
{
public bool IsDeleted { get; set; }
public IReadOnlyList<EkzDeleteImpactItem> ImpactItems { get; set; }
public string WarningMessage { get; set; }
}
internal static class EkzDirectoryRules
{
public const int SerialNumberMaxLength = 30;
public const int InventoryNumberMaxLength = 30;
public const int NotesMaxLength = 8000;
}
}

View File

@@ -0,0 +1,858 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Microsoft.Data.SqlClient;
namespace XLAB2
{
internal sealed class EkzDirectoryService
{
private static readonly string[] CascadingEkzChildTables = { "EKZMK", "EKZMCP" };
private static readonly string[] CascadingEkzMkChildTables = { "EKZMKFCTVL", "EKZMKDH", "EKZMKEKZK", "EKZMKND", "KSPELEKZMK" };
public int AddEkzItem(EkzDirectoryItem item)
{
var normalizedItem = NormalizeEkzItem(item);
const string sql = @"
INSERT INTO dbo.EKZ
(
IDTPRZ,
IDFRPDV,
KLSIPR,
NNZV,
NNIN,
DSEKZ,
GUIDEKZ,
IsDeleted
)
VALUES
(
@TypeSizeId,
@OwnerOrganizationId,
1,
@SerialNumber,
@InventoryNumber,
@Notes,
@Guid,
0
);
SELECT CAST(SCOPE_IDENTITY() AS int);";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureEkzIsUnique(connection, normalizedItem.TypeSizeId, normalizedItem.OwnerOrganizationId, normalizedItem.SerialNumber, null);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = normalizedItem.OwnerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = normalizedItem.SerialNumber;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@InventoryNumber", SqlDbType.VarChar, EkzDirectoryRules.InventoryNumberMaxLength, normalizedItem.InventoryNumber);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Notes", SqlDbType.VarChar, EkzDirectoryRules.NotesMaxLength, normalizedItem.Notes);
command.Parameters.Add("@Guid", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
return Convert.ToInt32(command.ExecuteScalar());
}
}
public EkzDeletePreview GetEkzDeletePreview(int id)
{
if (id <= 0)
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Не выбрана запись EKZ для удаления."
};
}
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
return BuildEkzDeletePreview(connection, null, id);
}
}
public EkzDeleteResult DeleteEkzItem(int id)
{
if (id <= 0)
{
throw new InvalidOperationException("Не выбрана запись EKZ для удаления.");
}
try
{
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
var preview = BuildEkzDeletePreview(connection, transaction, id);
if (!preview.CanDelete)
{
transaction.Rollback();
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = preview.ImpactItems ?? Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = preview.WarningMessage
};
}
var impactItems = new List<EkzDeleteImpactItem>();
AddImpactItem(impactItems, "EKZMKFCTVL", DeleteEkzMkFctvl(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKDH", DeleteEkzMkDh(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKEKZK", DeleteEkzMkEkzk(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKND", DeleteEkzMkNd(connection, transaction, id));
AddImpactItem(impactItems, "KSPELEKZMK", DeleteKspelEkzMk(connection, transaction, id));
AddImpactItem(impactItems, "DMS", DeleteEkzDms(connection, transaction, id));
AddImpactItem(impactItems, "EKZMK", DeleteEkzMk(connection, transaction, id));
AddImpactItem(impactItems, "EKZMCP", DeleteEkzMcp(connection, transaction, id));
var deletedEkzCount = DeleteEkz(connection, transaction, id);
if (deletedEkzCount == 0)
{
transaction.Rollback();
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Запись EKZ для удаления не найдена."
};
}
AddImpactItem(impactItems, "EKZ", deletedEkzCount);
transaction.Commit();
return new EkzDeleteResult
{
IsDeleted = true,
ImpactItems = OrderImpactItems(impactItems)
};
}
}
}
catch (SqlException ex) when (ex.Number == 547)
{
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = CreateEkzDeleteFailedMessage(ex)
};
}
}
public IReadOnlyList<EkzDirectoryItem> LoadEkzItems()
{
const string sql = @"
SELECT
z.IDEKZ AS Id,
z.IDTPRZ AS TypeSizeId,
areas.NMOI AS MeasurementAreaName,
names.NMTP AS InstrumentName,
tips.TP AS TypeName,
tprz.DPZN AS RangeText,
tprz.HRTC AS AccuracyText,
tprz.NNGSRS AS RegistryNumber,
z.IDFRPDV AS OwnerOrganizationId,
ownerOrg.NMFRPD AS OwnerOrganizationName,
z.NNZV AS SerialNumber,
z.NNIN AS InventoryNumber,
stickers.StickerNumbers AS StickerNumbers,
CAST(z.DSEKZ AS nvarchar(max)) AS Notes
FROM dbo.EKZ z
JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
JOIN dbo.FRPD ownerOrg ON ownerOrg.IDFRPD = z.IDFRPDV
OUTER APPLY
(
SELECT STUFF(
(
SELECT N' ' + sticker.StickerNumber
FROM
(
SELECT DISTINCT LTRIM(RTRIM(m.NNNKL)) AS StickerNumber
FROM dbo.EKZMK m
WHERE m.IDEKZ = z.IDEKZ
AND NULLIF(LTRIM(RTRIM(m.NNNKL)), N'') IS NOT NULL
) sticker
ORDER BY sticker.StickerNumber
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'),
1,
1,
N'') AS StickerNumbers
) stickers
WHERE ISNULL(z.IsDeleted, 0) = 0
ORDER BY ownerOrg.NMFRPD, areas.NMOI, names.NMTP, tips.TP, tprz.DPZN, z.NNZV, z.IDEKZ;";
var items = new List<EkzDirectoryItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new EkzDirectoryItem
{
Id = ReferenceDirectorySqlHelpers.GetInt32(reader, "Id"),
TypeSizeId = ReferenceDirectorySqlHelpers.GetInt32(reader, "TypeSizeId"),
MeasurementAreaName = ReferenceDirectorySqlHelpers.GetString(reader, "MeasurementAreaName"),
InstrumentName = ReferenceDirectorySqlHelpers.GetString(reader, "InstrumentName"),
TypeName = ReferenceDirectorySqlHelpers.GetString(reader, "TypeName"),
RangeText = ReferenceDirectorySqlHelpers.GetString(reader, "RangeText"),
AccuracyText = ReferenceDirectorySqlHelpers.GetString(reader, "AccuracyText"),
RegistryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "RegistryNumber"),
OwnerOrganizationId = ReferenceDirectorySqlHelpers.GetInt32(reader, "OwnerOrganizationId"),
OwnerOrganizationName = ReferenceDirectorySqlHelpers.GetString(reader, "OwnerOrganizationName"),
SerialNumber = ReferenceDirectorySqlHelpers.GetString(reader, "SerialNumber"),
InventoryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "InventoryNumber"),
StickerNumbers = ReferenceDirectorySqlHelpers.GetString(reader, "StickerNumbers"),
Notes = ReferenceDirectorySqlHelpers.GetString(reader, "Notes")
});
}
}
}
return items;
}
public IReadOnlyList<EkzMkDirectoryItem> LoadEkzMkItems(int instrumentId)
{
const string sql = @"
SELECT
m.IDEKZMK AS CardId,
m.IDEKZ AS InstrumentId,
verificationType.NMVDMK AS VerificationTypeName,
organization.NMFRPD AS VerificationOrganizationName,
m.NNZVPV AS DocumentNumber,
verificationDocument.NNDMS AS VerificationDocumentNumber,
verificationDocument.DTDMS AS VerificationDocumentDate,
m.NNNKL AS StickerNumber,
verifier.PRFIO AS VerifierName,
m.PRMK AS PeriodMonths,
m.DTPRM AS AcceptedOn,
m.DTMKPL AS PlannedOn,
m.DTMKFK AS PerformedOn,
m.DTVDM AS IssuedOn,
m.GDN AS IsPassed,
CAST(m.DSEKZMK AS nvarchar(max)) AS Notes
FROM dbo.EKZMK m
LEFT JOIN dbo.SPVDMK verificationType ON verificationType.IDSPVDMK = m.IDSPVDMK
LEFT JOIN dbo.FRPD organization ON organization.IDFRPD = m.IDFRPD
LEFT JOIN dbo.PRSN verifier ON verifier.IDPRSN = m.IDPRSN
OUTER APPLY
(
SELECT TOP (1)
d.NND AS NNDMS,
d.DTD AS DTDMS
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.FRDMS frdms ON frdms.IDFRDMS = d.IDFRDMS
JOIN dbo.SPVDD spvdd ON spvdd.IDSPVDD = frdms.IDSPVDD
WHERE d.IDOD = m.IDEKZMK
AND vdd.IDSPVDOD = 2
AND spvdd.IDSPVDD IN (2, 6, 8)
ORDER BY d.DTD DESC
) verificationDocument
WHERE m.IDEKZ = @InstrumentId
ORDER BY ISNULL(m.DTPRM, CONVERT(datetime, '19000101', 112)) DESC, m.IDEKZMK DESC;";
var items = new List<EkzMkDirectoryItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = instrumentId;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new EkzMkDirectoryItem
{
CardId = ReferenceDirectorySqlHelpers.GetInt32(reader, "CardId"),
InstrumentId = ReferenceDirectorySqlHelpers.GetInt32(reader, "InstrumentId"),
VerificationTypeName = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationTypeName"),
VerificationOrganizationName = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationOrganizationName"),
DocumentNumber = ReferenceDirectorySqlHelpers.GetString(reader, "DocumentNumber"),
VerificationDocumentNumber = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationDocumentNumber"),
VerificationDocumentDate = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "VerificationDocumentDate"),
StickerNumber = ReferenceDirectorySqlHelpers.GetString(reader, "StickerNumber"),
VerifierName = ReferenceDirectorySqlHelpers.GetString(reader, "VerifierName"),
PeriodMonths = ReferenceDirectorySqlHelpers.GetInt32(reader, "PeriodMonths"),
AcceptedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "AcceptedOn"),
PlannedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "PlannedOn"),
PerformedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "PerformedOn"),
IssuedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "IssuedOn"),
IsPassed = ReferenceDirectorySqlHelpers.GetNullableBoolean(reader, "IsPassed"),
Notes = ReferenceDirectorySqlHelpers.GetString(reader, "Notes")
});
}
}
}
return items;
}
public IReadOnlyList<DirectoryLookupItem> LoadFrpdReferences()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
fr.IDFRPD AS Id,
fr.NMFRPD AS Name
FROM dbo.FRPD fr
WHERE NULLIF(LTRIM(RTRIM(fr.NMFRPD)), '') IS NOT NULL
ORDER BY fr.NMFRPD, fr.IDFRPD;");
}
public IReadOnlyList<DirectoryLookupItem> LoadTypeSizeReferences()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
tprz.IDTPRZ AS Id,
LTRIM(RTRIM(
COALESCE(NULLIF(areas.NMOI, N'') + N' / ', N'')
+ COALESCE(NULLIF(names.NMTP, N'') + N' / ', N'')
+ COALESCE(NULLIF(tips.TP, N''), N'')
+ CASE WHEN NULLIF(LTRIM(RTRIM(tprz.DPZN)), N'') IS NULL THEN N'' ELSE N' / ' + tprz.DPZN END
+ CASE
WHEN NULLIF(LTRIM(RTRIM(CONVERT(nvarchar(50), tprz.NNGSRS))), N'') IS NULL THEN N''
ELSE N' / № ГР ' + CONVERT(nvarchar(50), tprz.NNGSRS)
END
)) AS Name
FROM dbo.TPRZ tprz
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
ORDER BY areas.NMOI, names.NMTP, tips.TP, tprz.DPZN, tprz.IDTPRZ;");
}
public void UpdateEkzItem(EkzDirectoryItem item)
{
var normalizedItem = NormalizeEkzItem(item);
if (normalizedItem.Id <= 0)
{
throw new InvalidOperationException("Не выбрана запись EKZ для изменения.");
}
const string sql = @"
UPDATE dbo.EKZ
SET IDTPRZ = @TypeSizeId,
IDFRPDV = @OwnerOrganizationId,
NNZV = @SerialNumber,
NNIN = @InventoryNumber,
DSEKZ = @Notes
WHERE IDEKZ = @Id;
SELECT @@ROWCOUNT;";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureEkzIsUnique(connection, normalizedItem.TypeSizeId, normalizedItem.OwnerOrganizationId, normalizedItem.SerialNumber, normalizedItem.Id);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = normalizedItem.OwnerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = normalizedItem.SerialNumber;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@InventoryNumber", SqlDbType.VarChar, EkzDirectoryRules.InventoryNumberMaxLength, normalizedItem.InventoryNumber);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Notes", SqlDbType.VarChar, EkzDirectoryRules.NotesMaxLength, normalizedItem.Notes);
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись EKZ для изменения не найдена.");
}
}
}
private static EkzDeletePreview BuildEkzDeletePreview(SqlConnection connection, SqlTransaction transaction, int id)
{
if (!EkzExists(connection, transaction, id))
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Запись EKZ для удаления не найдена."
};
}
var cardIds = LoadEkzMkCardIds(connection, transaction, id);
var blockers = new List<DeleteBlockerInfo>();
blockers.AddRange(ReferenceDirectorySqlHelpers.LoadDeleteBlockersFromForeignKeys(connection, transaction, "EKZ", id, CascadingEkzChildTables));
blockers.AddRange(LoadUnhandledDeleteBlockers(connection, transaction, "EKZMK", cardIds, CascadingEkzMkChildTables));
var impactItems = BuildImpactItems(connection, transaction, id, cardIds);
var mergedBlockers = MergeBlockers(blockers);
if (mergedBlockers.Count > 0)
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = impactItems,
WarningMessage = CreateEkzCascadeBlockedMessage(mergedBlockers)
};
}
return new EkzDeletePreview
{
CanDelete = true,
ImpactItems = impactItems,
ConfirmationMessage = CreateEkzDeleteConfirmationMessage(impactItems)
};
}
private static IReadOnlyList<EkzDeleteImpactItem> BuildImpactItems(SqlConnection connection, SqlTransaction transaction, int instrumentId, IReadOnlyCollection<int> cardIds)
{
var impactItems = new List<EkzDeleteImpactItem>();
AddImpactItem(impactItems, "EKZ", 1);
AddImpactItem(impactItems, "EKZMK", cardIds == null ? 0 : cardIds.Count);
AddImpactItem(impactItems, "DMS", CountEkzDms(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKFCTVL", CountEkzMkFctvl(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKDH", CountEkzMkDh(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKEKZK", CountEkzMkEkzk(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKND", CountEkzMkNd(connection, transaction, instrumentId));
AddImpactItem(impactItems, "KSPELEKZMK", CountKspelEkzMk(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMCP", CountEkzMcp(connection, transaction, instrumentId));
return OrderImpactItems(impactItems);
}
private static IReadOnlyList<EkzDeleteImpactItem> OrderImpactItems(IEnumerable<EkzDeleteImpactItem> impactItems)
{
var order = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
{ "EKZ", 1 },
{ "EKZMK", 2 },
{ "DMS", 3 },
{ "EKZMKFCTVL", 4 },
{ "EKZMKDH", 5 },
{ "EKZMKEKZK", 6 },
{ "EKZMKND", 7 },
{ "KSPELEKZMK", 8 },
{ "EKZMCP", 9 }
};
return (impactItems ?? Enumerable.Empty<EkzDeleteImpactItem>())
.Where(delegate(EkzDeleteImpactItem item) { return item != null && item.RowCount > 0; })
.OrderBy(delegate(EkzDeleteImpactItem item)
{
int value;
return order.TryGetValue(item.TableName ?? string.Empty, out value) ? value : int.MaxValue;
})
.ThenBy(delegate(EkzDeleteImpactItem item) { return item.TableName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static void AddImpactItem(ICollection<EkzDeleteImpactItem> impactItems, string tableName, int rowCount)
{
if (impactItems == null || string.IsNullOrWhiteSpace(tableName) || rowCount <= 0)
{
return;
}
impactItems.Add(new EkzDeleteImpactItem
{
TableName = tableName,
RowCount = rowCount
});
}
private static string CreateEkzDeleteConfirmationMessage(IEnumerable<EkzDeleteImpactItem> impactItems)
{
var items = OrderImpactItems(impactItems).ToList();
var lines = new List<string>();
if (items.Count == 0)
{
lines.Add("Будет физически удалена только запись EKZ.");
}
else
{
lines.Add("Будут физически удалены записи:");
foreach (var item in items)
{
lines.Add(string.Format("{0}: {1}", item.TableName, item.RowCount));
}
}
lines.Add(string.Empty);
lines.Add("Продолжить?");
return string.Join(Environment.NewLine, lines.ToArray());
}
private static string CreateEkzCascadeBlockedMessage(IEnumerable<DeleteBlockerInfo> blockers)
{
return string.Format(
"Экземпляр не может быть удалён автоматически. Есть связанные записи в таблицах, которые не входят в каскад удаления: {0}.",
FormatBlockerDetails(blockers));
}
private static string CreateEkzDeleteFailedMessage(SqlException ex)
{
var suffix = ex == null || string.IsNullOrWhiteSpace(ex.Message)
? string.Empty
: " " + ex.Message.Trim();
return "Экземпляр не может быть удалён из-за ограничений ссылочной целостности БД." + suffix;
}
private static string FormatBlockerDetails(IEnumerable<DeleteBlockerInfo> blockers)
{
var details = string.Join(", ", (blockers ?? Enumerable.Empty<DeleteBlockerInfo>())
.Where(delegate(DeleteBlockerInfo blocker) { return blocker != null && blocker.RowCount > 0; })
.OrderBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.Select(delegate(DeleteBlockerInfo blocker) { return string.Format("{0}: {1}", blocker.TableName, blocker.RowCount); }));
return string.IsNullOrWhiteSpace(details) ? "связанные данные" : details;
}
private static List<DeleteBlockerInfo> MergeBlockers(IEnumerable<DeleteBlockerInfo> blockers)
{
return (blockers ?? Enumerable.Empty<DeleteBlockerInfo>())
.Where(delegate(DeleteBlockerInfo blocker) { return blocker != null && blocker.RowCount > 0 && !string.IsNullOrWhiteSpace(blocker.TableName); })
.GroupBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, DeleteBlockerInfo> group)
{
return new DeleteBlockerInfo
{
TableName = group.Key,
RowCount = group.Sum(delegate(DeleteBlockerInfo blocker) { return blocker.RowCount; })
};
})
.OrderBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static List<DeleteBlockerInfo> LoadUnhandledDeleteBlockers(SqlConnection connection, SqlTransaction transaction, string parentTableName, IEnumerable<int> ids, IEnumerable<string> excludedChildTables)
{
var blockers = new List<DeleteBlockerInfo>();
foreach (var id in (ids ?? Enumerable.Empty<int>()).Where(delegate(int value) { return value > 0; }).Distinct())
{
blockers.AddRange(ReferenceDirectorySqlHelpers.LoadDeleteBlockersFromForeignKeys(connection, transaction, parentTableName, id, excludedChildTables));
}
return MergeBlockers(blockers);
}
private static bool EkzExists(SqlConnection connection, SqlTransaction transaction, int id)
{
const string sql = @"
SELECT COUNT(1)
FROM dbo.EKZ
WHERE IDEKZ = @Id;";
return ExecuteScalarForId(connection, transaction, sql, id) > 0;
}
private static List<int> LoadEkzMkCardIds(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
const string sql = @"
SELECT IDEKZMK
FROM dbo.EKZMK
WHERE IDEKZ = @InstrumentId
ORDER BY IDEKZMK;";
return ExecuteIdList(connection, transaction, sql, "@InstrumentId", instrumentId);
}
private static int CountEkzMcp(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMCP
WHERE IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkFctvl(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKFCTVL child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkDh(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKDH child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkEkzk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKEKZK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkNd(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKND child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountKspelEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.KSPELEKZMK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzDms(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.EKZMK m ON m.IDEKZMK = d.IDOD
WHERE m.IDEKZ = @InstrumentId
AND vdd.IDSPVDOD = 2;", instrumentId);
}
private static int DeleteEkzMcp(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE FROM dbo.EKZMCP
WHERE IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkFctvl(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKFCTVL child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkDh(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKDH child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkEkzk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKEKZK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkNd(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKND child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteKspelEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.KSPELEKZMK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzDms(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE d
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.EKZMK m ON m.IDEKZMK = d.IDOD
WHERE m.IDEKZ = @InstrumentId
AND vdd.IDSPVDOD = 2;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE FROM dbo.EKZMK
WHERE IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkz(SqlConnection connection, SqlTransaction transaction, int id)
{
return ExecuteScalarForId(connection, transaction, @"
DELETE FROM dbo.EKZ
WHERE IDEKZ = @Id;
SELECT @@ROWCOUNT;", id);
}
private static int ExecuteScalarForInstrument(SqlConnection connection, SqlTransaction transaction, string sql, int instrumentId)
{
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = instrumentId;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static int ExecuteScalarForId(SqlConnection connection, SqlTransaction transaction, string sql, int id)
{
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static List<int> ExecuteIdList(SqlConnection connection, SqlTransaction transaction, string sql, string parameterName, int parameterValue)
{
var result = new List<int>();
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add(parameterName, SqlDbType.Int).Value = parameterValue;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
result.Add(reader.GetInt32(0));
}
}
}
return result;
}
private static string NormalizeOptional(string value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
private static EkzDirectoryItem NormalizeEkzItem(EkzDirectoryItem item)
{
if (item == null)
{
throw new InvalidOperationException("Не переданы данные записи EKZ.");
}
var normalizedItem = new EkzDirectoryItem
{
Id = item.Id,
TypeSizeId = item.TypeSizeId,
OwnerOrganizationId = item.OwnerOrganizationId,
SerialNumber = NormalizeOptional(item.SerialNumber),
InventoryNumber = NormalizeOptional(item.InventoryNumber),
Notes = NormalizeOptional(item.Notes)
};
if (normalizedItem.TypeSizeId <= 0)
{
throw new InvalidOperationException("Не указан типоразмер СИ.");
}
if (normalizedItem.OwnerOrganizationId <= 0)
{
throw new InvalidOperationException("Не указана организация-владелец.");
}
if (string.IsNullOrWhiteSpace(normalizedItem.SerialNumber))
{
throw new InvalidOperationException("Не указан заводской номер.");
}
if (normalizedItem.SerialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength));
}
if (!string.IsNullOrWhiteSpace(normalizedItem.InventoryNumber) && normalizedItem.InventoryNumber.Length > EkzDirectoryRules.InventoryNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Инвентарный номер не должен превышать {0} символов.", EkzDirectoryRules.InventoryNumberMaxLength));
}
if (!string.IsNullOrWhiteSpace(normalizedItem.Notes) && normalizedItem.Notes.Length > EkzDirectoryRules.NotesMaxLength)
{
throw new InvalidOperationException(string.Format("Примечание не должно превышать {0} символов.", EkzDirectoryRules.NotesMaxLength));
}
return normalizedItem;
}
private static void EnsureEkzIsUnique(SqlConnection connection, int typeSizeId, int ownerOrganizationId, string serialNumber, int? excludeId)
{
const string sql = @"
SELECT TOP (1) z.IDEKZ
FROM dbo.EKZ z
WHERE z.IDTPRZ = @TypeSizeId
AND z.IDFRPDV = @OwnerOrganizationId
AND z.NNZV = @SerialNumber
AND ISNULL(z.IsDeleted, 0) = 0
AND (@ExcludeId IS NULL OR z.IDEKZ <> @ExcludeId)
ORDER BY z.IDEKZ DESC;";
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = ownerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = serialNumber;
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@ExcludeId", excludeId);
if (command.ExecuteScalar() != null)
{
throw new InvalidOperationException("Экземпляр с таким типоразмером, владельцем и заводским номером уже существует.");
}
}
}
}
}

View File

@@ -0,0 +1,198 @@
<Window x:Class="XLAB2.EkzDirectoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Экземпляры"
Height="900"
Width="1540"
MinHeight="760"
MinWidth="1260"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="2.2*" />
<RowDefinition Height="1.6*" />
<RowDefinition Height="Auto" />
<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="Поиск" />
<TextBox Width="360"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DockPanel>
<StackPanel Grid.Row="1"
Margin="0,8,0,0"
Orientation="Horizontal">
<TextBlock Margin="0,0,8,0"
VerticalAlignment="Center"
Text="Организация-владелец" />
<ComboBox Width="420"
ItemsSource="{Binding OwnerFilterItems}"
SelectedValue="{Binding SelectedOwnerFilterId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
</Grid>
<GroupBox Grid.Row="1"
Header="Экземпляры (EKZ)">
<DataGrid ItemsSource="{Binding EkzItems}"
SelectedItem="{Binding SelectedEkz, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddEkzCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить"
Command="{Binding EditEkzCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить"
Command="{Binding DeleteEkzCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Организация-владелец"
Width="220"
Binding="{Binding OwnerOrganizationName}" />
<DataGridTextColumn Header="Область измерений"
Width="160"
Binding="{Binding MeasurementAreaName}" />
<DataGridTextColumn Header="Наименование"
Width="220"
Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип"
Width="180"
Binding="{Binding TypeName}" />
<DataGridTextColumn Header="Диапазон"
Width="220"
Binding="{Binding RangeText}" />
<DataGridTextColumn Header="Х-ка точности"
Width="150"
Binding="{Binding AccuracyText}" />
<DataGridTextColumn Header="№ Госреестра"
Width="120"
Binding="{Binding RegistryNumber}" />
<DataGridTextColumn Header="Заводской номер"
Width="140"
Binding="{Binding SerialNumber}" />
<DataGridTextColumn Header="Инвентарный номер"
Width="140"
Binding="{Binding InventoryNumber}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<GroupBox Grid.Row="2"
Margin="0,12,0,0"
Header="МК выбранного экземпляра (EKZMK)">
<DataGrid ItemsSource="{Binding EkzMkItems}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="ПСВ/Акт-справка"
Width="220"
Binding="{Binding DocumentNumber}" />
<DataGridTextColumn Header="Документ по поверке"
Width="180"
Binding="{Binding VerificationDocumentDisplay}" />
<DataGridTextColumn Header="Номер наклейки"
Width="140"
Binding="{Binding StickerNumber}" />
<DataGridTextColumn Header="Поверитель"
Width="180"
Binding="{Binding VerifierName}" />
<DataGridTextColumn Header="Период, мес."
Width="95"
Binding="{Binding PeriodMonths}" />
<DataGridTextColumn Header="Принят"
Width="95"
Binding="{Binding AcceptedOn, StringFormat=d}" />
<DataGridTextColumn Header="Поверен"
Width="95"
Binding="{Binding PerformedOn, StringFormat=d}" />
<DataGridTextColumn Header="Выдан"
Width="95"
Binding="{Binding IssuedOn, StringFormat=d}" />
<DataGridTextColumn Header="Результат"
Width="95"
Binding="{Binding ResultText}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<TextBlock Grid.Row="3"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<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 XLAB2
{
public partial class EkzDirectoryWindow : Window
{
private readonly EkzDirectoryWindowViewModel _viewModel;
public EkzDirectoryWindow()
{
InitializeComponent();
_viewModel = new EkzDirectoryWindowViewModel(new EkzDirectoryService(), new EkzDirectoryDialogService(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,568 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class EkzDirectoryWindowViewModel : ObservableObject
{
private readonly IEkzDirectoryDialogService _dialogService;
private readonly EkzDirectoryService _service;
private List<EkzDirectoryItem> _ekzCache;
private bool _isApplyingFilter;
private bool _isBusy;
private string _searchText;
private EkzDirectoryItem _selectedEkz;
private int _selectedOwnerFilterId;
private string _statusText;
public EkzDirectoryWindowViewModel(EkzDirectoryService service, IEkzDirectoryDialogService dialogService)
{
_service = service;
_dialogService = dialogService;
_ekzCache = new List<EkzDirectoryItem>();
EkzItems = new ObservableCollection<EkzDirectoryItem>();
EkzMkItems = new ObservableCollection<EkzMkDirectoryItem>();
OwnerFilterItems = new ObservableCollection<DirectoryLookupItem>();
OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" });
AddEkzCommand = new RelayCommand(delegate { AddEkzAsync(); }, delegate { return !IsBusy; });
EditEkzCommand = new RelayCommand(delegate { EditEkzAsync(); }, delegate { return !IsBusy && SelectedEkz != null; });
DeleteEkzCommand = new RelayCommand(delegate { DeleteEkzWithPreviewAsync(); }, delegate { return !IsBusy && SelectedEkz != null; });
RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });
UpdateStatus();
}
public ICommand AddEkzCommand { get; private set; }
public ICommand DeleteEkzCommand { get; private set; }
public ICommand EditEkzCommand { get; private set; }
public ObservableCollection<EkzDirectoryItem> EkzItems { get; private set; }
public ObservableCollection<EkzMkDirectoryItem> EkzMkItems { get; private set; }
public bool IsBusy
{
get { return _isBusy; }
private set
{
if (SetProperty(ref _isBusy, value))
{
RaiseCommandStates();
}
}
}
public ObservableCollection<DirectoryLookupItem> OwnerFilterItems { get; private set; }
public ICommand RefreshCommand { get; private set; }
public string SearchText
{
get { return _searchText; }
set
{
if (SetProperty(ref _searchText, value))
{
ApplyFilter(SelectedEkz == null ? (int?)null : SelectedEkz.Id);
}
}
}
public EkzDirectoryItem SelectedEkz
{
get { return _selectedEkz; }
set
{
if (SetProperty(ref _selectedEkz, value))
{
RaiseCommandStates();
if (!_isApplyingFilter)
{
LoadEkzMkForSelection();
}
UpdateStatus();
}
}
}
public int SelectedOwnerFilterId
{
get { return _selectedOwnerFilterId; }
set
{
if (SetProperty(ref _selectedOwnerFilterId, value))
{
ApplyFilter(SelectedEkz == null ? (int?)null : SelectedEkz.Id);
}
}
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public async Task InitializeAsync()
{
await ExecuteBusyOperationAsync(delegate { return RefreshCoreAsync(null); });
}
private void AddEkzAsync()
{
var result = _dialogService.ShowEkzEditDialog(new EkzDirectoryItem(), true, _ekzCache.ToList(), _service);
if (result == null)
{
return;
}
RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _service.AddEkzItem(result); });
await RefreshCoreAsync(createdId);
_dialogService.ShowInfo("Запись EKZ добавлена.");
});
}
private void ApplyFilter(int? preferredId)
{
var filteredItems = _ekzCache.Where(delegate(EkzDirectoryItem item)
{
return MatchesOwnerFilter(item) && MatchesSearch(item);
}).ToList();
_isApplyingFilter = true;
try
{
EkzItems.Clear();
foreach (var item in filteredItems)
{
EkzItems.Add(item);
}
SelectedEkz = preferredId.HasValue
? EkzItems.FirstOrDefault(delegate(EkzDirectoryItem item) { return item.Id == preferredId.Value; })
: EkzItems.FirstOrDefault();
}
finally
{
_isApplyingFilter = false;
}
if (!IsBusy)
{
LoadEkzMkForSelection();
}
UpdateStatus();
}
private static EkzDirectoryItem CloneEkz(EkzDirectoryItem source)
{
return new EkzDirectoryItem
{
Id = source.Id,
TypeSizeId = source.TypeSizeId,
MeasurementAreaName = source.MeasurementAreaName,
InstrumentName = source.InstrumentName,
TypeName = source.TypeName,
RangeText = source.RangeText,
AccuracyText = source.AccuracyText,
RegistryNumber = source.RegistryNumber,
OwnerOrganizationId = source.OwnerOrganizationId,
OwnerOrganizationName = source.OwnerOrganizationName,
SerialNumber = source.SerialNumber,
InventoryNumber = source.InventoryNumber,
StickerNumbers = source.StickerNumbers,
Notes = source.Notes
};
}
private async void DeleteEkzWithPreviewAsync()
{
if (SelectedEkz == null)
{
return;
}
var selected = SelectedEkz;
EkzDeletePreview preview;
try
{
IsBusy = true;
preview = await Task.Run(delegate { return _service.GetEkzDeletePreview(selected.Id); });
}
catch (InvalidOperationException ex)
{
_dialogService.ShowWarning(ex.Message);
return;
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
return;
}
finally
{
IsBusy = false;
}
if (preview == null)
{
return;
}
if (!preview.CanDelete)
{
_dialogService.ShowWarning(preview.WarningMessage);
return;
}
if (!_dialogService.Confirm(BuildDeleteConfirmationMessage(selected, preview)))
{
return;
}
RunMutationOperation(async delegate
{
var result = await Task.Run(delegate { return _service.DeleteEkzItem(selected.Id); });
if (!result.IsDeleted)
{
_dialogService.ShowWarning(result.WarningMessage);
return;
}
await RefreshCoreAsync(null);
_dialogService.ShowInfo(BuildDeleteResultMessage(result));
});
}
private async void DeleteEkzAsync()
{
if (SelectedEkz == null)
{
return;
}
var selected = SelectedEkz;
EkzDeletePreview preview;
try
{
IsBusy = true;
preview = await Task.Run(delegate { return _service.GetEkzDeletePreview(selected.Id); });
}
catch (InvalidOperationException ex)
{
_dialogService.ShowWarning(ex.Message);
return;
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
return;
}
finally
{
IsBusy = false;
}
if (preview == null)
{
return;
}
if (!preview.CanDelete)
{
_dialogService.ShowWarning(preview.WarningMessage);
return;
}
if (!_dialogService.Confirm(string.Format("Удалить экземпляр \"{0}\"?", selected.SerialNumber)))
{
return;
}
RunMutationOperation(async delegate
{
var result = await Task.Run(delegate { return _service.DeleteEkzItem(selected.Id); });
if (!result.IsDeleted)
{
_dialogService.ShowWarning(result.WarningMessage);
return;
}
await RefreshCoreAsync(null);
_dialogService.ShowInfo("Запись EKZ удалена.");
});
}
private void EditEkzAsync()
{
if (SelectedEkz == null)
{
return;
}
var result = _dialogService.ShowEkzEditDialog(CloneEkz(SelectedEkz), false, _ekzCache.ToList(), _service);
if (result == null)
{
return;
}
RunMutationOperation(async delegate
{
await Task.Run(delegate { _service.UpdateEkzItem(result); });
await RefreshCoreAsync(result.Id);
_dialogService.ShowInfo("Запись EKZ обновлена.");
});
}
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 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 LoadEkzMkForSelection()
{
if (IsBusy)
{
return;
}
RunBusyOperation(async delegate
{
if (SelectedEkz == null)
{
EkzMkItems.Clear();
UpdateStatus();
return;
}
await RefreshEkzMkCoreAsync(SelectedEkz.Id);
});
}
private bool MatchesOwnerFilter(EkzDirectoryItem item)
{
return SelectedOwnerFilterId <= 0
|| (item != null && item.OwnerOrganizationId == SelectedOwnerFilterId);
}
private bool MatchesSearch(EkzDirectoryItem item)
{
var tokens = GetSearchTokens();
if (tokens.Length == 0)
{
return true;
}
var haystack = string.Join(
" ",
new[]
{
item == null ? null : item.Id.ToString(),
item == null ? null : item.OwnerOrganizationName,
item == null ? null : item.MeasurementAreaName,
item == null ? null : item.InstrumentName,
item == null ? null : item.TypeName,
item == null ? null : item.RangeText,
item == null ? null : item.AccuracyText,
item == null ? null : item.RegistryNumber,
item == null ? null : item.SerialNumber,
item == null ? null : item.InventoryNumber,
item == null ? null : item.StickerNumbers,
item == null ? null : item.Notes
}.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); }))
.ToUpperInvariant();
return tokens.All(delegate(string token) { return haystack.IndexOf(token, StringComparison.Ordinal) >= 0; });
}
private async Task RefreshCoreAsync(int? idToSelect)
{
var currentSelectedId = idToSelect ?? (SelectedEkz == null ? (int?)null : SelectedEkz.Id);
var currentOwnerFilterId = SelectedOwnerFilterId;
var ekzTask = Task.Run(delegate { return _service.LoadEkzItems(); });
var ownerTask = Task.Run(delegate { return _service.LoadFrpdReferences(); });
await Task.WhenAll(ekzTask, ownerTask);
_ekzCache = ekzTask.Result.ToList();
RebuildOwnerFilters(ownerTask.Result, currentOwnerFilterId);
ApplyFilter(currentSelectedId);
if (SelectedEkz == null)
{
EkzMkItems.Clear();
UpdateStatus();
return;
}
await RefreshEkzMkCoreAsync(SelectedEkz.Id);
UpdateStatus();
}
private async Task RefreshEkzMkCoreAsync(int instrumentId)
{
var items = await Task.Run(delegate { return _service.LoadEkzMkItems(instrumentId); });
EkzMkItems.Clear();
foreach (var item in items)
{
EkzMkItems.Add(item);
}
}
private void RebuildOwnerFilters(IReadOnlyList<DirectoryLookupItem> owners, int selectedId)
{
OwnerFilterItems.Clear();
OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" });
foreach (var owner in owners ?? Array.Empty<DirectoryLookupItem>())
{
OwnerFilterItems.Add(owner);
}
_selectedOwnerFilterId = OwnerFilterItems.Any(delegate(DirectoryLookupItem item) { return item.Id == selectedId; })
? selectedId
: 0;
OnPropertyChanged("SelectedOwnerFilterId");
}
private void RefreshAsync()
{
RunBusyOperation(delegate { return RefreshCoreAsync(SelectedEkz == null ? (int?)null : SelectedEkz.Id); });
}
private void RaiseCommandStates()
{
((RelayCommand)AddEkzCommand).RaiseCanExecuteChanged();
((RelayCommand)EditEkzCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteEkzCommand).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 static string BuildDeleteConfirmationMessage(EkzDirectoryItem selected, EkzDeletePreview preview)
{
return string.Format(
"Удалить экземпляр \"{0}\"?{1}{1}{2}",
selected == null || string.IsNullOrWhiteSpace(selected.SerialNumber) ? "(без номера)" : selected.SerialNumber,
Environment.NewLine,
preview == null ? string.Empty : preview.ConfirmationMessage ?? string.Empty);
}
private static string BuildDeleteResultMessage(EkzDeleteResult result)
{
var impacts = result == null || result.ImpactItems == null
? new List<EkzDeleteImpactItem>()
: result.ImpactItems.Where(delegate(EkzDeleteImpactItem item) { return item != null && item.RowCount > 0; }).ToList();
if (impacts.Count == 0)
{
return "Запись EKZ удалена.";
}
return "Удалены записи: " + string.Join(", ", impacts.Select(delegate(EkzDeleteImpactItem item)
{
return string.Format("{0}: {1}", item.TableName, item.RowCount);
})) + ".";
}
private void UpdateStatus()
{
var searchPrefix = string.IsNullOrWhiteSpace(SearchText)
? string.Empty
: string.Format("Поиск: \"{0}\". ", SearchText.Trim());
var ownerName = OwnerFilterItems.FirstOrDefault(delegate(DirectoryLookupItem item) { return item.Id == SelectedOwnerFilterId; });
var ownerPrefix = SelectedOwnerFilterId <= 0 || ownerName == null
? string.Empty
: string.Format("Владелец: \"{0}\". ", ownerName.Name);
StatusText = string.Format(
"{0}{1}EKZ: {2}/{3}. EKZMK: {4}.",
searchPrefix,
ownerPrefix,
EkzItems.Count,
_ekzCache.Count,
EkzMkItems.Count);
}
}
}

109
XLAB2/EkzEditWindow.xaml Normal file
View File

@@ -0,0 +1,109 @@
<Window x:Class="XLAB2.EkzEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="340"
Width="860"
MinHeight="340"
MinWidth="760"
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="*" />
</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 TypeSizeItems}"
SelectedValue="{Binding TypeSizeId}"
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 OwnerItems}"
SelectedValue="{Binding OwnerOrganizationId}"
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 SerialNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="3"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Инвентарный номер" />
<TextBox Grid.Row="3"
Grid.Column="1"
Margin="0,0,0,8"
Text="{Binding InventoryNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="4"
Grid.Column="0"
Margin="0,0,12,0"
VerticalAlignment="Top"
Text="Примечание" />
<TextBox Grid.Row="4"
Grid.Column="1"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
Text="{Binding Notes, 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 XLAB2
{
public partial class EkzEditWindow : Window
{
internal EkzEditWindow(EkzEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class EkzEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<EkzDirectoryItem> _existingItems;
private string _inventoryNumber;
private string _notes;
private int _ownerOrganizationId;
private string _serialNumber;
private int _typeSizeId;
private string _validationMessage;
public EkzEditWindowViewModel(EkzDirectoryItem seed, bool isNew, IReadOnlyList<EkzDirectoryItem> existingItems, EkzDirectoryService service)
{
var source = seed ?? new EkzDirectoryItem();
_existingItems = existingItems ?? Array.Empty<EkzDirectoryItem>();
Id = source.Id;
IsNew = isNew;
TypeSizeItems = service.LoadTypeSizeReferences();
OwnerItems = service.LoadFrpdReferences();
TypeSizeId = source.TypeSizeId;
OwnerOrganizationId = source.OwnerOrganizationId;
SerialNumber = source.SerialNumber ?? string.Empty;
InventoryNumber = source.InventoryNumber ?? string.Empty;
Notes = source.Notes ?? 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 int Id { get; private set; }
public bool IsNew { get; private set; }
public string InventoryNumber
{
get { return _inventoryNumber; }
set { SetProperty(ref _inventoryNumber, value); }
}
public string Notes
{
get { return _notes; }
set { SetProperty(ref _notes, value); }
}
public IReadOnlyList<DirectoryLookupItem> OwnerItems { get; private set; }
public int OwnerOrganizationId
{
get { return _ownerOrganizationId; }
set { SetProperty(ref _ownerOrganizationId, value); }
}
public string SerialNumber
{
get { return _serialNumber; }
set { SetProperty(ref _serialNumber, value); }
}
public string Title
{
get { return IsNew ? "Новый экземпляр" : "Редактирование экземпляра"; }
}
public IReadOnlyList<DirectoryLookupItem> TypeSizeItems { get; private set; }
public int TypeSizeId
{
get { return _typeSizeId; }
set { SetProperty(ref _typeSizeId, value); }
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public EkzDirectoryItem ToResult()
{
return new EkzDirectoryItem
{
Id = Id,
TypeSizeId = TypeSizeId,
OwnerOrganizationId = OwnerOrganizationId,
SerialNumber = Normalize(SerialNumber),
InventoryNumber = Normalize(InventoryNumber),
Notes = Normalize(Notes)
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
var serialNumber = Normalize(SerialNumber);
var inventoryNumber = Normalize(InventoryNumber);
var notes = Normalize(Notes);
if (TypeSizeId <= 0)
{
ValidationMessage = "Укажите типоразмер СИ.";
return;
}
if (OwnerOrganizationId <= 0)
{
ValidationMessage = "Укажите организацию-владельца.";
return;
}
if (string.IsNullOrWhiteSpace(serialNumber))
{
ValidationMessage = "Укажите заводской номер.";
return;
}
if (serialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
ValidationMessage = string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength);
return;
}
if (!string.IsNullOrWhiteSpace(inventoryNumber) && inventoryNumber.Length > EkzDirectoryRules.InventoryNumberMaxLength)
{
ValidationMessage = string.Format("Инвентарный номер не должен превышать {0} символов.", EkzDirectoryRules.InventoryNumberMaxLength);
return;
}
if (!string.IsNullOrWhiteSpace(notes) && notes.Length > EkzDirectoryRules.NotesMaxLength)
{
ValidationMessage = string.Format("Примечание не должно превышать {0} символов.", EkzDirectoryRules.NotesMaxLength);
return;
}
var duplicate = _existingItems.FirstOrDefault(delegate(EkzDirectoryItem item)
{
return item != null
&& item.Id != Id
&& item.TypeSizeId == TypeSizeId
&& item.OwnerOrganizationId == OwnerOrganizationId
&& string.Equals(item.SerialNumber ?? string.Empty, serialNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
});
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);
}
}
}
}

View File

@@ -0,0 +1,70 @@
using System.Collections.Generic;
using System.Windows;
namespace XLAB2
{
internal interface IFrpdDirectoryDialogService
{
bool Confirm(string message);
FrpdDirectoryItem ShowFrpdEditDialog(FrpdDirectoryItem seed, bool isNew, IReadOnlyList<FrpdDirectoryItem> existingItems, FrpdDirectoryService service);
FrpdvdDirectoryItem ShowFrpdvdEditDialog(FrpdvdDirectoryItem seed, bool isNew, IReadOnlyList<FrpdvdDirectoryItem> existingItems, FrpdDirectoryService service);
void ShowError(string message);
void ShowInfo(string message);
void ShowWarning(string message);
}
internal sealed class FrpdDirectoryDialogService : IFrpdDirectoryDialogService
{
private readonly Window _owner;
public FrpdDirectoryDialogService(Window owner)
{
_owner = owner;
}
public bool Confirm(string message)
{
return MessageBox.Show(_owner, message, "ПСВ", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
}
public FrpdDirectoryItem ShowFrpdEditDialog(FrpdDirectoryItem seed, bool isNew, IReadOnlyList<FrpdDirectoryItem> existingItems, FrpdDirectoryService service)
{
var viewModel = new FrpdEditWindowViewModel(seed, isNew, existingItems, service);
var window = new FrpdEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public FrpdvdDirectoryItem ShowFrpdvdEditDialog(FrpdvdDirectoryItem seed, bool isNew, IReadOnlyList<FrpdvdDirectoryItem> existingItems, FrpdDirectoryService service)
{
var viewModel = new FrpdvdEditWindowViewModel(seed, isNew, existingItems, service);
var window = new FrpdvdEditWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
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,45 @@
using System;
namespace XLAB2
{
public sealed class FrpdDirectoryItem
{
public string ActivityNames { get; set; }
public DateTime? CreatedOn { get; set; }
public string Guid { get; set; }
public int Id { get; set; }
public DateTime? LiquidatedOn { get; set; }
public string LocalCode { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public string ParentName { get; set; }
}
public sealed class FrpdvdDirectoryItem
{
public string ActivityName { get; set; }
public int ActivityId { get; set; }
public int FrpdId { get; set; }
public int Id { get; set; }
}
internal static class FrpdDirectoryRules
{
public const int GuidMaxLength = 50;
public const int LocalCodeMaxLength = 20;
public const int NameMaxLength = 80;
}
}

View File

@@ -0,0 +1,560 @@
using System;
using System.Collections.Generic;
using System.Data;
using Microsoft.Data.SqlClient;
using System.Linq;
namespace XLAB2
{
internal sealed class FrpdDirectoryService
{
public int AddFrpdItem(FrpdDirectoryItem item)
{
var normalizedItem = NormalizeFrpdItem(item);
var guidForInsert = string.IsNullOrWhiteSpace(normalizedItem.Guid)
? Guid.NewGuid().ToString().ToUpperInvariant()
: normalizedItem.Guid;
const string sql = @"
INSERT INTO dbo.FRPD
(
IDFRPDR,
NMFRPD,
KDFRPDLC,
FRPDGUID,
DTSZFRPD,
DTLKFRPD
)
VALUES
(
@ParentId,
@Name,
@LocalCode,
@Guid,
@CreatedOn,
@LiquidatedOn
);
SELECT CAST(SCOPE_IDENTITY() AS int);";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureFrpdGuidIsUnique(connection, guidForInsert, null);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@ParentId", normalizedItem.ParentId);
command.Parameters.Add("@Name", SqlDbType.VarChar, FrpdDirectoryRules.NameMaxLength).Value = normalizedItem.Name;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@LocalCode", SqlDbType.VarChar, FrpdDirectoryRules.LocalCodeMaxLength, normalizedItem.LocalCode);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Guid", SqlDbType.VarChar, FrpdDirectoryRules.GuidMaxLength, guidForInsert);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@CreatedOn", normalizedItem.CreatedOn);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@LiquidatedOn", normalizedItem.LiquidatedOn);
try
{
return Convert.ToInt32(command.ExecuteScalar());
}
catch (SqlException ex) when (ReferenceDirectorySqlHelpers.IsDuplicateViolation(ex, "IX_FRPD_FRPDGUID"))
{
throw CreateFrpdDuplicateGuidException(guidForInsert);
}
}
}
public int AddFrpdvdItem(FrpdvdDirectoryItem item)
{
var normalizedItem = NormalizeFrpdvdItem(item);
const string sql = @"
INSERT INTO dbo.FRPDVD
(
IDFRPD,
IDSPVDDO
)
VALUES
(
@FrpdId,
@ActivityId
);
SELECT CAST(SCOPE_IDENTITY() AS int);";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureFrpdvdIsUnique(connection, normalizedItem.FrpdId, normalizedItem.ActivityId, null);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@FrpdId", SqlDbType.Int).Value = normalizedItem.FrpdId;
command.Parameters.Add("@ActivityId", SqlDbType.Int).Value = normalizedItem.ActivityId;
try
{
return Convert.ToInt32(command.ExecuteScalar());
}
catch (SqlException ex) when (ReferenceDirectorySqlHelpers.IsDuplicateViolation(ex, "XAK1FRPDVD"))
{
throw CreateFrpdvdDuplicateException();
}
}
}
public DirectoryDeleteResult DeleteFrpdItem(int id)
{
if (id <= 0)
{
throw new InvalidOperationException("Не выбрана запись FRPD для удаления.");
}
const string sql = @"
DELETE FROM dbo.FRPD
WHERE IDFRPD = @Id;
SELECT @@ROWCOUNT;";
try
{
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
var blockers = ReferenceDirectorySqlHelpers.LoadDeleteBlockersFromForeignKeys(connection, "FRPD", id);
if (blockers.Count > 0)
{
return new DirectoryDeleteResult
{
IsDeleted = false,
WarningMessage = ReferenceDirectorySqlHelpers.CreateDeleteBlockedMessage("FRPD", blockers)
};
}
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись FRPD для удаления не найдена.");
}
}
return new DirectoryDeleteResult
{
IsDeleted = true
};
}
}
catch (SqlException ex) when (ex.Number == 547)
{
return new DirectoryDeleteResult
{
IsDeleted = false,
WarningMessage = ReferenceDirectorySqlHelpers.CreateDeleteBlockedMessage("FRPD", ex)
};
}
}
public DirectoryDeleteResult DeleteFrpdvdItem(int id)
{
if (id <= 0)
{
throw new InvalidOperationException("Не выбрана запись FRPDVD для удаления.");
}
const string sql = @"
DELETE FROM dbo.FRPDVD
WHERE IDFRPDVD = @Id;
SELECT @@ROWCOUNT;";
try
{
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
var blockers = ReferenceDirectorySqlHelpers.LoadDeleteBlockersFromForeignKeys(connection, "FRPDVD", id);
if (blockers.Count > 0)
{
return new DirectoryDeleteResult
{
IsDeleted = false,
WarningMessage = ReferenceDirectorySqlHelpers.CreateDeleteBlockedMessage("FRPDVD", blockers)
};
}
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись FRPDVD для удаления не найдена.");
}
}
return new DirectoryDeleteResult
{
IsDeleted = true
};
}
}
catch (SqlException ex) when (ex.Number == 547)
{
return new DirectoryDeleteResult
{
IsDeleted = false,
WarningMessage = ReferenceDirectorySqlHelpers.CreateDeleteBlockedMessage("FRPDVD", ex)
};
}
}
public IReadOnlyList<FrpdDirectoryItem> LoadFrpdItems()
{
const string sql = @"
SELECT
fr.IDFRPD AS Id,
fr.IDFRPDR AS ParentId,
parent.NMFRPD AS ParentName,
fr.NMFRPD AS Name,
fr.KDFRPDLC AS LocalCode,
fr.FRPDGUID AS Guid,
fr.DTSZFRPD AS CreatedOn,
fr.DTLKFRPD AS LiquidatedOn,
ISNULL(
STUFF((
SELECT ', ' + activity.ActivityName
FROM
(
SELECT DISTINCT sp.NMVDDO AS ActivityName
FROM dbo.FRPDVD link
JOIN dbo.SPVDDO sp ON sp.IDSPVDDO = link.IDSPVDDO
WHERE link.IDFRPD = fr.IDFRPD
) activity
ORDER BY activity.ActivityName
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 2, ''),
''
) AS ActivityNames
FROM dbo.FRPD fr
LEFT JOIN dbo.FRPD parent ON parent.IDFRPD = fr.IDFRPDR
ORDER BY fr.NMFRPD, fr.IDFRPD;";
var items = new List<FrpdDirectoryItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new FrpdDirectoryItem
{
ActivityNames = ReferenceDirectorySqlHelpers.GetString(reader, "ActivityNames"),
CreatedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "CreatedOn"),
Guid = ReferenceDirectorySqlHelpers.GetString(reader, "Guid"),
Id = ReferenceDirectorySqlHelpers.GetInt32(reader, "Id"),
LiquidatedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "LiquidatedOn"),
LocalCode = ReferenceDirectorySqlHelpers.GetString(reader, "LocalCode"),
Name = ReferenceDirectorySqlHelpers.GetString(reader, "Name"),
ParentId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "ParentId"),
ParentName = ReferenceDirectorySqlHelpers.GetString(reader, "ParentName")
});
}
}
}
return items;
}
public IReadOnlyList<DirectoryLookupItem> LoadFrpdReferences()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
fr.IDFRPD AS Id,
fr.NMFRPD AS Name
FROM dbo.FRPD fr
ORDER BY fr.NMFRPD, fr.IDFRPD;");
}
public IReadOnlyList<FrpdvdDirectoryItem> LoadFrpdvdItems(int frpdId)
{
const string sql = @"
SELECT
link.IDFRPDVD AS Id,
link.IDFRPD AS FrpdId,
link.IDSPVDDO AS ActivityId,
sp.NMVDDO AS ActivityName
FROM dbo.FRPDVD link
JOIN dbo.SPVDDO sp ON sp.IDSPVDDO = link.IDSPVDDO
WHERE link.IDFRPD = @FrpdId
ORDER BY sp.NMVDDO, link.IDFRPDVD;";
var items = new List<FrpdvdDirectoryItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@FrpdId", SqlDbType.Int).Value = frpdId;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new FrpdvdDirectoryItem
{
ActivityId = ReferenceDirectorySqlHelpers.GetInt32(reader, "ActivityId"),
ActivityName = ReferenceDirectorySqlHelpers.GetString(reader, "ActivityName"),
FrpdId = ReferenceDirectorySqlHelpers.GetInt32(reader, "FrpdId"),
Id = ReferenceDirectorySqlHelpers.GetInt32(reader, "Id")
});
}
}
}
return items;
}
public IReadOnlyList<DirectoryLookupItem> LoadSpvddoReferences()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
sp.IDSPVDDO AS Id,
sp.NMVDDO AS Name
FROM dbo.SPVDDO sp
ORDER BY sp.NMVDDO, sp.IDSPVDDO;");
}
public void UpdateFrpdItem(FrpdDirectoryItem item)
{
var normalizedItem = NormalizeFrpdItem(item);
if (normalizedItem.Id <= 0)
{
throw new InvalidOperationException("Не выбрана запись FRPD для изменения.");
}
const string sql = @"
UPDATE dbo.FRPD
SET IDFRPDR = @ParentId,
NMFRPD = @Name,
KDFRPDLC = @LocalCode,
FRPDGUID = @Guid,
DTSZFRPD = @CreatedOn,
DTLKFRPD = @LiquidatedOn
WHERE IDFRPD = @Id;
SELECT @@ROWCOUNT;";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureFrpdGuidIsUnique(connection, normalizedItem.Guid, normalizedItem.Id);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@ParentId", normalizedItem.ParentId);
command.Parameters.Add("@Name", SqlDbType.VarChar, FrpdDirectoryRules.NameMaxLength).Value = normalizedItem.Name;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@LocalCode", SqlDbType.VarChar, FrpdDirectoryRules.LocalCodeMaxLength, normalizedItem.LocalCode);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Guid", SqlDbType.VarChar, FrpdDirectoryRules.GuidMaxLength, normalizedItem.Guid);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@CreatedOn", normalizedItem.CreatedOn);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@LiquidatedOn", normalizedItem.LiquidatedOn);
try
{
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись FRPD для изменения не найдена.");
}
}
catch (SqlException ex) when (ReferenceDirectorySqlHelpers.IsDuplicateViolation(ex, "IX_FRPD_FRPDGUID"))
{
throw CreateFrpdDuplicateGuidException(normalizedItem.Guid);
}
}
}
public void UpdateFrpdvdItem(FrpdvdDirectoryItem item)
{
var normalizedItem = NormalizeFrpdvdItem(item);
if (normalizedItem.Id <= 0)
{
throw new InvalidOperationException("Не выбрана запись FRPDVD для изменения.");
}
const string sql = @"
UPDATE dbo.FRPDVD
SET IDSPVDDO = @ActivityId
WHERE IDFRPDVD = @Id;
SELECT @@ROWCOUNT;";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureFrpdvdIsUnique(connection, normalizedItem.FrpdId, normalizedItem.ActivityId, normalizedItem.Id);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
command.Parameters.Add("@ActivityId", SqlDbType.Int).Value = normalizedItem.ActivityId;
try
{
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись FRPDVD для изменения не найдена.");
}
}
catch (SqlException ex) when (ReferenceDirectorySqlHelpers.IsDuplicateViolation(ex, "XAK1FRPDVD"))
{
throw CreateFrpdvdDuplicateException();
}
}
}
private static InvalidOperationException CreateFrpdDuplicateGuidException(string guid)
{
return new InvalidOperationException(string.Format("GUID подразделения \"{0}\" уже существует в справочнике.", guid));
}
private static InvalidOperationException CreateFrpdvdDuplicateException()
{
return new InvalidOperationException("Выбранный вид деятельности уже существует у этой организации/подразделения.");
}
private static void EnsureFrpdGuidIsUnique(SqlConnection connection, string guid, int? excludeId)
{
if (string.IsNullOrWhiteSpace(guid))
{
return;
}
const string sql = @"
SELECT COUNT(1)
FROM dbo.FRPD
WHERE FRPDGUID = @Guid
AND (@ExcludeId IS NULL OR IDFRPD <> @ExcludeId);";
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Guid", SqlDbType.VarChar, FrpdDirectoryRules.GuidMaxLength).Value = guid;
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@ExcludeId", excludeId);
if (Convert.ToInt32(command.ExecuteScalar()) > 0)
{
throw CreateFrpdDuplicateGuidException(guid);
}
}
}
private static void EnsureFrpdvdIsUnique(SqlConnection connection, int frpdId, int activityId, int? excludeId)
{
const string sql = @"
SELECT COUNT(1)
FROM dbo.FRPDVD
WHERE IDFRPD = @FrpdId
AND IDSPVDDO = @ActivityId
AND (@ExcludeId IS NULL OR IDFRPDVD <> @ExcludeId);";
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@FrpdId", SqlDbType.Int).Value = frpdId;
command.Parameters.Add("@ActivityId", SqlDbType.Int).Value = activityId;
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@ExcludeId", excludeId);
if (Convert.ToInt32(command.ExecuteScalar()) > 0)
{
throw CreateFrpdvdDuplicateException();
}
}
}
private static string NormalizeRequired(string value, int maxLength, string fieldDisplayName)
{
var normalizedValue = string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
if (normalizedValue.Length == 0)
{
throw new InvalidOperationException(string.Format("Укажите {0}.", fieldDisplayName));
}
if (normalizedValue.Length > maxLength)
{
throw new InvalidOperationException(string.Format("{0} не должно превышать {1} символов.", fieldDisplayName, maxLength));
}
return normalizedValue;
}
private static string NormalizeNullable(string value, int maxLength, string fieldDisplayName)
{
var normalizedValue = string.IsNullOrWhiteSpace(value) ? null : value.Trim();
if (normalizedValue != null && normalizedValue.Length > maxLength)
{
throw new InvalidOperationException(string.Format("{0} не должно превышать {1} символов.", fieldDisplayName, maxLength));
}
return normalizedValue;
}
private static FrpdDirectoryItem NormalizeFrpdItem(FrpdDirectoryItem item)
{
if (item == null)
{
throw new InvalidOperationException("Не передана запись FRPD.");
}
var normalizedItem = new FrpdDirectoryItem
{
CreatedOn = item.CreatedOn,
Guid = NormalizeNullable(item.Guid, FrpdDirectoryRules.GuidMaxLength, "GUID подразделения"),
Id = item.Id,
LiquidatedOn = item.LiquidatedOn,
LocalCode = NormalizeNullable(item.LocalCode, FrpdDirectoryRules.LocalCodeMaxLength, "Локальный код организации/подразделения"),
Name = NormalizeRequired(item.Name, FrpdDirectoryRules.NameMaxLength, "организацию/подразделение"),
ParentId = item.ParentId.HasValue && item.ParentId.Value > 0 ? item.ParentId : null
};
if (normalizedItem.Id > 0
&& normalizedItem.ParentId.HasValue
&& normalizedItem.ParentId.Value == normalizedItem.Id)
{
throw new InvalidOperationException("Организация/подразделение не может ссылаться на себя как на родительскую запись.");
}
return normalizedItem;
}
private static FrpdvdDirectoryItem NormalizeFrpdvdItem(FrpdvdDirectoryItem item)
{
if (item == null)
{
throw new InvalidOperationException("Не передана запись FRPDVD.");
}
if (item.FrpdId <= 0)
{
throw new InvalidOperationException("Не выбрана организация/подразделение для вида деятельности.");
}
if (item.ActivityId <= 0)
{
throw new InvalidOperationException("Укажите вид деятельности организации.");
}
return new FrpdvdDirectoryItem
{
ActivityId = item.ActivityId,
FrpdId = item.FrpdId,
Id = item.Id
};
}
}
}

View File

@@ -0,0 +1,174 @@
<Window x:Class="XLAB2.FrpdDirectoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Организации и подразделения"
Height="820"
Width="1280"
MinHeight="680"
MinWidth="1040"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="2.2*" />
<RowDefinition Height="1.5*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0"
Margin="0,0,0,12">
<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="Поиск " />
<TextBox Width="360"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DockPanel>
<GroupBox Grid.Row="1"
Header="Организации и подразделения">
<DataGrid ItemsSource="{Binding FrpdItems}"
SelectedItem="{Binding SelectedFrpd, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddFrpdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить"
Command="{Binding EditFrpdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить"
Command="{Binding DeleteFrpdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Наименование" Width="*" Binding="{Binding Name}" />
<DataGridTextColumn Header="Локальный код" Width="150" Binding="{Binding LocalCode}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<GroupBox Grid.Row="2"
Margin="0,12,0,0"
Header="Виды деятельности подразделения">
<DataGrid ItemsSource="{Binding FrpdvdItems}"
SelectedItem="{Binding SelectedFrpdvd, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddFrpdvdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить"
Command="{Binding EditFrpdvdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить"
Command="{Binding DeleteFrpdvdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Вид деятельности" Width="*" Binding="{Binding ActivityName}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<TextBlock Grid.Row="3"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<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 XLAB2
{
public partial class FrpdDirectoryWindow : Window
{
private readonly FrpdDirectoryWindowViewModel _viewModel;
public FrpdDirectoryWindow()
{
InitializeComponent();
_viewModel = new FrpdDirectoryWindowViewModel(new FrpdDirectoryService(), new FrpdDirectoryDialogService(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,476 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class FrpdDirectoryWindowViewModel : ObservableObject
{
private readonly IFrpdDirectoryDialogService _dialogService;
private readonly FrpdDirectoryService _service;
private List<FrpdDirectoryItem> _frpdCache;
private bool _isBusy;
private string _searchText;
private FrpdDirectoryItem _selectedFrpd;
private FrpdvdDirectoryItem _selectedFrpdvd;
private string _statusText;
public FrpdDirectoryWindowViewModel(FrpdDirectoryService service, IFrpdDirectoryDialogService dialogService)
{
_service = service;
_dialogService = dialogService;
_frpdCache = new List<FrpdDirectoryItem>();
FrpdItems = new ObservableCollection<FrpdDirectoryItem>();
FrpdvdItems = new ObservableCollection<FrpdvdDirectoryItem>();
AddFrpdCommand = new RelayCommand(delegate { AddFrpdAsync(); }, delegate { return !IsBusy; });
EditFrpdCommand = new RelayCommand(delegate { EditFrpdAsync(); }, delegate { return !IsBusy && SelectedFrpd != null; });
DeleteFrpdCommand = new RelayCommand(delegate { DeleteFrpdAsync(); }, delegate { return !IsBusy && SelectedFrpd != null; });
AddFrpdvdCommand = new RelayCommand(delegate { AddFrpdvdAsync(); }, delegate { return !IsBusy && SelectedFrpd != null; });
EditFrpdvdCommand = new RelayCommand(delegate { EditFrpdvdAsync(); }, delegate { return !IsBusy && SelectedFrpdvd != null; });
DeleteFrpdvdCommand = new RelayCommand(delegate { DeleteFrpdvdAsync(); }, delegate { return !IsBusy && SelectedFrpdvd != null; });
RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });
UpdateStatus();
}
public ICommand AddFrpdCommand { get; private set; }
public ICommand AddFrpdvdCommand { get; private set; }
public ICommand DeleteFrpdCommand { get; private set; }
public ICommand DeleteFrpdvdCommand { get; private set; }
public ICommand EditFrpdCommand { get; private set; }
public ICommand EditFrpdvdCommand { get; private set; }
public ObservableCollection<FrpdDirectoryItem> FrpdItems { get; private set; }
public ObservableCollection<FrpdvdDirectoryItem> FrpdvdItems { get; private set; }
public ICommand RefreshCommand { get; private set; }
public bool IsBusy
{
get { return _isBusy; }
private set
{
if (SetProperty(ref _isBusy, value))
{
RaiseCommandStates();
}
}
}
public string SearchText
{
get { return _searchText; }
set
{
if (SetProperty(ref _searchText, value))
{
ApplySearchFilter();
UpdateStatus();
}
}
}
public FrpdDirectoryItem SelectedFrpd
{
get { return _selectedFrpd; }
set
{
if (SetProperty(ref _selectedFrpd, value))
{
RaiseCommandStates();
LoadFrpdvdForSelection();
UpdateStatus();
}
}
}
public FrpdvdDirectoryItem SelectedFrpdvd
{
get { return _selectedFrpdvd; }
set
{
if (SetProperty(ref _selectedFrpdvd, value))
{
RaiseCommandStates();
UpdateStatus();
}
}
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public async Task InitializeAsync()
{
await ExecuteBusyOperationAsync(async delegate { await RefreshFrpdCoreAsync(null, null); });
}
private void AddFrpdAsync()
{
var result = _dialogService.ShowFrpdEditDialog(new FrpdDirectoryItem(), true, _frpdCache.ToList(), _service);
if (result == null)
{
return;
}
RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _service.AddFrpdItem(result); });
await RefreshFrpdCoreAsync(createdId, null);
_dialogService.ShowInfo("Запись FRPD добавлена.");
});
}
private void AddFrpdvdAsync()
{
if (SelectedFrpd == null)
{
return;
}
var result = _dialogService.ShowFrpdvdEditDialog(new FrpdvdDirectoryItem { FrpdId = SelectedFrpd.Id }, true, FrpdvdItems.ToList(), _service);
if (result == null)
{
return;
}
RunMutationOperation(async delegate
{
var createdId = await Task.Run(delegate { return _service.AddFrpdvdItem(result); });
await RefreshFrpdCoreAsync(result.FrpdId, createdId);
_dialogService.ShowInfo("Запись FRPDVD добавлена.");
});
}
private void ApplyFrpdFilter(int? preferredId)
{
var filteredItems = _frpdCache.Where(delegate(FrpdDirectoryItem item) { return MatchesSearch(item); }).ToList();
FrpdItems.Clear();
foreach (var item in filteredItems)
{
FrpdItems.Add(item);
}
SelectedFrpd = preferredId.HasValue
? FrpdItems.FirstOrDefault(delegate(FrpdDirectoryItem item) { return item.Id == preferredId.Value; })
: FrpdItems.FirstOrDefault();
}
private void ApplySearchFilter()
{
if (!IsBusy)
{
ApplyFrpdFilter(SelectedFrpd == null ? (int?)null : SelectedFrpd.Id);
}
}
private static FrpdDirectoryItem CloneFrpd(FrpdDirectoryItem source)
{
return new FrpdDirectoryItem
{
CreatedOn = source.CreatedOn,
Guid = source.Guid,
Id = source.Id,
LiquidatedOn = source.LiquidatedOn,
LocalCode = source.LocalCode,
Name = source.Name,
ParentId = source.ParentId,
ParentName = source.ParentName
};
}
private static FrpdvdDirectoryItem CloneFrpdvd(FrpdvdDirectoryItem source)
{
return new FrpdvdDirectoryItem
{
ActivityId = source.ActivityId,
ActivityName = source.ActivityName,
FrpdId = source.FrpdId,
Id = source.Id
};
}
private void DeleteFrpdAsync()
{
if (SelectedFrpd == null)
{
return;
}
var selected = SelectedFrpd;
if (!_dialogService.Confirm(string.Format("Удалить организацию/подразделение \"{0}\"?", selected.Name)))
{
return;
}
RunMutationOperation(async delegate
{
var result = await Task.Run(delegate { return _service.DeleteFrpdItem(selected.Id); });
if (!result.IsDeleted)
{
_dialogService.ShowWarning(result.WarningMessage);
return;
}
await RefreshFrpdCoreAsync(null, null);
_dialogService.ShowInfo("Запись удалена.");
});
}
private void DeleteFrpdvdAsync()
{
if (SelectedFrpdvd == null)
{
return;
}
var selected = SelectedFrpdvd;
if (!_dialogService.Confirm(string.Format("Удалить вид деятельности \"{0}\"?", selected.ActivityName)))
{
return;
}
RunMutationOperation(async delegate
{
var result = await Task.Run(delegate { return _service.DeleteFrpdvdItem(selected.Id); });
if (!result.IsDeleted)
{
_dialogService.ShowWarning(result.WarningMessage);
return;
}
await RefreshFrpdCoreAsync(selected.FrpdId, null);
_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 void EditFrpdAsync()
{
if (SelectedFrpd == null)
{
return;
}
var result = _dialogService.ShowFrpdEditDialog(CloneFrpd(SelectedFrpd), false, _frpdCache.ToList(), _service);
if (result == null)
{
return;
}
RunMutationOperation(async delegate
{
await Task.Run(delegate { _service.UpdateFrpdItem(result); });
await RefreshFrpdCoreAsync(result.Id, SelectedFrpdvd == null ? (int?)null : SelectedFrpdvd.Id);
_dialogService.ShowInfo("Запись обновлена.");
});
}
private void EditFrpdvdAsync()
{
if (SelectedFrpdvd == null)
{
return;
}
var result = _dialogService.ShowFrpdvdEditDialog(CloneFrpdvd(SelectedFrpdvd), false, FrpdvdItems.ToList(), _service);
if (result == null)
{
return;
}
RunMutationOperation(async delegate
{
await Task.Run(delegate { _service.UpdateFrpdvdItem(result); });
await RefreshFrpdCoreAsync(result.FrpdId, result.Id);
_dialogService.ShowInfo("Запись обновлена.");
});
}
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 LoadFrpdvdForSelection()
{
if (!IsBusy)
{
RunBusyOperation(async delegate
{
if (SelectedFrpd == null)
{
FrpdvdItems.Clear();
SelectedFrpdvd = null;
UpdateStatus();
return;
}
await RefreshFrpdvdCoreAsync(SelectedFrpd.Id, null);
});
}
}
private bool MatchesSearch(FrpdDirectoryItem item)
{
if (item == null)
{
return false;
}
var tokens = GetSearchTokens();
if (tokens.Length == 0)
{
return true;
}
var haystack = string.Join(
" ",
new[]
{
item.Id.ToString(),
item.Name,
item.ParentName,
item.LocalCode,
item.Guid,
item.ActivityNames
}
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); }))
.ToUpperInvariant();
return tokens.All(delegate(string token) { return haystack.IndexOf(token, StringComparison.Ordinal) >= 0; });
}
private async Task RefreshFrpdCoreAsync(int? frpdIdToSelect, int? frpdvdIdToSelect)
{
var currentFrpdId = SelectedFrpd == null ? (int?)null : SelectedFrpd.Id;
_frpdCache = (await Task.Run(delegate { return _service.LoadFrpdItems(); })).ToList();
ApplyFrpdFilter(frpdIdToSelect.HasValue ? frpdIdToSelect : currentFrpdId);
if (SelectedFrpd == null)
{
FrpdvdItems.Clear();
SelectedFrpdvd = null;
UpdateStatus();
return;
}
await RefreshFrpdvdCoreAsync(SelectedFrpd.Id, frpdvdIdToSelect);
UpdateStatus();
}
private async Task RefreshFrpdvdCoreAsync(int frpdId, int? frpdvdIdToSelect)
{
var items = await Task.Run(delegate { return _service.LoadFrpdvdItems(frpdId); });
FrpdvdItems.Clear();
foreach (var item in items)
{
FrpdvdItems.Add(item);
}
SelectedFrpdvd = frpdvdIdToSelect.HasValue
? FrpdvdItems.FirstOrDefault(delegate(FrpdvdDirectoryItem item) { return item.Id == frpdvdIdToSelect.Value; })
: FrpdvdItems.FirstOrDefault();
UpdateStatus();
}
private void RaiseCommandStates()
{
((RelayCommand)AddFrpdCommand).RaiseCanExecuteChanged();
((RelayCommand)EditFrpdCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteFrpdCommand).RaiseCanExecuteChanged();
((RelayCommand)AddFrpdvdCommand).RaiseCanExecuteChanged();
((RelayCommand)EditFrpdvdCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteFrpdvdCommand).RaiseCanExecuteChanged();
((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
}
private void RefreshAsync()
{
RunBusyOperation(async delegate { await RefreshFrpdCoreAsync(SelectedFrpd == null ? (int?)null : SelectedFrpd.Id, SelectedFrpdvd == null ? (int?)null : SelectedFrpdvd.Id); });
}
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}Подразделений: {1}/{2}. Видов деятельности: {3}.",
string.IsNullOrWhiteSpace(searchText) ? string.Empty : string.Format("Поиск: \"{0}\". ", searchText),
FrpdItems.Count,
_frpdCache.Count,
FrpdvdItems.Count);
}
}
}

48
XLAB2/FrpdEditWindow.xaml Normal file
View File

@@ -0,0 +1,48 @@
<Window x:Class="XLAB2.FrpdEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="220"
Width="680"
MinHeight="220"
MinWidth="620"
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" />
</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 ParentItems}" SelectedValue="{Binding ParentId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />-->
<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.Column="0" Margin="0,0,12,8" VerticalAlignment="Center" Text="Локальный код" />
<TextBox Grid.Row="2" Grid.Column="1" Margin="0,0,0,8" Text="{Binding LocalCode, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<TextBlock Grid.Row="1" Margin="0,12,0,0" Foreground="Firebrick" Text="{Binding ValidationMessage}" />
<StackPanel Grid.Row="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 XLAB2
{
public partial class FrpdEditWindow : Window
{
internal FrpdEditWindow(FrpdEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class FrpdEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<FrpdDirectoryItem> _existingItems;
private string _guid;
private string _localCode;
private string _name;
private int _parentId;
private string _validationMessage;
public FrpdEditWindowViewModel(FrpdDirectoryItem seed, bool isNew, IReadOnlyList<FrpdDirectoryItem> existingItems, FrpdDirectoryService service)
{
var source = seed ?? new FrpdDirectoryItem();
_existingItems = existingItems ?? Array.Empty<FrpdDirectoryItem>();
Id = source.Id;
IsNew = isNew;
ParentItems = new[] { new DirectoryLookupItem { Id = 0, Name = string.Empty } }
.Concat((service.LoadFrpdReferences() ?? Array.Empty<DirectoryLookupItem>()).Where(delegate(DirectoryLookupItem item) { return item.Id != Id; }))
.ToList();
ParentId = source.ParentId.HasValue ? source.ParentId.Value : 0;
Name = source.Name ?? string.Empty;
LocalCode = source.LocalCode ?? string.Empty;
Guid = source.Guid ?? string.Empty;
CreatedOn = source.CreatedOn;
LiquidatedOn = source.LiquidatedOn;
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 DateTime? CreatedOn { get; set; }
public string Guid
{
get { return _guid; }
set { SetProperty(ref _guid, value); }
}
public Visibility GuidFieldVisibility
{
get { return IsNew ? Visibility.Collapsed : Visibility.Visible; }
}
public int Id { get; private set; }
public bool IsNew { get; private set; }
public DateTime? LiquidatedOn { get; set; }
public string LocalCode
{
get { return _localCode; }
set { SetProperty(ref _localCode, value); }
}
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
public int ParentId
{
get { return _parentId; }
set { SetProperty(ref _parentId, value); }
}
public IReadOnlyList<DirectoryLookupItem> ParentItems { get; private set; }
public string Title
{
get { return IsNew ? "Новая организация/подразделение" : "Редактирование организации/подразделения"; }
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public FrpdDirectoryItem ToResult()
{
return new FrpdDirectoryItem
{
CreatedOn = CreatedOn,
Guid = string.IsNullOrWhiteSpace(Guid) ? null : Guid.Trim(),
Id = Id,
LiquidatedOn = LiquidatedOn,
LocalCode = string.IsNullOrWhiteSpace(LocalCode) ? null : LocalCode.Trim(),
Name = string.IsNullOrWhiteSpace(Name) ? string.Empty : Name.Trim(),
ParentId = ParentId > 0 ? (int?)ParentId : null
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
var normalizedName = string.IsNullOrWhiteSpace(Name) ? string.Empty : Name.Trim();
var normalizedLocalCode = string.IsNullOrWhiteSpace(LocalCode) ? null : LocalCode.Trim();
var normalizedGuid = string.IsNullOrWhiteSpace(Guid) ? null : Guid.Trim();
if (normalizedName.Length == 0)
{
ValidationMessage = "Укажите организацию/подразделение.";
return;
}
if (normalizedName.Length > FrpdDirectoryRules.NameMaxLength)
{
ValidationMessage = string.Format("Организация/подразделение не должна превышать {0} символов.", FrpdDirectoryRules.NameMaxLength);
return;
}
if (normalizedLocalCode != null && normalizedLocalCode.Length > FrpdDirectoryRules.LocalCodeMaxLength)
{
ValidationMessage = string.Format("Локальный код не должен превышать {0} символов.", FrpdDirectoryRules.LocalCodeMaxLength);
return;
}
if (normalizedGuid != null && normalizedGuid.Length > FrpdDirectoryRules.GuidMaxLength)
{
ValidationMessage = string.Format("GUID подразделения не должен превышать {0} символов.", FrpdDirectoryRules.GuidMaxLength);
return;
}
if (ParentId == Id && Id > 0)
{
ValidationMessage = "Нельзя выбрать эту же запись как родительскую.";
return;
}
var duplicateGuid = _existingItems.FirstOrDefault(delegate(FrpdDirectoryItem item)
{
return item != null
&& item.Id != Id
&& !string.IsNullOrWhiteSpace(item.Guid)
&& normalizedGuid != null
&& string.Equals(item.Guid.Trim(), normalizedGuid, StringComparison.OrdinalIgnoreCase);
});
if (duplicateGuid != null)
{
ValidationMessage = string.Format("GUID подразделения \"{0}\" уже существует в справочнике.", normalizedGuid);
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
}
}

View File

@@ -0,0 +1,32 @@
<Window x:Class="XLAB2.FrpdvdEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="210"
Width="560"
MinHeight="200"
MinWidth="520"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<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 ActivityItems}" SelectedValue="{Binding ActivityId}" SelectedValuePath="Id" DisplayMemberPath="Name" IsTextSearchEnabled="True" />
<TextBlock Grid.Row="1" Grid.ColumnSpan="2" Margin="0,8,0,0" Foreground="Firebrick" Text="{Binding ValidationMessage}" />
<StackPanel Grid.Row="2" 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 XLAB2
{
public partial class FrpdvdEditWindow : Window
{
internal FrpdvdEditWindow(FrpdvdEditWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
viewModel.CloseRequested += ViewModelOnCloseRequested;
}
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{
DialogResult = dialogResult;
Close();
}
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class FrpdvdEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<FrpdvdDirectoryItem> _existingItems;
private int _activityId;
private string _validationMessage;
public FrpdvdEditWindowViewModel(FrpdvdDirectoryItem seed, bool isNew, IReadOnlyList<FrpdvdDirectoryItem> existingItems, FrpdDirectoryService service)
{
var source = seed ?? new FrpdvdDirectoryItem();
_existingItems = existingItems ?? Array.Empty<FrpdvdDirectoryItem>();
Id = source.Id;
FrpdId = source.FrpdId;
IsNew = isNew;
ActivityItems = service.LoadSpvddoReferences();
ActivityId = source.ActivityId;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public int ActivityId
{
get { return _activityId; }
set { SetProperty(ref _activityId, value); }
}
public IReadOnlyList<DirectoryLookupItem> ActivityItems { get; private set; }
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public int FrpdId { get; private set; }
public int Id { get; private set; }
public bool IsNew { get; private set; }
public string Title
{
get { return IsNew ? "Новый вид деятельности организации" : "Редактирование вида деятельности организации"; }
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public FrpdvdDirectoryItem ToResult()
{
return new FrpdvdDirectoryItem
{
ActivityId = ActivityId,
FrpdId = FrpdId,
Id = Id
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
if (ActivityId <= 0)
{
ValidationMessage = "Укажите вид деятельности организации.";
return;
}
var duplicate = _existingItems.FirstOrDefault(delegate(FrpdvdDirectoryItem item)
{
return item != null
&& item.Id != Id
&& item.ActivityId == ActivityId;
});
if (duplicate != null)
{
ValidationMessage = "Выбранный вид деятельности уже существует у этой организации/подразделения.";
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
}
}

View File

@@ -0,0 +1,73 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Threading;
namespace XLAB2.Infrastructure;
internal static class DatabaseConfiguration
{
private static readonly Lazy<DatabaseOptions> Options = new(LoadOptions, LazyThreadSafetyMode.ExecutionAndPublication);
public static DatabaseOptions Current => Options.Value;
private static DatabaseOptions LoadOptions()
{
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables(prefix: "XLAB2_")
.Build();
var options = configuration.GetSection("Database").Get<DatabaseOptions>() ?? new DatabaseOptions();
Validate(options);
return options;
}
private static void Validate(DatabaseOptions options)
{
if (string.IsNullOrWhiteSpace(options.Server))
{
throw new InvalidOperationException("Database:Server is required.");
}
if (string.IsNullOrWhiteSpace(options.Database))
{
throw new InvalidOperationException("Database:Database is required.");
}
if (options.ConnectTimeoutSeconds <= 0)
{
throw new InvalidOperationException("Database:ConnectTimeoutSeconds must be greater than zero.");
}
if (options.CommandTimeoutSeconds <= 0)
{
throw new InvalidOperationException("Database:CommandTimeoutSeconds must be greater than zero.");
}
if (options.MinPoolSize < 0)
{
throw new InvalidOperationException("Database:MinPoolSize cannot be negative.");
}
if (options.MaxPoolSize <= 0)
{
throw new InvalidOperationException("Database:MaxPoolSize must be greater than zero.");
}
if (options.MinPoolSize > options.MaxPoolSize)
{
throw new InvalidOperationException("Database:MinPoolSize cannot be greater than Database:MaxPoolSize.");
}
if (options.ConnectRetryCount < 0)
{
throw new InvalidOperationException("Database:ConnectRetryCount cannot be negative.");
}
if (options.ConnectRetryIntervalSeconds < 1)
{
throw new InvalidOperationException("Database:ConnectRetryIntervalSeconds must be greater than zero.");
}
}
}

View File

@@ -0,0 +1,32 @@
namespace XLAB2.Infrastructure;
internal sealed class DatabaseOptions
{
public string ApplicationName { get; set; } = "XLAB2";
public int CommandTimeoutSeconds { get; set; } = 60;
public int ConnectRetryCount { get; set; } = 3;
public int ConnectRetryIntervalSeconds { get; set; } = 10;
public int ConnectTimeoutSeconds { get; set; } = 15;
public string Database { get; set; } = "ASUMS";
public bool Encrypt { get; set; } = false;
public bool IntegratedSecurity { get; set; } = true;
public bool MultipleActiveResultSets { get; set; } = true;
public bool Pooling { get; set; } = true;
public int MaxPoolSize { get; set; } = 100;
public int MinPoolSize { get; set; } = 0;
public string Server { get; set; } = @"SEVENHILL\SQLEXPRESS";
public bool TrustServerCertificate { get; set; } = true;
}

Some files were not shown because too many files have changed in this diff Show More