Compare commits

..

2 Commits

Author SHA1 Message Date
Курнат Андрей
ff133e0215 edit 2026-03-18 20:45:53 +03:00
Курнат Андрей
dedd1c5c25 edit 2026-03-17 23:53:30 +03:00
8 changed files with 628 additions and 106 deletions

View File

@@ -1,9 +1,148 @@
<Application x:Class="XLAB.App"
<Application x:Class="XLAB.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:XLAB"
StartupUri="MainWindow.xaml">
<Application.Resources>
<LinearGradientBrush x:Key="AppWindowBackgroundBrush" StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#FFF8FAFD" Offset="0" />
<GradientStop Color="#FFF2F6FB" Offset="1" />
</LinearGradientBrush>
<SolidColorBrush x:Key="AppSurfaceBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="AppAccentBrush" Color="#FF5C7FA8" />
<SolidColorBrush x:Key="AppAccentSoftBrush" Color="#FFDDE8F3" />
<SolidColorBrush x:Key="AppBorderBrush" Color="#FFC9D6E2" />
<SolidColorBrush x:Key="AppTextBrush" Color="#FF263645" />
<SolidColorBrush x:Key="AppMutedTextBrush" Color="#FF6B7B88" />
<SolidColorBrush x:Key="AppButtonBrush" Color="#FFF5F8FC" />
<SolidColorBrush x:Key="AppButtonHoverBrush" Color="#FFEAF2FA" />
<SolidColorBrush x:Key="AppSelectionBrush" Color="#FFDDEAF7" />
<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}" />
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
</Style>
<Style TargetType="{x:Type GroupBox}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppAccentBrush}" />
</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 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 AppSurfaceBrush}" />
<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>
<Style TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
</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="RowBackground" Value="#FFFFFFFF" />
<Setter Property="AlternatingRowBackground" Value="#FFF7FAFD" />
<Setter Property="ColumnHeaderStyle">
<Setter.Value>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="#FFEAF2FA" />
<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>

View File

@@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Новый ПСВ"
Height="220"
Height="240"
Width="430"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">

View File

