Compare commits
14 Commits
ff133e0215
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2b4762702 | ||
|
|
1b20b39aca | ||
|
|
22ed7d978a | ||
|
|
db15503315 | ||
|
|
339b9ab8aa | ||
|
|
054d10cb6d | ||
|
|
d46172beb9 | ||
|
|
74d793948e | ||
|
|
bf9f54f91c | ||
|
|
d54f5b8e22 | ||
|
|
234dac7d32 | ||
|
|
7bbca6ba55 | ||
|
|
a47a7a5a3b | ||
|
|
ce3a3f02d2 |
BIN
ClosePsv.docx
BIN
ClosePsv.docx
Binary file not shown.
BIN
OpenPsv.docx
BIN
OpenPsv.docx
Binary file not shown.
34
XLAB.sln
34
XLAB.sln
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
222
XLAB/App.xaml
222
XLAB/App.xaml
@@ -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" />
|
||||||
|
|||||||
58
XLAB/EkzDirectoryDialogService.cs
Normal file
58
XLAB/EkzDirectoryDialogService.cs
Normal 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
138
XLAB/EkzDirectoryModels.cs
Normal 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
858
XLAB/EkzDirectoryService.cs
Normal 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("Экземпляр с таким типоразмером, владельцем и заводским номером уже существует.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
176
XLAB/EkzDirectoryWindow.xaml
Normal file
176
XLAB/EkzDirectoryWindow.xaml
Normal 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>
|
||||||
33
XLAB/EkzDirectoryWindow.xaml.cs
Normal file
33
XLAB/EkzDirectoryWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
568
XLAB/EkzDirectoryWindowViewModel.cs
Normal file
568
XLAB/EkzDirectoryWindowViewModel.cs
Normal 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
109
XLAB/EkzEditWindow.xaml
Normal 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>
|
||||||
20
XLAB/EkzEditWindow.xaml.cs
Normal file
20
XLAB/EkzEditWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
185
XLAB/EkzEditWindowViewModel.cs
Normal file
185
XLAB/EkzEditWindowViewModel.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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}" />
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
70
XLAB/PlanningDialogService.cs
Normal file
70
XLAB/PlanningDialogService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
111
XLAB/PlanningEditWindow.xaml
Normal file
111
XLAB/PlanningEditWindow.xaml
Normal 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>
|
||||||
20
XLAB/PlanningEditWindow.xaml.cs
Normal file
20
XLAB/PlanningEditWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
250
XLAB/PlanningEditWindowViewModel.cs
Normal file
250
XLAB/PlanningEditWindowViewModel.cs
Normal 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
157
XLAB/PlanningModels.cs
Normal 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
394
XLAB/PlanningService.cs
Normal 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
161
XLAB/PlanningWindow.xaml
Normal 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>
|
||||||
33
XLAB/PlanningWindow.xaml.cs
Normal file
33
XLAB/PlanningWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
546
XLAB/PlanningWindowViewModel.cs
Normal file
546
XLAB/PlanningWindowViewModel.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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="Закрыть" />
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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="Закрыть" />
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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}" />
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
79
XLAB/VerificationReportsModels.cs
Normal file
79
XLAB/VerificationReportsModels.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
228
XLAB/VerificationReportsService.cs
Normal file
228
XLAB/VerificationReportsService.cs
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
238
XLAB/VerificationReportsWindow.xaml
Normal file
238
XLAB/VerificationReportsWindow.xaml
Normal 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>
|
||||||
21
XLAB/VerificationReportsWindow.xaml.cs
Normal file
21
XLAB/VerificationReportsWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
249
XLAB/VerificationReportsWindowViewModel.cs
Normal file
249
XLAB/VerificationReportsWindowViewModel.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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">
|
||||||
|
|||||||
118
XLAB2/AccountingBookDirectoryWindow.xaml
Normal file
118
XLAB2/AccountingBookDirectoryWindow.xaml
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<Window x:Class="XLAB2.AccountingBookDirectoryWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="Книги учета"
|
||||||
|
Height="680"
|
||||||
|
Width="980"
|
||||||
|
MinHeight="520"
|
||||||
|
MinWidth="820"
|
||||||
|
Loaded="Window_Loaded"
|
||||||
|
WindowStartupLocation="CenterOwner">
|
||||||
|
<Grid Margin="12">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<DockPanel Grid.Row="0"
|
||||||
|
Margin="0,0,0,8">
|
||||||
|
<StackPanel DockPanel.Dock="Left"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<TextBlock Width="210"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="Поиск по ключу или книге учета" />
|
||||||
|
<TextBox Width="320"
|
||||||
|
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel DockPanel.Dock="Right"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<Button Width="110"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Command="{Binding RefreshCommand}"
|
||||||
|
Content="Обновить" />
|
||||||
|
</StackPanel>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<DataGrid Grid.Row="1"
|
||||||
|
ItemsSource="{Binding ItemsView}"
|
||||||
|
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
IsReadOnly="True"
|
||||||
|
HeadersVisibility="Column">
|
||||||
|
<DataGrid.ContextMenu>
|
||||||
|
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||||||
|
<MenuItem Header="Добавить"
|
||||||
|
Command="{Binding AddCommand}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="14" Height="14">
|
||||||
|
<Grid Width="16" Height="16">
|
||||||
|
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
|
||||||
|
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
|
||||||
|
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
|
||||||
|
</Grid>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Изменить"
|
||||||
|
Command="{Binding EditCommand}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="14" Height="14">
|
||||||
|
<Canvas Width="16" Height="16">
|
||||||
|
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
|
||||||
|
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Удалить"
|
||||||
|
Command="{Binding DeleteCommand}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Viewbox Width="14" Height="14">
|
||||||
|
<Canvas Width="16" Height="16">
|
||||||
|
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
|
||||||
|
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
|
||||||
|
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
|
||||||
|
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
|
||||||
|
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
</ContextMenu>
|
||||||
|
</DataGrid.ContextMenu>
|
||||||
|
<DataGrid.RowStyle>
|
||||||
|
<Style TargetType="DataGridRow">
|
||||||
|
<EventSetter Event="PreviewMouseRightButtonDown"
|
||||||
|
Handler="DataGridRow_PreviewMouseRightButtonDown" />
|
||||||
|
</Style>
|
||||||
|
</DataGrid.RowStyle>
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="Ключ"
|
||||||
|
Width="240"
|
||||||
|
Binding="{Binding Key}" />
|
||||||
|
<DataGridTextColumn Header="Номер книги учета"
|
||||||
|
Width="*"
|
||||||
|
Binding="{Binding Title}" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
Foreground="DimGray"
|
||||||
|
Text="{Binding StatusText}" />
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="3"
|
||||||
|
Margin="0,12,0,0"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<Button Width="90"
|
||||||
|
IsCancel="True"
|
||||||
|
Content="Закрыть" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
35
XLAB2/AccountingBookDirectoryWindow.xaml.cs
Normal file
35
XLAB2/AccountingBookDirectoryWindow.xaml.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace XLAB2
|
||||||
|
{
|
||||||
|
public partial class AccountingBookDirectoryWindow : Window
|
||||||
|
{
|
||||||
|
private readonly AccountingBookDirectoryWindowViewModel _viewModel;
|
||||||
|
|
||||||
|
internal AccountingBookDirectoryWindow(DocumentNumberDirectoryService documentNumberDirectoryService)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_viewModel = new AccountingBookDirectoryWindowViewModel(
|
||||||
|
documentNumberDirectoryService,
|
||||||
|
new DialogService(this, documentNumberDirectoryService));
|
||||||
|
DataContext = _viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
var row = sender as DataGridRow;
|
||||||
|
if (row != null)
|
||||||
|
{
|
||||||
|
row.IsSelected = true;
|
||||||
|
row.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_viewModel.Initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
266
XLAB2/AccountingBookDirectoryWindowViewModel.cs
Normal file
266
XLAB2/AccountingBookDirectoryWindowViewModel.cs
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace XLAB2
|
||||||
|
{
|
||||||
|
internal sealed class AccountingBookDirectoryWindowViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly IDialogService _dialogService;
|
||||||
|
private readonly DocumentNumberDirectoryService _service;
|
||||||
|
private string _searchText;
|
||||||
|
private GroupOption _selectedItem;
|
||||||
|
private string _statusText;
|
||||||
|
|
||||||
|
public AccountingBookDirectoryWindowViewModel(DocumentNumberDirectoryService service, IDialogService dialogService)
|
||||||
|
{
|
||||||
|
_service = service ?? throw new ArgumentNullException("service");
|
||||||
|
_dialogService = dialogService ?? throw new ArgumentNullException("dialogService");
|
||||||
|
|
||||||
|
Items = new ObservableCollection<GroupOption>();
|
||||||
|
ItemsView = CollectionViewSource.GetDefaultView(Items);
|
||||||
|
ItemsView.Filter = FilterItems;
|
||||||
|
|
||||||
|
AddCommand = new RelayCommand(delegate { AddItem(); });
|
||||||
|
DeleteCommand = new RelayCommand(delegate { DeleteSelectedItem(); }, delegate { return SelectedItem != null; });
|
||||||
|
EditCommand = new RelayCommand(delegate { EditSelectedItem(); }, delegate { return SelectedItem != null; });
|
||||||
|
RefreshCommand = new RelayCommand(delegate { RefreshItems(null); });
|
||||||
|
|
||||||
|
UpdateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand AddCommand { get; private set; }
|
||||||
|
|
||||||
|
public ICommand DeleteCommand { get; private set; }
|
||||||
|
|
||||||
|
public ICommand EditCommand { get; private set; }
|
||||||
|
|
||||||
|
public ObservableCollection<GroupOption> Items { get; private set; }
|
||||||
|
|
||||||
|
public ICollectionView ItemsView { get; private set; }
|
||||||
|
|
||||||
|
public ICommand RefreshCommand { get; private set; }
|
||||||
|
|
||||||
|
public string SearchText
|
||||||
|
{
|
||||||
|
get { return _searchText; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _searchText, value))
|
||||||
|
{
|
||||||
|
ItemsView.Refresh();
|
||||||
|
UpdateStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupOption SelectedItem
|
||||||
|
{
|
||||||
|
get { return _selectedItem; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _selectedItem, value))
|
||||||
|
{
|
||||||
|
RaiseCommandStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StatusText
|
||||||
|
{
|
||||||
|
get { return _statusText; }
|
||||||
|
private set { SetProperty(ref _statusText, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
RunOperation(delegate { RefreshItems(null); }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddItem()
|
||||||
|
{
|
||||||
|
var result = _dialogService.ShowAccountingBookEditDialog(new GroupOption(), true, Items.ToList());
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RunOperation(delegate
|
||||||
|
{
|
||||||
|
var items = CloneItems(Items);
|
||||||
|
items.Add(CloneItem(result));
|
||||||
|
_service.SaveAccountingBooks(items);
|
||||||
|
RefreshItems(result.Key);
|
||||||
|
_dialogService.ShowInfo("Запись справочника добавлена.");
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Contains(string source, string searchText)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(source)
|
||||||
|
&& source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteSelectedItem()
|
||||||
|
{
|
||||||
|
if (SelectedItem == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedItem = SelectedItem;
|
||||||
|
if (!_dialogService.Confirm(string.Format("Удалить книгу учета \"{0}\"?", selectedItem.Title)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RunOperation(delegate
|
||||||
|
{
|
||||||
|
var items = CloneItems(Items);
|
||||||
|
items.RemoveAll(delegate(GroupOption item)
|
||||||
|
{
|
||||||
|
return string.Equals(item.Key, selectedItem.Key, StringComparison.OrdinalIgnoreCase);
|
||||||
|
});
|
||||||
|
|
||||||
|
_service.SaveAccountingBooks(items);
|
||||||
|
RefreshItems(null);
|
||||||
|
_dialogService.ShowInfo("Запись справочника удалена.");
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditSelectedItem()
|
||||||
|
{
|
||||||
|
if (SelectedItem == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var seed = CloneItem(SelectedItem);
|
||||||
|
var result = _dialogService.ShowAccountingBookEditDialog(seed, false, Items.ToList());
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RunOperation(delegate
|
||||||
|
{
|
||||||
|
var items = CloneItems(Items);
|
||||||
|
var index = items.FindIndex(delegate(GroupOption item)
|
||||||
|
{
|
||||||
|
return string.Equals(item.Key, seed.Key, StringComparison.OrdinalIgnoreCase);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Не удалось найти книгу учета для обновления.");
|
||||||
|
}
|
||||||
|
|
||||||
|
items[index] = CloneItem(result);
|
||||||
|
_service.SaveAccountingBooks(items);
|
||||||
|
RefreshItems(result.Key);
|
||||||
|
_dialogService.ShowInfo("Запись справочника обновлена.");
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FilterItems(object item)
|
||||||
|
{
|
||||||
|
var directoryItem = item as GroupOption;
|
||||||
|
if (directoryItem == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(SearchText))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Contains(directoryItem.Key, SearchText)
|
||||||
|
|| Contains(directoryItem.Title, SearchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseCommandStates()
|
||||||
|
{
|
||||||
|
((RelayCommand)AddCommand).RaiseCanExecuteChanged();
|
||||||
|
((RelayCommand)DeleteCommand).RaiseCanExecuteChanged();
|
||||||
|
((RelayCommand)EditCommand).RaiseCanExecuteChanged();
|
||||||
|
((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshItems(string keyToSelect)
|
||||||
|
{
|
||||||
|
var items = _service.LoadAccountingBooks();
|
||||||
|
var currentKey = string.IsNullOrWhiteSpace(keyToSelect)
|
||||||
|
? (SelectedItem == null ? null : SelectedItem.Key)
|
||||||
|
: keyToSelect;
|
||||||
|
|
||||||
|
Items.Clear();
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
Items.Add(CloneItem(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemsView.Refresh();
|
||||||
|
SelectedItem = string.IsNullOrWhiteSpace(currentKey)
|
||||||
|
? Items.FirstOrDefault()
|
||||||
|
: Items.FirstOrDefault(delegate(GroupOption item)
|
||||||
|
{
|
||||||
|
return string.Equals(item.Key, currentKey, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}) ?? Items.FirstOrDefault();
|
||||||
|
UpdateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunOperation(Action action, bool showWarningForInvalidOperation)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
if (showWarningForInvalidOperation)
|
||||||
|
{
|
||||||
|
_dialogService.ShowWarning(ex.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dialogService.ShowError(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_dialogService.ShowError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GroupOption CloneItem(GroupOption item)
|
||||||
|
{
|
||||||
|
return new GroupOption
|
||||||
|
{
|
||||||
|
Key = item == null ? string.Empty : item.Key,
|
||||||
|
Title = item == null ? string.Empty : item.Title
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<GroupOption> CloneItems(IEnumerable<GroupOption> items)
|
||||||
|
{
|
||||||
|
var result = new List<GroupOption>();
|
||||||
|
|
||||||
|
foreach (var item in items ?? Array.Empty<GroupOption>())
|
||||||
|
{
|
||||||
|
result.Add(CloneItem(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStatus()
|
||||||
|
{
|
||||||
|
var visibleCount = ItemsView.Cast<object>().Count();
|
||||||
|
StatusText = string.Format("Всего записей: {0}. По фильтру: {1}.", Items.Count, visibleCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
XLAB2/AccountingBookEditWindow.xaml
Normal file
64
XLAB2/AccountingBookEditWindow.xaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<Window x:Class="XLAB2.AccountingBookEditWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="{Binding Title}"
|
||||||
|
Height="250"
|
||||||
|
Width="560"
|
||||||
|
MinHeight="240"
|
||||||
|
MinWidth="520"
|
||||||
|
ResizeMode="NoResize"
|
||||||
|
WindowStartupLocation="CenterOwner">
|
||||||
|
<Grid Margin="16">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="180" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="0,0,12,8"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="Номер книги учета" />
|
||||||
|
<TextBox Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Text="{Binding BookNumber, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="0,0,12,8"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="Ключ" />
|
||||||
|
<TextBox Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Text="{Binding Key, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
Foreground="Firebrick"
|
||||||
|
Text="{Binding ValidationMessage}" />
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="3"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0,12,0,0"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<Button Width="100"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
IsDefault="True"
|
||||||
|
Command="{Binding ConfirmCommand}"
|
||||||
|
Content="Сохранить" />
|
||||||
|
<Button Width="90"
|
||||||
|
Command="{Binding CancelCommand}"
|
||||||
|
Content="Отмена" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
20
XLAB2/AccountingBookEditWindow.xaml.cs
Normal file
20
XLAB2/AccountingBookEditWindow.xaml.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace XLAB2
|
||||||
|
{
|
||||||
|
public partial class AccountingBookEditWindow : Window
|
||||||
|
{
|
||||||
|
internal AccountingBookEditWindow(AccountingBookEditWindowViewModel viewModel)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = viewModel;
|
||||||
|
viewModel.CloseRequested += ViewModelOnCloseRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
|
||||||
|
{
|
||||||
|
DialogResult = dialogResult;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
138
XLAB2/AccountingBookEditWindowViewModel.cs
Normal file
138
XLAB2/AccountingBookEditWindowViewModel.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace XLAB2
|
||||||
|
{
|
||||||
|
internal sealed class AccountingBookEditWindowViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyList<GroupOption> _existingItems;
|
||||||
|
private readonly string _originalKey;
|
||||||
|
private string _bookNumber;
|
||||||
|
private string _key;
|
||||||
|
private string _validationMessage;
|
||||||
|
|
||||||
|
public AccountingBookEditWindowViewModel(GroupOption seed, bool isNew, IReadOnlyList<GroupOption> existingItems)
|
||||||
|
{
|
||||||
|
var source = seed ?? new GroupOption();
|
||||||
|
_existingItems = existingItems ?? Array.Empty<GroupOption>();
|
||||||
|
_originalKey = Normalize(source.Key);
|
||||||
|
IsNew = isNew;
|
||||||
|
BookNumber = source.Title ?? string.Empty;
|
||||||
|
Key = source.Key ?? string.Empty;
|
||||||
|
|
||||||
|
ConfirmCommand = new RelayCommand(Confirm);
|
||||||
|
CancelCommand = new RelayCommand(Cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<bool?> CloseRequested;
|
||||||
|
|
||||||
|
public string BookNumber
|
||||||
|
{
|
||||||
|
get { return _bookNumber; }
|
||||||
|
set { SetProperty(ref _bookNumber, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand CancelCommand { get; private set; }
|
||||||
|
|
||||||
|
public ICommand ConfirmCommand { get; private set; }
|
||||||
|
|
||||||
|
public bool IsNew { get; private set; }
|
||||||
|
|
||||||
|
public string Key
|
||||||
|
{
|
||||||
|
get { return _key; }
|
||||||
|
set { SetProperty(ref _key, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
get { return IsNew ? "Новая книга учета" : "Редактирование книги учета"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ValidationMessage
|
||||||
|
{
|
||||||
|
get { return _validationMessage; }
|
||||||
|
private set { SetProperty(ref _validationMessage, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupOption ToResult()
|
||||||
|
{
|
||||||
|
return new GroupOption
|
||||||
|
{
|
||||||
|
Key = Normalize(Key) ?? string.Empty,
|
||||||
|
Title = Normalize(BookNumber) ?? string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel(object parameter)
|
||||||
|
{
|
||||||
|
RaiseCloseRequested(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Confirm(object parameter)
|
||||||
|
{
|
||||||
|
var normalizedKey = Normalize(Key);
|
||||||
|
if (normalizedKey == null)
|
||||||
|
{
|
||||||
|
ValidationMessage = "Укажите ключ книги учета.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedBookNumber = Normalize(BookNumber);
|
||||||
|
if (normalizedBookNumber == null)
|
||||||
|
{
|
||||||
|
ValidationMessage = "Укажите номер книги учета.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var duplicateKey = _existingItems.FirstOrDefault(delegate(GroupOption item)
|
||||||
|
{
|
||||||
|
return item != null
|
||||||
|
&& !IsCurrentItem(item)
|
||||||
|
&& string.Equals(Normalize(item.Key), normalizedKey, StringComparison.OrdinalIgnoreCase);
|
||||||
|
});
|
||||||
|
if (duplicateKey != null)
|
||||||
|
{
|
||||||
|
ValidationMessage = string.Format("Ключ книги учета \"{0}\" уже существует.", normalizedKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var duplicateBookNumber = _existingItems.FirstOrDefault(delegate(GroupOption item)
|
||||||
|
{
|
||||||
|
return item != null
|
||||||
|
&& !IsCurrentItem(item)
|
||||||
|
&& string.Equals(Normalize(item.Title), normalizedBookNumber, StringComparison.OrdinalIgnoreCase);
|
||||||
|
});
|
||||||
|
if (duplicateBookNumber != null)
|
||||||
|
{
|
||||||
|
ValidationMessage = string.Format("Книга учета \"{0}\" уже существует.", normalizedBookNumber);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationMessage = string.Empty;
|
||||||
|
RaiseCloseRequested(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsCurrentItem(GroupOption item)
|
||||||
|
{
|
||||||
|
return !IsNew
|
||||||
|
&& string.Equals(Normalize(item == null ? null : item.Key), _originalKey, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Normalize(string value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseCloseRequested(bool? dialogResult)
|
||||||
|
{
|
||||||
|
var handler = CloseRequested;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, dialogResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
406
XLAB2/App.xaml
Normal file
406
XLAB2/App.xaml
Normal 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
156
XLAB2/App.xaml.cs
Normal 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
39
XLAB2/AppHost.cs
Normal 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
10
XLAB2/AssemblyInfo.cs
Normal 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
BIN
XLAB2/Assets/XlabApp.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
BIN
XLAB2/Assets/XlabAppIconPreview.png
Normal file
BIN
XLAB2/Assets/XlabAppIconPreview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 303 KiB |
BIN
XLAB2/Assets/XlabAppIconPreviewAbstract.png
Normal file
BIN
XLAB2/Assets/XlabAppIconPreviewAbstract.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 303 KiB |
18
XLAB2/Assets/document-number-directory.json
Normal file
18
XLAB2/Assets/document-number-directory.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"documentTypes": [
|
||||||
|
{
|
||||||
|
"key": "PSV",
|
||||||
|
"title": "ПСВ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ACT_REFERENCE",
|
||||||
|
"title": "Акт-справка"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accountingBooks": [
|
||||||
|
{
|
||||||
|
"key": "219/С5/15",
|
||||||
|
"title": "219/С5/15"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
57
XLAB2/CloneVerificationWindow.xaml
Normal file
57
XLAB2/CloneVerificationWindow.xaml
Normal 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>
|
||||||
20
XLAB2/CloneVerificationWindow.xaml.cs
Normal file
20
XLAB2/CloneVerificationWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
115
XLAB2/CloneVerificationWindowViewModel.cs
Normal file
115
XLAB2/CloneVerificationWindowViewModel.cs
Normal 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
BIN
XLAB2/ClosePsv.docx
Normal file
Binary file not shown.
109
XLAB2/CreateDocumentWindow.xaml
Normal file
109
XLAB2/CreateDocumentWindow.xaml
Normal 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>
|
||||||
20
XLAB2/CreateDocumentWindow.xaml.cs
Normal file
20
XLAB2/CreateDocumentWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
212
XLAB2/CreateDocumentWindowViewModel.cs
Normal file
212
XLAB2/CreateDocumentWindowViewModel.cs
Normal 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
188
XLAB2/DialogService.cs
Normal 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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
260
XLAB2/DocumentNumberDirectoryService.cs
Normal file
260
XLAB2/DocumentNumberDirectoryService.cs
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace XLAB2
|
||||||
|
{
|
||||||
|
internal sealed class DocumentNumberDirectory
|
||||||
|
{
|
||||||
|
public DocumentNumberDirectory(IReadOnlyList<GroupOption> accountingBooks, IReadOnlyList<GroupOption> documentTypes)
|
||||||
|
{
|
||||||
|
AccountingBooks = accountingBooks ?? Array.Empty<GroupOption>();
|
||||||
|
DocumentTypes = documentTypes ?? Array.Empty<GroupOption>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<GroupOption> AccountingBooks { get; private set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<GroupOption> DocumentTypes { get; private set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DocumentNumberDirectoryService
|
||||||
|
{
|
||||||
|
private readonly string _filePath;
|
||||||
|
|
||||||
|
public DocumentNumberDirectoryService(string filePath)
|
||||||
|
{
|
||||||
|
_filePath = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DocumentNumberDirectory LoadDirectory()
|
||||||
|
{
|
||||||
|
var configuration = LoadConfiguration();
|
||||||
|
|
||||||
|
return new DocumentNumberDirectory(
|
||||||
|
NormalizeEntries(configuration.AccountingBooks, "книг учета"),
|
||||||
|
NormalizeEntries(configuration.DocumentTypes, "типов документов"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<GroupOption> LoadAccountingBooks()
|
||||||
|
{
|
||||||
|
return LoadDirectory().AccountingBooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveAccountingBooks(IReadOnlyList<GroupOption> accountingBooks)
|
||||||
|
{
|
||||||
|
var configuration = LoadConfiguration();
|
||||||
|
var normalizedAccountingBooks = NormalizeWritableEntries(accountingBooks, "книг учета");
|
||||||
|
var normalizedDocumentTypes = NormalizeEntries(configuration.DocumentTypes, "типов документов");
|
||||||
|
|
||||||
|
configuration.AccountingBooks = ToConfigurationEntries(normalizedAccountingBooks);
|
||||||
|
configuration.DocumentTypes = ToConfigurationEntries(normalizedDocumentTypes);
|
||||||
|
|
||||||
|
SaveConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<GroupOption> NormalizeEntries(IEnumerable<DocumentNumberDirectoryEntry> entries, string sectionName)
|
||||||
|
{
|
||||||
|
var items = new List<GroupOption>();
|
||||||
|
var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var entry in entries ?? Array.Empty<DocumentNumberDirectoryEntry>())
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = DocumentNumberFormatter.NormalizePart(entry.Key);
|
||||||
|
var title = DocumentNumberFormatter.NormalizePart(entry.Title);
|
||||||
|
if (key == null || title == null || !seenKeys.Add(key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.Add(new GroupOption
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Title = title
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Format("Справочник номеров документов не содержит {0}.", sectionName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<GroupOption> NormalizeWritableEntries(IEnumerable<GroupOption> entries, string sectionName)
|
||||||
|
{
|
||||||
|
var items = new List<GroupOption>();
|
||||||
|
var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var seenTitles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var entry in entries ?? Array.Empty<GroupOption>())
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = DocumentNumberFormatter.NormalizePart(entry.Key);
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Не задан ключ книги учета.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var title = DocumentNumberFormatter.NormalizePart(entry.Title);
|
||||||
|
if (title == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Не задан номер книги учета.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!seenKeys.Add(key))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Format("Ключ книги учета \"{0}\" повторяется.", key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!seenTitles.Add(title))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Format("Номер книги учета \"{0}\" повторяется.", title));
|
||||||
|
}
|
||||||
|
|
||||||
|
items.Add(new GroupOption
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Title = title
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Format("Справочник номеров документов не содержит {0}.", sectionName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DocumentNumberDirectoryConfiguration LoadConfiguration()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_filePath))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Не задан путь к справочнику номеров документов.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(_filePath))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Format("Не найден справочник номеров документов: {0}.", _filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var stream = File.OpenRead(_filePath))
|
||||||
|
{
|
||||||
|
var configuration = JsonSerializer.Deserialize<DocumentNumberDirectoryConfiguration>(stream, CreateReadSerializerOptions());
|
||||||
|
if (configuration == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Справочник номеров документов пуст или поврежден.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveConfiguration(DocumentNumberDirectoryConfiguration configuration)
|
||||||
|
{
|
||||||
|
var directoryPath = Path.GetDirectoryName(_filePath);
|
||||||
|
if (!string.IsNullOrWhiteSpace(directoryPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directoryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(configuration, CreateWriteSerializerOptions());
|
||||||
|
File.WriteAllText(_filePath, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<DocumentNumberDirectoryEntry> ToConfigurationEntries(IEnumerable<GroupOption> entries)
|
||||||
|
{
|
||||||
|
var items = new List<DocumentNumberDirectoryEntry>();
|
||||||
|
|
||||||
|
foreach (var entry in entries ?? Array.Empty<GroupOption>())
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.Add(new DocumentNumberDirectoryEntry
|
||||||
|
{
|
||||||
|
Key = entry.Key,
|
||||||
|
Title = entry.Title
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonSerializerOptions CreateReadSerializerOptions()
|
||||||
|
{
|
||||||
|
return new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonSerializerOptions CreateWriteSerializerOptions()
|
||||||
|
{
|
||||||
|
return new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class DocumentNumberFormatter
|
||||||
|
{
|
||||||
|
public const int MaxLength = 60;
|
||||||
|
|
||||||
|
public static string Build(string documentTypeTitle, string accountingBookTitle, string documentSequenceNumber)
|
||||||
|
{
|
||||||
|
var normalizedDocumentTypeTitle = NormalizePart(documentTypeTitle);
|
||||||
|
var normalizedAccountingBookTitle = NormalizePart(accountingBookTitle);
|
||||||
|
var normalizedDocumentSequenceNumber = NormalizePart(documentSequenceNumber);
|
||||||
|
|
||||||
|
if (normalizedDocumentTypeTitle == null
|
||||||
|
|| normalizedAccountingBookTitle == null
|
||||||
|
|| normalizedDocumentSequenceNumber == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Format("{0} № {1}-{2}", normalizedDocumentTypeTitle, normalizedAccountingBookTitle, normalizedDocumentSequenceNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string NormalizePart(string value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DocumentNumberDirectoryConfiguration
|
||||||
|
{
|
||||||
|
public List<DocumentNumberDirectoryEntry> AccountingBooks { get; set; }
|
||||||
|
|
||||||
|
public List<DocumentNumberDirectoryEntry> DocumentTypes { get; set; }
|
||||||
|
|
||||||
|
[JsonExtensionData]
|
||||||
|
public Dictionary<string, JsonElement> ExtensionData { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DocumentNumberDirectoryEntry
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
public string Title { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
58
XLAB2/EkzDirectoryDialogService.cs
Normal file
58
XLAB2/EkzDirectoryDialogService.cs
Normal 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
138
XLAB2/EkzDirectoryModels.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
858
XLAB2/EkzDirectoryService.cs
Normal file
858
XLAB2/EkzDirectoryService.cs
Normal 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("Экземпляр с таким типоразмером, владельцем и заводским номером уже существует.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
198
XLAB2/EkzDirectoryWindow.xaml
Normal file
198
XLAB2/EkzDirectoryWindow.xaml
Normal 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>
|
||||||
33
XLAB2/EkzDirectoryWindow.xaml.cs
Normal file
33
XLAB2/EkzDirectoryWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
568
XLAB2/EkzDirectoryWindowViewModel.cs
Normal file
568
XLAB2/EkzDirectoryWindowViewModel.cs
Normal 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
109
XLAB2/EkzEditWindow.xaml
Normal 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>
|
||||||
20
XLAB2/EkzEditWindow.xaml.cs
Normal file
20
XLAB2/EkzEditWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
185
XLAB2/EkzEditWindowViewModel.cs
Normal file
185
XLAB2/EkzEditWindowViewModel.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
XLAB2/FrpdDirectoryDialogService.cs
Normal file
70
XLAB2/FrpdDirectoryDialogService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
XLAB2/FrpdDirectoryModels.cs
Normal file
45
XLAB2/FrpdDirectoryModels.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
560
XLAB2/FrpdDirectoryService.cs
Normal file
560
XLAB2/FrpdDirectoryService.cs
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
174
XLAB2/FrpdDirectoryWindow.xaml
Normal file
174
XLAB2/FrpdDirectoryWindow.xaml
Normal 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>
|
||||||
33
XLAB2/FrpdDirectoryWindow.xaml.cs
Normal file
33
XLAB2/FrpdDirectoryWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
476
XLAB2/FrpdDirectoryWindowViewModel.cs
Normal file
476
XLAB2/FrpdDirectoryWindowViewModel.cs
Normal 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
48
XLAB2/FrpdEditWindow.xaml
Normal 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>
|
||||||
20
XLAB2/FrpdEditWindow.xaml.cs
Normal file
20
XLAB2/FrpdEditWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
174
XLAB2/FrpdEditWindowViewModel.cs
Normal file
174
XLAB2/FrpdEditWindowViewModel.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
XLAB2/FrpdvdEditWindow.xaml
Normal file
32
XLAB2/FrpdvdEditWindow.xaml
Normal 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>
|
||||||
20
XLAB2/FrpdvdEditWindow.xaml.cs
Normal file
20
XLAB2/FrpdvdEditWindow.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
XLAB2/FrpdvdEditWindowViewModel.cs
Normal file
103
XLAB2/FrpdvdEditWindowViewModel.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
XLAB2/Infrastructure/DatabaseConfiguration.cs
Normal file
73
XLAB2/Infrastructure/DatabaseConfiguration.cs
Normal 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
XLAB2/Infrastructure/DatabaseOptions.cs
Normal file
32
XLAB2/Infrastructure/DatabaseOptions.cs
Normal 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
Reference in New Issue
Block a user