@@ -22,11 +22,11 @@
<MenuItem Header="Наименования типов СИ"
Click="SpnmtpDirectoryMenuItem_Click" />
</MenuItem>
<MenuItem Header="Организации и подразделения"
<MenuItem Header="Подразделения"
Click="FrpdDirectoryMenuItem_Click" />
<MenuItem Header="Персоны"
<MenuItem Header="Персонал"
Click="PrsnDirectoryMenuItem_Click" />
<MenuItem Header="Типоразмеры СИ"
<MenuItem Header="Типоразмеры"
Click="TypeSizeDirectoryMenuItem_Click" />
</Menu>
@@ -40,22 +40,35 @@
<GroupBox Grid.Column="0" Header="Документы">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
<WrapPanel Grid.Row="0"
Margin="0,0,0,8"
VerticalAlignment="Center">
<RadioButton Margin="0,0,16,0"
GroupName="DocumentModeGroup"
Content="Открытые ПСВ"
IsChecked="{Binding ShowOpenDocuments, Mode=TwoWay}" />
<RadioButton GroupName="DocumentModeGroup"
Content="Закрытые ПСВ (текущий год)"
IsChecked="{Binding ShowClosedDocuments, Mode=TwoWay}" />
</WrapPanel>
<TextBlock Grid.Row="1"
Margin="0,0,0,4"
VerticalAlignment="Center"
Text="Поиск по номеру ПСВ или заказчику" />
Text="Поиск по номеру ПСВ или подразделению" />
<TextBox Grid.Row="1"
<TextBox Grid.Row="2"
Margin="0,0,0,8"
Text="{Binding DocumentFilterText, UpdateSourceTrigger=PropertyChanged}" />
<ListBox Grid.Row="2"
<ListBox Grid.Row="3"
ItemsSource="{Binding DocumentsView}"
SelectedItem="{Binding SelectedDocument, Mode=TwoWay}"
BorderThickness="1"
@@ -67,6 +80,7 @@
Command="{Binding AddDocumentCommand}" />
<MenuItem Header="Распечатать"
Command="{Binding PrintDocumentCommand}" />
<Separator/>
<MenuItem Header="Удалить"
Command="{Binding DeleteDocumentCommand}" />
</ContextMenu>
@@ -85,7 +99,47 @@
<Border Padding="8"
BorderBrush="#DDD"
BorderThickness="0,0,0,1">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0"
Width="10"
Height="10"
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Stroke="#8093A4B5"
StrokeThickness="1">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Visibility" Value="Collapsed" />
<Setter Property="Fill" Value="Transparent" />
<Setter Property="ToolTip" Value="{x:Null}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsOpenDocumentAtTenDays}" Value="True">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Fill" Value="{StaticResource OpenDocumentTenDaysIndicatorBrush}" />
<Setter Property="ToolTip" Value="С даты приемки прошло 10 дней." />
</DataTrigger>
<DataTrigger Binding="{Binding IsOpenDocumentAtTwentyDays}" Value="True">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Fill" Value="{StaticResource OpenDocumentTwentyDaysIndicatorBrush}" />
<Setter Property="ToolTip" Value="С даты приемки прошло 20 дней." />
</DataTrigger>
<DataTrigger Binding="{Binding IsOpenDocumentOverdue}" Value="True">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Fill" Value="{StaticResource OpenDocumentOverdueIndicatorBrush}" />
<Setter Property="ToolTip" Value="Срок ПСВ истек: дата приемки + 30 дней." />
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<StackPanel Grid.Column="1">
<DockPanel LastChildFill="False">
<TextBlock DockPanel.Dock="Left"
FontWeight="SemiBold"
@@ -98,12 +152,13 @@
Foreground="DimGray"
Text="{Binding CustomerName}" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="3"
<TextBlock Grid.Row="4"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding DocumentStatusText}" />
@@ -142,6 +197,7 @@
<TextBox Grid.Row="0"
Grid.Column="1"
Margin="0,0,12,8"
IsEnabled="{Binding IsDocumentHeaderEditable}"
Text="{Binding DocumentNumberEditor, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="0"
@@ -152,6 +208,7 @@
<DatePicker Grid.Row="0"
Grid.Column="3"
Margin="0,0,12,8"
IsEnabled="{Binding IsDocumentHeaderEditable}"
SelectedDate="{Binding HeaderReceivedOn, Mode=TwoWay}"
SelectedDateFormat="Short" />
@@ -163,6 +220,7 @@
<DatePicker Grid.Row="0"
Grid.Column="5"
Margin="0,0,12,8"
IsEnabled="{Binding IsDocumentHeaderEditable}"
SelectedDate="{Binding HeaderIssuedOn, Mode=TwoWay}"
SelectedDateFormat="Short" />
@@ -172,6 +230,7 @@
HorizontalAlignment="Right">
<Button Width="120"
Margin="0,0,0,8"
IsEnabled="{Binding IsDocumentHeaderEditable}"
Command="{Binding SaveDocumentHeaderCommand}"
Content="Сохранить" />
</StackPanel>
@@ -180,7 +239,7 @@
Grid.Column="0"
Margin="0,0,8,6"
VerticalAlignment="Center"
Text="Заказчик" />
Text="Подразделение" />
<ComboBox Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="3"
@@ -191,18 +250,6 @@
SelectedValue="{Binding SelectedCustomerId, Mode=TwoWay}"
IsEnabled="{Binding IsCustomerEditable}" />
<TextBlock Grid.Row="1"
Grid.Column="4"
Margin="0,0,8,6"
VerticalAlignment="Center"
Text="Подразделение" />
<TextBox Grid.Row="1"
Grid.Column="5"
Grid.ColumnSpan="2"
Margin="0,0,0,6"
IsReadOnly="True"
Text="{Binding HeaderDepartmentName, Mode=OneWay}" />
<TextBlock Grid.Row="2"
Grid.ColumnSpan="7"
Foreground="DimGray"
@@ -224,6 +271,7 @@
Command="{Binding OpenInstrumentPickerCommand}" />
<MenuItem Header="Добавить по типу"
Command="{Binding OpenInstrumentTypePickerCommand}" />
<Separator/>
<MenuItem Header="Удалить"
Command="{Binding DeleteSelectedGroupsCommand}" />
</ContextMenu>
@@ -263,7 +311,7 @@
<DataGridTextColumn Header="Годных"
Width="90"
Binding="{Binding GoodCount}" />
<DataGridTextColumn Header="Забраковано"
<DataGridTextColumn Header="Забракованых"
Width="105"
Binding="{Binding RejectedCount}" />
</DataGrid.Columns>
@@ -296,7 +344,7 @@
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Клонировать поверку по зав. №..."
<MenuItem Header="Клонировать поверку в выбранные строки"
Command="{Binding CloneLineVerificationCommand}" />
<MenuItem Header="Распечатать документ о поверке"
Command="{Binding PrintVerificationDocumentCommand}" />
@@ -308,10 +356,14 @@
<Separator/>
<MenuItem Header="Отменить проверку"
Command="{Binding ResetLineVerificationCommand}" />
<MenuItem Header="Удалить"
Command="{Binding DeleteSelectedLinesCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="MouseDoubleClick"
Handler="DocumentLineRow_MouseDoubleClick" />
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DocumentLineRow_PreviewMouseRightButtonDown" />
</Style>
@@ -330,6 +382,9 @@
<DataGridTextColumn Header="Зав. №"
Width="120"
Binding="{Binding SerialNumber}" />
<DataGridTextColumn Header="Дата поверки"
Width="110"
Binding="{Binding VerificationDateDisplay}" />
<DataGridTextColumn Header="Поверитель"
Width="180"
Binding="{Binding VerifierName}" />

View File

@@ -35,6 +35,19 @@ namespace XLAB
}
}
private void DocumentLineRow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row == null)
{
return;
}
row.IsSelected = true;
row.Focus();
_viewModel.TryEditVerificationFromDoubleClick(row.Item as PsvDocumentLine);
}
private void DocumentGroupRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;

View File

@@ -25,10 +25,12 @@ namespace XLAB
private DateTime? _headerReceivedOn;
private bool _isBusy;
private string _lineStatusText;
private PsvDocumentLine _lastCloneSourceLine;
private int? _selectedCustomerId;
private PsvDocumentSummary _selectedDocument;
private PsvDocumentGroupSummary _selectedDocumentGroup;
private PsvDocumentLine _selectedDocumentLine;
private bool _showClosedDocuments;
public MainWindowViewModel(PsvDataService service, IDialogService dialogService)
{
@@ -49,14 +51,15 @@ namespace XLAB
DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines);
DocumentLinesView.Filter = FilterDocumentLines;
AddDocumentCommand = new RelayCommand(delegate { AddDocument(); }, delegate { return !IsBusy; });
AddDocumentCommand = new RelayCommand(delegate { AddDocument(); }, delegate { return !IsBusy && !ShowClosedDocuments; });
CloneLineVerificationCommand = new RelayCommand(delegate { CloneSelectedLineVerificationAsync(); }, delegate { return CanCloneSelectedLineVerification(); });
DeleteDocumentCommand = new RelayCommand(delegate { DeleteDocumentAsync(); }, delegate { return !IsBusy && SelectedDocument != null; });
DeleteSelectedLinesCommand = new RelayCommand(delegate { DeleteSelectedLinesAsync(); }, delegate { return CanDeleteSelectedLines(); });
DeleteSelectedGroupsCommand = new RelayCommand(delegate { DeleteSelectedGroupsAsync(); }, delegate { return CanDeleteSelectedGroups(); });
MarkLinePassedCommand = new RelayCommand(delegate { EditLineVerificationAsync(true); }, delegate { return CanEditSelectedLineVerification(); });
MarkLineRejectedCommand = new RelayCommand(delegate { EditLineVerificationAsync(false); }, delegate { return CanEditSelectedLineVerification(); });
OpenInstrumentPickerCommand = new RelayCommand(delegate { OpenInstrumentPickerAsync(); }, delegate { return !IsBusy && SelectedDocument != null && SelectedDocument.CustomerId.HasValue; });
OpenInstrumentTypePickerCommand = new RelayCommand(delegate { OpenInstrumentTypePickerAsync(); }, delegate { return !IsBusy && SelectedDocument != null && SelectedDocument.CustomerId.HasValue; });
OpenInstrumentPickerCommand = new RelayCommand(delegate { OpenInstrumentPickerAsync(); }, delegate { return CanAddInstrumentsToSelectedDocument(); });
OpenInstrumentTypePickerCommand = new RelayCommand(delegate { OpenInstrumentTypePickerAsync(); }, delegate { return CanAddInstrumentsToSelectedDocument(); });
PrintDocumentCommand = new RelayCommand(delegate { PrintSelectedDocumentAsync(); }, delegate { return CanPrintSelectedDocument(); });
PrintVerificationDocumentCommand = new RelayCommand(delegate { PrintSelectedVerificationDocumentAsync(); }, delegate { return CanPrintSelectedVerificationDocument(); });
RefreshDocumentsCommand = new RelayCommand(delegate { RefreshDocumentsAsync(null, null); }, delegate { return !IsBusy; });
@@ -85,6 +88,35 @@ namespace XLAB
}
}
public bool ShowClosedDocuments
{
get { return _showClosedDocuments; }
set
{
if (!SetProperty(ref _showClosedDocuments, value))
{
return;
}
OnPropertyChanged("ShowOpenDocuments");
OnPropertyChanged("IsDocumentHeaderEditable");
RaiseCommandStates();
RefreshDocumentsAsync(null, null);
}
}
public bool ShowOpenDocuments
{
get { return !ShowClosedDocuments; }
set
{
if (value)
{
ShowClosedDocuments = false;
}
}
}
public ObservableCollection<PsvDocumentLine> DocumentLines { get; private set; }
public ICollectionView DocumentLinesView { get; private set; }
@@ -109,6 +141,8 @@ namespace XLAB
public ICommand DeleteDocumentCommand { get; private set; }
public ICommand DeleteSelectedLinesCommand { get; private set; }
public ICommand DeleteSelectedGroupsCommand { get; private set; }
public string GroupDetailFilterText
@@ -129,6 +163,16 @@ namespace XLAB
private set { SetProperty(ref _headerDepartmentName, value); }
}
public bool IsDocumentHeaderEditable
{
get
{
return !IsBusy
&& SelectedDocument != null
&& !IsDocumentClosed(SelectedDocument);
}
}
public DateTime? HeaderIssuedOn
{
get { return _headerIssuedOn; }
@@ -150,6 +194,7 @@ namespace XLAB
{
RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable");
OnPropertyChanged("IsDocumentHeaderEditable");
}
}
}
@@ -210,9 +255,11 @@ namespace XLAB
{
if (SetProperty(ref _selectedDocument, value))
{
_lastCloneSourceLine = null;
FillHeaderFromSelection();
RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable");
OnPropertyChanged("IsDocumentHeaderEditable");
LoadSelectedDocumentAsync();
}
}
@@ -237,6 +284,11 @@ namespace XLAB
{
if (SetProperty(ref _selectedDocumentLine, value))
{
if (CanUseLineAsCloneSource(value))
{
_lastCloneSourceLine = value;
}
RaiseCommandStates();
}
}
@@ -297,7 +349,7 @@ namespace XLAB
InsertDraftIntoCollection(draft);
DocumentsView.Refresh();
SelectedDocument = draft;
DocumentStatusText = string.Format("Документов: {0}.", Documents.Count);
DocumentStatusText = BuildDocumentStatusText(Documents.Count);
}
private void ApplySelectedCustomer()
@@ -321,6 +373,11 @@ namespace XLAB
return false;
}
if (IsDocumentClosed(SelectedDocument))
{
return false;
}
if (!SelectedDocument.IsDraft)
{
return true;
@@ -331,11 +388,16 @@ namespace XLAB
private bool CanDeleteSelectedGroups()
{
return !IsBusy
&& SelectedDocument != null
return CanModifySelectedDocument()
&& GetDeleteTargetGroups().Count > 0;
}
private bool CanDeleteSelectedLines()
{
return CanModifySelectedDocument()
&& GetDeleteTargetLines().Count > 0;
}
private bool CanPrintSelectedDocument()
{
return !IsBusy
@@ -346,14 +408,17 @@ namespace XLAB
private bool CanEditSelectedLineVerification()
{
var targetLines = GetVerificationTargetLines();
return !IsBusy
return CanModifySelectedDocument()
&& targetLines.Count > 0
&& targetLines.All(delegate(PsvDocumentLine line) { return !HasVerificationData(line); });
}
private bool CanCloneSelectedLineVerification()
{
return !IsBusy && CanUseLineAsCloneSource(SelectedDocumentLine);
var sourceLine = ResolveCloneSourceLine();
return CanModifySelectedDocument()
&& CanUseLineAsCloneSource(sourceLine)
&& GetCheckedCloneTargetLines(sourceLine).Count > 0;
}
private bool CanPrintSelectedVerificationDocument()
@@ -364,11 +429,28 @@ namespace XLAB
private bool CanResetSelectedLineVerification()
{
var targetLines = GetVerificationTargetLines();
return !IsBusy
return CanModifySelectedDocument()
&& targetLines.Count > 0
&& targetLines.All(HasVerificationData);
}
private bool CanModifySelectedDocument()
{
return !IsBusy
&& SelectedDocument != null
&& !IsDocumentClosed(SelectedDocument);
}
private bool CanAddInstrumentsToSelectedDocument()
{
return CanModifySelectedDocument() && SelectedDocument.CustomerId.HasValue;
}
private static bool IsDocumentClosed(PsvDocumentSummary document)
{
return document != null && document.IssuedOn.HasValue;
}
private static bool HasVerificationData(PsvDocumentLine line)
{
return line != null
@@ -436,6 +518,14 @@ namespace XLAB
return true;
}
private bool CanEditVerificationFromDoubleClick(PsvDocumentLine line)
{
return CanModifySelectedDocument()
&& line != null
&& line.IsPassed.HasValue
&& HasVerificationData(line);
}
private List<PsvDocumentLine> GetCheckedDocumentLines()
{
return DocumentLinesView.Cast<object>()
@@ -477,6 +567,57 @@ namespace XLAB
: new List<PsvDocumentLine> { SelectedDocumentLine };
}
private List<PsvDocumentLine> GetDeleteTargetLines()
{
var checkedLines = GetCheckedDocumentLines();
if (checkedLines.Count > 0)
{
return checkedLines;
}
return SelectedDocumentLine == null
? new List<PsvDocumentLine>()
: new List<PsvDocumentLine> { SelectedDocumentLine };
}
private List<PsvDocumentLine> GetCheckedCloneTargetLines(PsvDocumentLine sourceLine)
{
if (sourceLine == null)
{
return new List<PsvDocumentLine>();
}
return GetCheckedDocumentLines()
.Where(delegate(PsvDocumentLine line)
{
return line != null
&& !ReferenceEquals(line, sourceLine)
&& BelongsToSameGroup(line, sourceLine);
})
.ToList();
}
private PsvDocumentLine ResolveCloneSourceLine()
{
if (CanUseLineAsCloneSource(SelectedDocumentLine))
{
return SelectedDocumentLine;
}
if (_lastCloneSourceLine != null
&& DocumentLines.Contains(_lastCloneSourceLine)
&& CanUseLineAsCloneSource(_lastCloneSourceLine))
{
return _lastCloneSourceLine;
}
var checkedSourceLines = GetCheckedDocumentLines()
.Where(CanUseLineAsCloneSource)
.ToList();
return checkedSourceLines.Count == 1 ? checkedSourceLines[0] : null;
}
private void ClearCollections<T>(ObservableCollection<T> collection)
{
collection.Clear();
@@ -925,54 +1066,27 @@ namespace XLAB
private void CloneSelectedLineVerificationAsync()
{
var sourceLine = SelectedDocumentLine;
var sourceLine = ResolveCloneSourceLine();
if (!CanUseLineAsCloneSource(sourceLine))
{
_dialogService.ShowWarning("В выбранной строке нет полной поверки, пригодной для клонирования.");
_dialogService.ShowWarning("Выберите строку-источник с заполненной поверкой. Источник можно выделить строкой или отметить единственную подходящую строку чекбоксом.");
return;
}
var serialNumbers = _dialogService.ShowCloneVerificationDialog(CreateCloneVerificationSeed(sourceLine));
if (serialNumbers == null || serialNumbers.Count == 0)
var checkedLines = GetCheckedDocumentLines();
if (checkedLines.Count == 0)
{
_dialogService.ShowWarning("Отметьте чекбоксами строки, в которые нужно склонировать поверочные данные.");
return;
}
var sourceSerialNumber = NormalizeSerialNumber(sourceLine.SerialNumber);
var requestedSerialNumbers = new HashSet<string>(serialNumbers, StringComparer.OrdinalIgnoreCase);
var includedSourceSerialNumber = !string.IsNullOrWhiteSpace(sourceSerialNumber)
&& requestedSerialNumbers.Remove(sourceSerialNumber);
var matchedLines = DocumentLines
.Where(delegate(PsvDocumentLine line)
{
return line != null
&& !ReferenceEquals(line, sourceLine)
&& BelongsToSameGroup(line, sourceLine)
&& requestedSerialNumbers.Contains(NormalizeSerialNumber(line.SerialNumber));
})
.ToList();
var matchedSerialNumbers = new HashSet<string>(
matchedLines
.Select(delegate(PsvDocumentLine line) { return NormalizeSerialNumber(line.SerialNumber); })
.Where(delegate(string serialNumber) { return !string.IsNullOrWhiteSpace(serialNumber); }),
StringComparer.OrdinalIgnoreCase);
var missingSerialNumbers = serialNumbers
.Where(delegate(string serialNumber)
{
var normalized = NormalizeSerialNumber(serialNumber);
return !string.IsNullOrWhiteSpace(normalized)
&& !string.Equals(normalized, sourceSerialNumber, StringComparison.OrdinalIgnoreCase)
&& !matchedSerialNumbers.Contains(normalized);
})
.ToList();
var includedSourceSerialNumber = checkedLines.Any(delegate(PsvDocumentLine line) { return ReferenceEquals(line, sourceLine); });
var matchedLines = GetCheckedCloneTargetLines(sourceLine);
var targetLines = matchedLines.Where(delegate(PsvDocumentLine line) { return !HasVerificationData(line); }).ToList();
var skippedWithExistingVerificationCount = matchedLines.Count - targetLines.Count;
if (targetLines.Count == 0)
{
ShowCloneVerificationResult(0, skippedWithExistingVerificationCount, missingSerialNumbers, includedSourceSerialNumber, true);
ShowCloneVerificationResult(0, skippedWithExistingVerificationCount, includedSourceSerialNumber, true);
return;
}
@@ -992,7 +1106,7 @@ namespace XLAB
}
RefreshAfterLineVerificationChanged(sourceLine);
ShowCloneVerificationResult(targetLines.Count, skippedWithExistingVerificationCount, missingSerialNumbers, includedSourceSerialNumber, false);
ShowCloneVerificationResult(targetLines.Count, skippedWithExistingVerificationCount, includedSourceSerialNumber, false);
return;
}
@@ -1006,14 +1120,13 @@ namespace XLAB
}
await ReloadSelectedDocumentLinesAsync();
ShowCloneVerificationResult(targetLines.Count, skippedWithExistingVerificationCount, missingSerialNumbers, includedSourceSerialNumber, false);
ShowCloneVerificationResult(targetLines.Count, skippedWithExistingVerificationCount, includedSourceSerialNumber, false);
});
}
private void ShowCloneVerificationResult(
int clonedCount,
int skippedWithExistingVerificationCount,
IReadOnlyList<string> missingSerialNumbers,
bool includedSourceSerialNumber,
bool showWarning)
{
@@ -1033,14 +1146,9 @@ namespace XLAB
messages.Add("Номер строки-источника пропущен.");
}
if (missingSerialNumbers != null && missingSerialNumbers.Count > 0)
{
messages.Add(string.Format("Не найдены заводские номера: {0}.", BuildSerialNumberPreview(missingSerialNumbers)));
}
if (messages.Count == 0)
{
messages.Add("Подходящих строк для клонирования не найдено.");
messages.Add("Подходящих отмеченных строк для клонирования не найдено.");
}
var message = string.Join(" ", messages.ToArray());
@@ -1064,6 +1172,22 @@ namespace XLAB
EditLineVerificationCoreAsync(targetLines, isPassed);
}
public void TryEditVerificationFromDoubleClick(PsvDocumentLine line)
{
if (line == null)
{
return;
}
SelectedDocumentLine = line;
if (!CanEditVerificationFromDoubleClick(line))
{
return;
}
EditLineVerificationCoreAsync(new[] { line }, line.IsPassed.Value);
}
private async void EditLineVerificationCoreAsync(IReadOnlyList<PsvDocumentLine> targetLines, bool isPassed)
{
IReadOnlyList<DocumentFormReference> documentForms;
@@ -1214,7 +1338,7 @@ namespace XLAB
Documents.Remove(draft);
DocumentsView.Refresh();
SelectedDocument = Documents.Count > 0 ? Documents[0] : null;
DocumentStatusText = string.Format("Документов: {0}.", Documents.Count);
DocumentStatusText = BuildDocumentStatusText(Documents.Count);
}
private void PrintSelectedDocumentAsync()
@@ -1331,7 +1455,115 @@ namespace XLAB
UpdateDocumentSummaryFromLines(selectedDocument, pendingLines);
ApplyDocumentLines(pendingLines, SelectedDocumentGroup);
DocumentsView.Refresh();
DocumentStatusText = string.Format("Документов: {0}.", Documents.Count);
DocumentStatusText = BuildDocumentStatusText(Documents.Count);
}
else if (remainingPersistedCount == 0 && remainingPendingCount > 0)
{
if (!_draftDocuments.Any(delegate(PsvDocumentSummary draft) { return draft.DocumentKey == selectedDocumentKey; }))
{
_draftDocuments.Add(selectedDocument);
}
selectedDocument.IsDraft = true;
UpdateDocumentSummaryFromLines(selectedDocument, pendingLines);
await RefreshDocumentsCoreAsync(selectedDocumentKey, selectedDocumentNumber);
}
else
{
await RefreshDocumentsCoreAsync(selectedDocumentKey, selectedDocumentNumber);
}
var messages = new List<string>();
if (deletedPendingCount > 0)
{
messages.Add(string.Format("Удалено черновых строк: {0}.", deletedPendingCount));
}
if (deletedResult.DeletedEkzMkFctvlCount > 0)
{
messages.Add(string.Format("Удалено строк EKZMKFCTVL: {0}.", deletedResult.DeletedEkzMkFctvlCount));
}
if (deletedResult.DeletedEkzMkCount > 0)
{
messages.Add(string.Format("Удалено строк EKZMK: {0}.", deletedResult.DeletedEkzMkCount));
}
if (deletedResult.DeletedDmsCount > 0)
{
messages.Add(string.Format("Удалено связанных DMS: {0}.", deletedResult.DeletedDmsCount));
}
if (messages.Count > 0)
{
_dialogService.ShowInfo(string.Join(" ", messages.ToArray()));
}
});
}
private void DeleteSelectedLinesAsync()
{
if (SelectedDocument == null)
{
return;
}
var targetLines = GetDeleteTargetLines();
if (targetLines.Count == 0)
{
return;
}
if (!_dialogService.Confirm(string.Format(
"Удалить из ПСВ \"{0}\" строк приборов: {1}?",
SelectedDocument.DocumentNumber,
targetLines.Count)))
{
return;
}
var selectedDocument = SelectedDocument;
var selectedDocumentKey = selectedDocument.DocumentKey;
var selectedDocumentNumber = selectedDocument.DocumentNumber;
RunBusyOperation(async delegate
{
var pendingLines = GetPendingLines(selectedDocument);
var pendingLinesToRemove = pendingLines
.Where(delegate(PsvDocumentLine line) { return targetLines.Any(delegate(PsvDocumentLine targetLine) { return ReferenceEquals(line, targetLine); }); })
.ToList();
var persistedCardIds = targetLines
.Where(delegate(PsvDocumentLine line) { return !line.IsPendingInsert; })
.Select(delegate(PsvDocumentLine line) { return line.CardId; })
.Distinct()
.ToList();
foreach (var pendingLine in pendingLinesToRemove)
{
pendingLines.Remove(pendingLine);
}
var deletedPendingCount = pendingLinesToRemove.Count;
var deletedResult = new DocumentGroupDeleteResult();
if (persistedCardIds.Count > 0)
{
deletedResult = await Task.Run(delegate
{
return _service.DeleteDocumentGroups(selectedDocumentNumber, persistedCardIds);
});
}
var remainingPendingCount = pendingLines.Count;
var remainingPersistedCount = DocumentLines.Count(delegate(PsvDocumentLine line) { return !line.IsPendingInsert; }) - persistedCardIds.Count;
if (selectedDocument.IsDraft)
{
UpdateDocumentSummaryFromLines(selectedDocument, pendingLines);
ApplyDocumentLines(pendingLines, SelectedDocumentGroup);
DocumentsView.Refresh();
DocumentStatusText = BuildDocumentStatusText(Documents.Count);
}
else if (remainingPersistedCount == 0 && remainingPendingCount > 0)
{
@@ -1457,11 +1689,6 @@ namespace XLAB
return false;
}
if (document.IssuedOn.HasValue)
{
return false;
}
if (!string.IsNullOrWhiteSpace(DocumentFilterText)
&& !Contains(document.DocumentNumber, DocumentFilterText)
&& !Contains(document.CustomerName, DocumentFilterText))
@@ -1472,6 +1699,16 @@ namespace XLAB
return true;
}
private string BuildDocumentStatusText(int count)
{
if (ShowClosedDocuments)
{
return string.Format("Закрытых ПСВ за {0:yyyy}: {1}.", DateTime.Today, count);
}
return string.Format("Открытых ПСВ: {0}.", count);
}
private bool FilterDocumentLines(object item)
{
var line = item as PsvDocumentLine;
@@ -1972,6 +2209,7 @@ namespace XLAB
((RelayCommand)AddDocumentCommand).RaiseCanExecuteChanged();
((RelayCommand)CloneLineVerificationCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteDocumentCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteSelectedLinesCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteSelectedGroupsCommand).RaiseCanExecuteChanged();
((RelayCommand)MarkLinePassedCommand).RaiseCanExecuteChanged();
((RelayCommand)MarkLineRejectedCommand).RaiseCanExecuteChanged();
@@ -2041,13 +2279,15 @@ namespace XLAB
{
DocumentStatusText = "Загрузка списка ПСВ...";
var databaseDocuments = await Task.Run(delegate { return _service.LoadDocuments(); });
var databaseDocuments = await Task.Run(delegate { return _service.LoadDocuments(ShowClosedDocuments); });
var currentDocumentKey = documentKeyToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentKey : null);
var currentDocumentNumber = documentNumberToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentNumber : null);
ClearCollections(Documents);
foreach (var draft in _draftDocuments.OrderByDescending(delegate(PsvDocumentSummary item) { return item.AcceptedOn ?? DateTime.MinValue; }))
foreach (var draft in _draftDocuments
.Where(delegate(PsvDocumentSummary item) { return !ShowClosedDocuments; })
.OrderByDescending(delegate(PsvDocumentSummary item) { return item.AcceptedOn ?? DateTime.MinValue; }))
{
Documents.Add(draft);
}
@@ -2084,7 +2324,7 @@ namespace XLAB
SelectedDocument = Documents[0];
}
DocumentStatusText = string.Format("Документов: {0}.", Documents.Count);
DocumentStatusText = BuildDocumentStatusText(Documents.Count);
}
private void SaveDocumentAsync()

View File

@@ -492,9 +492,9 @@ SELECT @@ROWCOUNT;";
}
}
public IReadOnlyList<PsvDocumentSummary> LoadDocuments()
public IReadOnlyList<PsvDocumentSummary> LoadDocuments(bool loadClosedDocumentsForCurrentYear)
{
const string sql = @"
var sql = @"
SELECT
m.NNZVPV AS DocumentNumber,
MAX(m.DTPRM) AS AcceptedOn,
@@ -512,14 +512,27 @@ LEFT JOIN dbo.FRPD dep ON dep.IDFRPD = m.IDFRPD
LEFT JOIN dbo.FRPD ownerOrg ON ownerOrg.IDFRPD = z.IDFRPDV
WHERE NULLIF(LTRIM(RTRIM(m.NNZVPV)), N'') IS NOT NULL
GROUP BY m.NNZVPV
HAVING MAX(m.DTVDM) IS NULL
ORDER BY MAX(m.DTPRM) DESC, m.NNZVPV DESC;";
HAVING " + (loadClosedDocumentsForCurrentYear
? "MAX(m.DTVDM) >= @IssuedFrom AND MAX(m.DTVDM) < @IssuedTo"
: "MAX(m.DTVDM) IS NULL") + @"
ORDER BY " + (loadClosedDocumentsForCurrentYear
? "MAX(m.DTVDM) DESC"
: "MAX(m.DTPRM) DESC") + @", m.NNZVPV DESC;";
var documents = new List<PsvDocumentSummary>();
var today = DateTime.Today;
var currentYearStart = new DateTime(today.Year, 1, 1);
var nextYearStart = currentYearStart.AddYears(1);
using (var connection = CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
if (loadClosedDocumentsForCurrentYear)
{
command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart;
command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart;
}
connection.Open();
command.CommandTimeout = 60;

View File

@@ -49,6 +49,7 @@ namespace XLAB
if (SetProperty(ref _acceptedOn, value))
{
OnPropertyChanged("AcceptedMonthGroup");
RaiseOpenDocumentTimelinePropertiesChanged();
}
}
}
@@ -109,7 +110,13 @@ namespace XLAB
public DateTime? IssuedOn
{
get { return _issuedOn; }
set { SetProperty(ref _issuedOn, value); }
set
{
if (SetProperty(ref _issuedOn, value))
{
RaiseOpenDocumentTimelinePropertiesChanged();
}
}
}
public int ItemCount
@@ -123,6 +130,52 @@ namespace XLAB
get { return _passedCount; }
set { SetProperty(ref _passedCount, value); }
}
public DateTime? DueOn
{
get { return AcceptedOn.HasValue ? AcceptedOn.Value.Date.AddDays(30) : (DateTime?)null; }
}
public bool IsOpenDocumentOverdue
{
get
{
return !IssuedOn.HasValue
&& DueOn.HasValue
&& DateTime.Today.Date >= DueOn.Value.Date;
}
}
public bool IsOpenDocumentAtTwentyDays
{
get
{
return !IssuedOn.HasValue
&& AcceptedOn.HasValue
&& !IsOpenDocumentOverdue
&& (DateTime.Today.Date - AcceptedOn.Value.Date).TotalDays >= 20;
}
}
public bool IsOpenDocumentAtTenDays
{
get
{
return !IssuedOn.HasValue
&& AcceptedOn.HasValue
&& !IsOpenDocumentOverdue
&& !IsOpenDocumentAtTwentyDays
&& (DateTime.Today.Date - AcceptedOn.Value.Date).TotalDays >= 10;
}
}
private void RaiseOpenDocumentTimelinePropertiesChanged()
{
OnPropertyChanged("DueOn");
OnPropertyChanged("IsOpenDocumentOverdue");
OnPropertyChanged("IsOpenDocumentAtTwentyDays");
OnPropertyChanged("IsOpenDocumentAtTenDays");
}
}
public sealed class PsvDocumentLine : ObservableObject
@@ -220,6 +273,15 @@ namespace XLAB
}
}
public string VerificationDateDisplay
{
get
{
var verificationDate = VerificationPerformedOn ?? VerificationDocumentDate;
return verificationDate.HasValue ? verificationDate.Value.ToString("d") : string.Empty;
}
}
public string VerificationDocumentDisplay
{
get

Binary file not shown.