using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using System.Windows.Data; using System.Windows.Input; namespace XLAB2 { internal sealed class MainWindowViewModel : ObservableObject { private readonly List _draftDocuments; private readonly IDialogService _dialogService; private readonly Dictionary> _pendingLinesByDocumentKey; private readonly PsvPrintService _printService; private readonly PsvDataService _service; private string _documentFilterText; private string _documentNumberEditor; private string _documentStatusText; private string _detailTableCountText; private string _groupFilterText; private string _groupDetailFilterText; private string _headerDepartmentName; private int _headerInstrumentCount; private DateTime? _headerIssuedOn; 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) { _service = service; _dialogService = dialogService; _printService = new PsvPrintService(); _draftDocuments = new List(); _pendingLinesByDocumentKey = new Dictionary>(StringComparer.OrdinalIgnoreCase); Customers = new ObservableCollection(); Documents = new ObservableCollection(); DocumentLines = new ObservableCollection(); DocumentGroupSummaries = new ObservableCollection(); DocumentsView = CollectionViewSource.GetDefaultView(Documents); DocumentsView.Filter = FilterDocuments; DocumentGroupsView = CollectionViewSource.GetDefaultView(DocumentGroupSummaries); DocumentGroupsView.Filter = FilterDocumentGroups; DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines); DocumentLinesView.Filter = FilterDocumentLines; 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 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; }); ResetLineVerificationCommand = new RelayCommand(delegate { ResetSelectedLineVerificationAsync(); }, delegate { return CanResetSelectedLineVerification(); }); SaveDocumentHeaderCommand = new RelayCommand(delegate { SaveDocumentAsync(); }, delegate { return CanSaveDocument(); }); DocumentStatusText = "Готово."; DetailTableCountText = "Приборов в таблице: 0."; LineStatusText = "Документ не выбран."; } public ICommand AddDocumentCommand { get; private set; } public ICommand CloneLineVerificationCommand { get; private set; } public ObservableCollection Customers { get; private set; } public string DocumentFilterText { get { return _documentFilterText; } set { if (SetProperty(ref _documentFilterText, value)) { DocumentsView.Refresh(); } } } 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 DocumentLines { get; private set; } public ICollectionView DocumentLinesView { get; private set; } public string DocumentNumberEditor { get { return _documentNumberEditor; } set { SetProperty(ref _documentNumberEditor, value); } } public ObservableCollection DocumentGroupSummaries { get; private set; } public ICollectionView DocumentGroupsView { get; private set; } public string DocumentStatusText { get { return _documentStatusText; } private set { SetProperty(ref _documentStatusText, value); } } public string DetailTableCountText { get { return _detailTableCountText; } private set { SetProperty(ref _detailTableCountText, value); } } public ObservableCollection Documents { get; private set; } public ICollectionView DocumentsView { get; private set; } public ICommand DeleteDocumentCommand { get; private set; } public ICommand DeleteSelectedLinesCommand { 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 { get { return _groupDetailFilterText; } set { if (SetProperty(ref _groupDetailFilterText, value)) { RefreshDocumentLinesView(); } } } public string HeaderDepartmentName { get { return _headerDepartmentName; } private set { SetProperty(ref _headerDepartmentName, value); } } public int HeaderInstrumentCount { get { return _headerInstrumentCount; } private set { SetProperty(ref _headerInstrumentCount, value); } } public bool IsDocumentHeaderEditable { get { return !IsBusy && SelectedDocument != null && !IsDocumentClosed(SelectedDocument); } } public DateTime? HeaderIssuedOn { get { return _headerIssuedOn; } set { SetProperty(ref _headerIssuedOn, value); } } public DateTime? HeaderReceivedOn { get { return _headerReceivedOn; } set { SetProperty(ref _headerReceivedOn, value); } } public bool IsBusy { get { return _isBusy; } private set { if (SetProperty(ref _isBusy, value)) { RaiseCommandStates(); OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsDocumentHeaderEditable"); } } } public bool IsCustomerEditable { get { return !IsBusy && SelectedDocument != null && SelectedDocument.IsDraft && GetPendingLines(SelectedDocument).Count == 0; } } public string LineStatusText { get { return _lineStatusText; } private set { SetProperty(ref _lineStatusText, value); } } public ICommand MarkLinePassedCommand { get; private set; } public ICommand MarkLineRejectedCommand { get; private set; } public ICommand OpenInstrumentPickerCommand { get; private set; } public ICommand OpenInstrumentTypePickerCommand { get; private set; } public ICommand PrintDocumentCommand { get; private set; } public ICommand PrintVerificationDocumentCommand { get; private set; } public ICommand RefreshDocumentsCommand { get; private set; } public ICommand ResetLineVerificationCommand { get; private set; } public ICommand SaveDocumentHeaderCommand { get; private set; } public int? SelectedCustomerId { get { return _selectedCustomerId; } set { if (!SetProperty(ref _selectedCustomerId, value)) { return; } ApplySelectedCustomer(); } } public PsvDocumentSummary SelectedDocument { get { return _selectedDocument; } set { if (SetProperty(ref _selectedDocument, value)) { _lastCloneSourceLine = null; FillHeaderFromSelection(); RaiseCommandStates(); OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsDocumentHeaderEditable"); LoadSelectedDocumentAsync(); } } } public PsvDocumentGroupSummary SelectedDocumentGroup { get { return _selectedDocumentGroup; } set { if (SetProperty(ref _selectedDocumentGroup, value)) { RefreshDocumentLinesView(); } } } public PsvDocumentLine SelectedDocumentLine { get { return _selectedDocumentLine; } set { if (SetProperty(ref _selectedDocumentLine, value)) { if (CanUseLineAsCloneSource(value)) { _lastCloneSourceLine = value; } RaiseCommandStates(); } } } public async Task InitializeAsync() { await ExecuteBusyOperationAsync(async delegate { await LoadCustomersCoreAsync(); await RefreshDocumentsCoreAsync(null, null); }); } private void AddDocument() { var request = _dialogService.ShowCreateDocumentDialog(new DocumentEditorResult { AcceptedOn = DateTime.Today, IssuedOn = null, DocumentNumber = string.Empty }); if (request == null) { return; } if (string.IsNullOrWhiteSpace(request.DocumentNumber)) { _dialogService.ShowWarning("Введите номер ПСВ."); return; } if (DocumentExistsInCollections(request.DocumentNumber, null)) { _dialogService.ShowWarning("ПСВ с таким номером уже есть в списке."); return; } var draft = new PsvDocumentSummary { DocumentKey = Guid.NewGuid().ToString("N"), DocumentNumber = request.DocumentNumber.Trim(), AcceptedOn = request.AcceptedOn, IssuedOn = null, CustomerName = string.Empty, CustomerId = null, DepartmentName = string.Empty, ItemCount = 0, IssuedCount = 0, PassedCount = 0, FailedCount = 0, IsDraft = true }; _draftDocuments.Add(draft); InsertDraftIntoCollection(draft); DocumentsView.Refresh(); SelectedDocument = draft; DocumentStatusText = BuildDocumentStatusText(Documents.Count); } private void ApplySelectedCustomer() { if (SelectedDocument == null || !SelectedDocument.IsDraft) { return; } SelectedDocument.CustomerId = SelectedCustomerId; var customer = Customers.FirstOrDefault(delegate(CustomerReference item) { return item.CustomerId == SelectedCustomerId; }); SelectedDocument.CustomerName = customer == null ? string.Empty : customer.CustomerName; DocumentsView.Refresh(); RaiseCommandStates(); } private bool CanSaveDocument() { if (IsBusy || SelectedDocument == null) { return false; } if (IsDocumentClosed(SelectedDocument)) { return false; } if (!SelectedDocument.IsDraft) { return true; } return GetPendingLines(SelectedDocument).Count > 0; } private bool CanDeleteSelectedGroups() { return CanModifySelectedDocument() && GetDeleteTargetGroups().Count > 0; } private bool CanDeleteSelectedLines() { return CanModifySelectedDocument() && GetDeleteTargetLines().Count > 0; } private bool CanPrintSelectedDocument() { return !IsBusy && SelectedDocument != null && !SelectedDocument.IsDraft; } private bool CanEditSelectedLineVerification() { var targetLines = GetVerificationTargetLines(); return CanModifySelectedDocument() && targetLines.Count > 0 && targetLines.All(delegate(PsvDocumentLine line) { return !HasVerificationData(line); }); } private bool CanCloneSelectedLineVerification() { var sourceLine = ResolveCloneSourceLine(); return CanModifySelectedDocument() && CanUseLineAsCloneSource(sourceLine) && GetCheckedCloneTargetLines(sourceLine).Count > 0; } private bool CanPrintSelectedVerificationDocument() { return !IsBusy && HasPrintableVerificationDocument(SelectedDocumentLine); } private bool CanResetSelectedLineVerification() { var targetLines = GetVerificationTargetLines(); 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 string BuildSerialNumbersText(IEnumerable lines) { var serialNumbers = (lines ?? Enumerable.Empty()) .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 bool HasVerificationData(PsvDocumentLine line) { return line != null && (line.IsPassed.HasValue || line.VerificationPerformedOn.HasValue || line.VerifierId.HasValue || !string.IsNullOrWhiteSpace(line.StickerNumber) || line.VerificationDocumentFormId.HasValue || line.VerificationDocumentLinkTypeId.HasValue || !string.IsNullOrWhiteSpace(line.VerificationDocumentNumber) || line.VerificationDocumentDate.HasValue || !string.IsNullOrWhiteSpace(line.RejectionReason)); } private static bool CanUseLineAsCloneSource(PsvDocumentLine line) { if (line == null || !line.IsPassed.HasValue || !line.VerifierId.HasValue) { return false; } if (!line.VerificationPerformedOn.HasValue && !line.VerificationDocumentDate.HasValue) { return false; } if (!line.IsPassed.Value && (string.IsNullOrWhiteSpace(line.RejectionReason) || string.IsNullOrWhiteSpace(line.VerificationDocumentNumber))) { return false; } if (!string.IsNullOrWhiteSpace(line.VerificationDocumentNumber) && (!line.VerificationDocumentFormId.HasValue || !line.VerificationDocumentLinkTypeId.HasValue)) { return false; } return true; } private static bool HasPrintableVerificationDocument(PsvDocumentLine line) { if (line == null || !line.IsPassed.HasValue) { return false; } if (string.IsNullOrWhiteSpace(line.VerificationDocumentNumber)) { return false; } if (!line.VerificationPerformedOn.HasValue && !line.VerificationDocumentDate.HasValue) { return false; } if (!line.IsPassed.Value && string.IsNullOrWhiteSpace(line.RejectionReason)) { return false; } return true; } private bool CanEditVerificationFromDoubleClick(PsvDocumentLine line) { return CanModifySelectedDocument() && line != null && line.IsPassed.HasValue && HasVerificationData(line); } private List GetCheckedDocumentLines() { return DocumentLinesView.Cast() .OfType() .Where(delegate(PsvDocumentLine line) { return line.IsBatchSelected; }) .ToList(); } private List GetCheckedDocumentGroups() { return DocumentGroupSummaries .Where(delegate(PsvDocumentGroupSummary group) { return group.IsBatchSelected; }) .ToList(); } private List GetDeleteTargetGroups() { var checkedGroups = GetCheckedDocumentGroups(); if (checkedGroups.Count > 0) { return checkedGroups; } return SelectedDocumentGroup == null ? new List() : new List { SelectedDocumentGroup }; } private List GetVerificationTargetLines() { var checkedLines = GetCheckedDocumentLines(); if (checkedLines.Count > 0) { return checkedLines; } return SelectedDocumentLine == null ? new List() : new List { SelectedDocumentLine }; } private List GetDeleteTargetLines() { var checkedLines = GetCheckedDocumentLines(); if (checkedLines.Count > 0) { return checkedLines; } return SelectedDocumentLine == null ? new List() : new List { SelectedDocumentLine }; } private List GetCheckedCloneTargetLines(PsvDocumentLine sourceLine) { if (sourceLine == null) { return new List(); } 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(ObservableCollection collection) { collection.Clear(); } private void ClearDocumentLines() { foreach (var line in DocumentLines.ToList()) { UnsubscribeFromDocumentLine(line); } ClearCollections(DocumentLines); HeaderInstrumentCount = 0; } private void ClearDocumentGroups() { foreach (var group in DocumentGroupSummaries.ToList()) { UnsubscribeFromDocumentGroup(group); } ClearCollections(DocumentGroupSummaries); } private void SubscribeToDocumentLine(PsvDocumentLine line) { if (line == null) { return; } line.PropertyChanged -= DocumentLine_PropertyChanged; line.PropertyChanged += DocumentLine_PropertyChanged; } private void SubscribeToDocumentGroup(PsvDocumentGroupSummary group) { if (group == null) { return; } group.PropertyChanged -= DocumentGroup_PropertyChanged; group.PropertyChanged += DocumentGroup_PropertyChanged; } private void UnsubscribeFromDocumentLine(PsvDocumentLine line) { if (line == null) { return; } line.PropertyChanged -= DocumentLine_PropertyChanged; } private void UnsubscribeFromDocumentGroup(PsvDocumentGroupSummary group) { if (group == null) { return; } group.PropertyChanged -= DocumentGroup_PropertyChanged; } private void DocumentLine_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (string.Equals(e.PropertyName, "IsBatchSelected", StringComparison.Ordinal)) { UpdateLineStatus(); RaiseCommandStates(); } } private void DocumentGroup_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (string.Equals(e.PropertyName, "IsBatchSelected", StringComparison.Ordinal)) { RaiseCommandStates(); } } private void ClearHeader() { DocumentNumberEditor = string.Empty; HeaderReceivedOn = null; HeaderIssuedOn = null; HeaderDepartmentName = string.Empty; SelectedCustomerId = null; } private VerificationEditSeed CreateVerificationSeed(PsvDocumentLine line, bool isPassed, IReadOnlyList documentForms) { var selectedForm = documentForms == null ? null : documentForms.FirstOrDefault(delegate(DocumentFormReference item) { return item.DocumentFormId == line.VerificationDocumentFormId; }); return new VerificationEditSeed { DocumentForm = selectedForm, IsPassed = isPassed, RejectionReason = isPassed ? string.Empty : line.RejectionReason, StickerNumber = isPassed ? line.StickerNumber : string.Empty, VerificationDate = line.VerificationPerformedOn ?? line.VerificationDocumentDate ?? DateTime.Today, VerificationDocumentNumber = line.VerificationDocumentNumber, VerifierId = line.VerifierId }; } private CloneVerificationSeed CreateCloneVerificationSeed(PsvDocumentLine sourceLine) { var documentDisplay = string.IsNullOrWhiteSpace(sourceLine.VerificationDocumentDisplay) ? "не указан" : sourceLine.VerificationDocumentDisplay; var rejectionPart = sourceLine.IsPassed == false && !string.IsNullOrWhiteSpace(sourceLine.RejectionReason) ? string.Format(" Причина: {0}.", sourceLine.RejectionReason.Trim()) : string.Empty; return new CloneVerificationSeed { SourceSerialNumber = sourceLine.SerialNumber, VerificationSummary = string.Format( "Результат: {0}. Дата поверки: {1:d}. Поверитель: {2}. Документ: {3}.{4}", sourceLine.ResultText, sourceLine.VerificationPerformedOn ?? sourceLine.VerificationDocumentDate ?? DateTime.Today, string.IsNullOrWhiteSpace(sourceLine.VerifierName) ? "не указан" : sourceLine.VerifierName, documentDisplay, rejectionPart) }; } private VerificationEditResult CreateVerificationResultFromLine(PsvDocumentLine sourceLine) { return new VerificationEditResult { DocumentFormId = string.IsNullOrWhiteSpace(sourceLine.VerificationDocumentNumber) ? 0 : sourceLine.VerificationDocumentFormId.GetValueOrDefault(), DocumentLinkTypeId = string.IsNullOrWhiteSpace(sourceLine.VerificationDocumentNumber) ? 0 : sourceLine.VerificationDocumentLinkTypeId.GetValueOrDefault(), IsPassed = sourceLine.IsPassed.GetValueOrDefault(), RejectionReason = string.IsNullOrWhiteSpace(sourceLine.RejectionReason) ? string.Empty : sourceLine.RejectionReason.Trim(), StickerNumber = string.IsNullOrWhiteSpace(sourceLine.StickerNumber) ? string.Empty : sourceLine.StickerNumber.Trim(), VerificationDate = sourceLine.VerificationPerformedOn ?? sourceLine.VerificationDocumentDate ?? DateTime.Today, VerificationDocumentNumber = string.IsNullOrWhiteSpace(sourceLine.VerificationDocumentNumber) ? string.Empty : sourceLine.VerificationDocumentNumber.Trim(), VerifierId = sourceLine.VerifierId.GetValueOrDefault(), VerifierName = string.IsNullOrWhiteSpace(sourceLine.VerifierName) ? string.Empty : sourceLine.VerifierName }; } private bool Contains(string source, string filter) { return !string.IsNullOrWhiteSpace(source) && source.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0; } private static bool BelongsToSameGroup(PsvDocumentLine candidate, PsvDocumentLine sourceLine) { return candidate != null && sourceLine != null && string.Equals(candidate.InstrumentType ?? string.Empty, sourceLine.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(candidate.RangeText ?? string.Empty, sourceLine.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(candidate.RegistryNumber ?? string.Empty, sourceLine.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase); } private static string NormalizeSerialNumber(string serialNumber) { return string.IsNullOrWhiteSpace(serialNumber) ? string.Empty : serialNumber.Trim(); } private static string BuildSerialNumberPreview(IEnumerable serialNumbers) { var materialized = serialNumbers == null ? new List() : serialNumbers .Where(delegate(string serialNumber) { return !string.IsNullOrWhiteSpace(serialNumber); }) .ToList(); var preview = materialized.Take(5).ToList(); if (preview.Count == 0) { return string.Empty; } var text = string.Join(", ", preview); if (materialized.Count > preview.Count) { text += ", ..."; } return text; } private string BuildOpenDocumentConflictMessage(IEnumerable conflicts) { var materializedConflicts = NormalizeOpenDocumentConflicts(conflicts); if (materializedConflicts.Count == 0) { return "Прибор уже находится в другой открытой ПСВ этого заказчика."; } var preview = materializedConflicts .Take(5) .Select(delegate(OpenDocumentConflictInfo conflict) { return string.Format("зав. № {0} -> {1}", conflict.SerialNumber, conflict.DocumentNumber); }) .ToList(); var suffix = materializedConflicts.Count > preview.Count ? string.Format(" Еще конфликтов: {0}.", materializedConflicts.Count - preview.Count) : string.Empty; return string.Format( "Приборы уже находятся в других открытых ПСВ этого заказчика: {0}.{1}", string.Join("; ", preview.ToArray()), suffix); } private List FindOpenDocumentConflicts(IEnumerable candidateLines) { if (SelectedDocument == null || !SelectedDocument.CustomerId.HasValue || candidateLines == null) { return new List(); } var conflicts = FindPendingOpenDocumentConflicts(SelectedDocument, candidateLines); conflicts.AddRange(_service.FindOpenDocumentConflicts( SelectedDocument.CustomerId.Value, SelectedDocument.IsDraft ? null : SelectedDocument.DocumentNumber, candidateLines)); return NormalizeOpenDocumentConflicts(conflicts); } private List FindPendingOpenDocumentConflicts(PsvDocumentSummary currentDocument, IEnumerable candidateLines) { if (currentDocument == null || !currentDocument.CustomerId.HasValue || candidateLines == null) { return new List(); } var candidateKeys = new HashSet( candidateLines .Where(delegate(PsvDocumentLine line) { return line != null && line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber); }) .Select(delegate(PsvDocumentLine line) { return line.OpenDocumentConflictKey; }), StringComparer.OrdinalIgnoreCase); if (candidateKeys.Count == 0) { return new List(); } var conflicts = new List(); foreach (var pendingLinesByDocument in _pendingLinesByDocumentKey) { if (string.Equals(pendingLinesByDocument.Key, currentDocument.DocumentKey, StringComparison.OrdinalIgnoreCase)) { continue; } var otherDocument = Documents.FirstOrDefault(delegate(PsvDocumentSummary document) { return string.Equals(document.DocumentKey, pendingLinesByDocument.Key, StringComparison.OrdinalIgnoreCase); }); if (otherDocument == null || !otherDocument.CustomerId.HasValue || otherDocument.CustomerId.Value != currentDocument.CustomerId.Value) { continue; } foreach (var otherLine in pendingLinesByDocument.Value.Where(delegate(PsvDocumentLine line) { return line != null && line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber); })) { if (!candidateKeys.Contains(otherLine.OpenDocumentConflictKey)) { continue; } conflicts.Add(new OpenDocumentConflictInfo { DocumentNumber = otherDocument.DocumentNumber, TypeSizeId = otherLine.TypeSizeId, SerialNumber = otherLine.SerialNumber }); } } return NormalizeOpenDocumentConflicts(conflicts); } private static List NormalizeOpenDocumentConflicts(IEnumerable conflicts) { return (conflicts ?? Enumerable.Empty()) .Where(delegate(OpenDocumentConflictInfo conflict) { return conflict != null; }) .GroupBy(delegate(OpenDocumentConflictInfo conflict) { return string.Format( "{0}|{1}|{2}", conflict.DocumentNumber ?? string.Empty, conflict.TypeSizeId, conflict.SerialNumber ?? string.Empty); }, StringComparer.OrdinalIgnoreCase) .Select(delegate(IGrouping group) { return group.First(); }) .OrderBy(delegate(OpenDocumentConflictInfo conflict) { return conflict.SerialNumber ?? string.Empty; }) .ThenBy(delegate(OpenDocumentConflictInfo conflict) { return conflict.DocumentNumber ?? string.Empty; }) .ToList(); } private PsvDocumentLine CreatePendingLine(AvailableInstrumentItem item) { return new PsvDocumentLine { CardId = 0, InstrumentId = item.InstrumentId, TypeSizeId = item.TypeSizeId, SerialNumber = item.SerialNumber, InventoryNumber = item.InventoryNumber, CustomerName = item.CustomerName, InstrumentType = item.InstrumentType, InstrumentName = item.InstrumentName, MeasurementArea = item.MeasurementArea, RangeText = item.RangeText, RegistryNumber = item.RegistryNumber, AccuracyText = item.AccuracyText, VerificationType = string.Empty, PeriodMonths = 0, AcceptedOn = HeaderReceivedOn, IssuedOn = null, IsPassed = null, VerificationPerformedOn = null, VerifierId = null, VerifierName = string.Empty, StickerNumber = string.Empty, VerificationDocumentFormId = null, VerificationDocumentLinkTypeId = null, VerificationDocumentNumber = string.Empty, VerificationDocumentDate = null, RejectionReason = string.Empty, Notes = string.Empty, IsPendingInsert = true }; } private PsvDocumentLine CreatePendingTypeLine(AvailableInstrumentItem item, string serialNumber) { return new PsvDocumentLine { CardId = 0, InstrumentId = 0, TypeSizeId = item.TypeSizeId, SerialNumber = string.IsNullOrWhiteSpace(serialNumber) ? string.Empty : serialNumber.Trim(), InventoryNumber = string.Empty, CustomerName = SelectedDocument == null ? string.Empty : SelectedDocument.CustomerName, InstrumentType = item.InstrumentType, InstrumentName = item.InstrumentName, MeasurementArea = item.MeasurementArea, RangeText = item.RangeText, RegistryNumber = item.RegistryNumber, AccuracyText = item.AccuracyText, VerificationType = string.Empty, PeriodMonths = 0, AcceptedOn = HeaderReceivedOn, IssuedOn = null, IsPassed = null, VerificationPerformedOn = null, VerifierId = null, VerifierName = string.Empty, StickerNumber = string.Empty, VerificationDocumentFormId = null, VerificationDocumentLinkTypeId = null, VerificationDocumentNumber = string.Empty, VerificationDocumentDate = null, RejectionReason = string.Empty, Notes = string.Empty, IsPendingInsert = true }; } private static void ApplyVerificationResultCore(PsvDocumentLine line, VerificationEditResult result) { if (line == null || result == null) { return; } line.IsPassed = result.IsPassed; line.VerificationPerformedOn = result.VerificationDate; line.VerifierId = result.VerifierId; line.VerifierName = result.VerifierName; line.StickerNumber = result.IsPassed ? result.StickerNumber : string.Empty; line.VerificationDocumentFormId = string.IsNullOrWhiteSpace(result.VerificationDocumentNumber) ? (int?)null : result.DocumentFormId; line.VerificationDocumentLinkTypeId = string.IsNullOrWhiteSpace(result.VerificationDocumentNumber) ? (int?)null : result.DocumentLinkTypeId; line.VerificationDocumentNumber = result.VerificationDocumentNumber; line.VerificationDocumentDate = result.VerificationDate; line.RejectionReason = result.IsPassed ? string.Empty : result.RejectionReason; } private void ApplyVerificationResultToLine(PsvDocumentLine line, VerificationEditResult result) { ApplyVerificationResultCore(line, result); RefreshAfterLineVerificationChanged(line); } private static void ClearVerificationFromLineCore(PsvDocumentLine line) { if (line == null) { return; } line.IsPassed = null; line.VerificationPerformedOn = null; line.VerifierId = null; line.VerifierName = string.Empty; line.StickerNumber = string.Empty; line.VerificationDocumentFormId = null; line.VerificationDocumentLinkTypeId = null; line.VerificationDocumentNumber = string.Empty; line.VerificationDocumentDate = null; line.RejectionReason = string.Empty; } private void ClearVerificationFromLine(PsvDocumentLine line) { ClearVerificationFromLineCore(line); RefreshAfterLineVerificationChanged(line); } private void CloneSelectedLineVerificationAsync() { var sourceLine = ResolveCloneSourceLine(); if (!CanUseLineAsCloneSource(sourceLine)) { _dialogService.ShowWarning("Выберите строку-источник с заполненной поверкой. Источник можно выделить строкой или отметить единственную подходящую строку чекбоксом."); return; } var checkedLines = GetCheckedDocumentLines(); if (checkedLines.Count == 0) { _dialogService.ShowWarning("Отметьте чекбоксами строки, в которые нужно склонировать поверочные данные."); return; } 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, includedSourceSerialNumber, true); return; } var result = CreateVerificationResultFromLine(sourceLine); var pendingLines = targetLines.Where(delegate(PsvDocumentLine line) { return line.IsPendingInsert; }).ToList(); var persistedCardIds = targetLines .Where(delegate(PsvDocumentLine line) { return !line.IsPendingInsert; }) .Select(delegate(PsvDocumentLine line) { return line.CardId; }) .Distinct() .ToList(); if (persistedCardIds.Count == 0) { foreach (var pendingLine in pendingLines) { ApplyVerificationResultCore(pendingLine, result); } RefreshAfterLineVerificationChanged(sourceLine); ShowCloneVerificationResult(targetLines.Count, skippedWithExistingVerificationCount, includedSourceSerialNumber, false); return; } RunBusyOperation(async delegate { await _service.SaveLineVerificationAsync(persistedCardIds, result); foreach (var pendingLine in pendingLines) { ApplyVerificationResultCore(pendingLine, result); } await ReloadSelectedDocumentLinesAsync(); ShowCloneVerificationResult(targetLines.Count, skippedWithExistingVerificationCount, includedSourceSerialNumber, false); }); } private void ShowCloneVerificationResult( int clonedCount, int skippedWithExistingVerificationCount, bool includedSourceSerialNumber, bool showWarning) { var messages = new List(); if (clonedCount > 0) { messages.Add(string.Format("Клонировано поверок: {0}.", clonedCount)); } if (skippedWithExistingVerificationCount > 0) { messages.Add(string.Format("Пропущено строк с уже заполненной поверкой: {0}.", skippedWithExistingVerificationCount)); } if (includedSourceSerialNumber) { messages.Add("Номер строки-источника пропущен."); } if (messages.Count == 0) { messages.Add("Подходящих отмеченных строк для клонирования не найдено."); } var message = string.Join(" ", messages.ToArray()); if (showWarning || clonedCount == 0) { _dialogService.ShowWarning(message); return; } _dialogService.ShowInfo(message); } private void EditLineVerificationAsync(bool isPassed) { var targetLines = GetVerificationTargetLines(); if (targetLines.Count == 0) { return; } 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 targetLines, bool isPassed) { IReadOnlyList documentForms; IReadOnlyList verifiers; try { IsBusy = true; verifiers = await _service.LoadVerifiersAsync(); documentForms = await _service.LoadVerificationDocumentFormsAsync(isPassed); } catch (Exception ex) { _dialogService.ShowError(ex.Message); return; } finally { IsBusy = false; } var seed = CreateVerificationSeed(targetLines[0], isPassed, documentForms); var result = _dialogService.ShowVerificationDialog(seed, verifiers, documentForms); if (result == null) { return; } var pendingLines = targetLines.Where(delegate(PsvDocumentLine line) { return line.IsPendingInsert; }).ToList(); var persistedCardIds = targetLines.Where(delegate(PsvDocumentLine line) { return !line.IsPendingInsert; }) .Select(delegate(PsvDocumentLine line) { return line.CardId; }) .Distinct() .ToList(); if (persistedCardIds.Count == 0) { foreach (var pendingLine in pendingLines) { ApplyVerificationResultCore(pendingLine, result); pendingLine.IsBatchSelected = false; } RefreshAfterLineVerificationChanged(targetLines[0]); return; } RunBusyOperation(async delegate { await _service.SaveLineVerificationAsync(persistedCardIds, result); foreach (var pendingLine in pendingLines) { ApplyVerificationResultCore(pendingLine, result); pendingLine.IsBatchSelected = false; } await ReloadSelectedDocumentLinesAsync(); }); } private void RefreshAfterLineVerificationChanged(PsvDocumentLine line) { var previousGroup = SelectedDocumentGroup; RebuildDocumentGroupSummaries(DocumentLines); SelectedDocumentGroup = FindMatchingGroup(previousGroup) ?? DocumentGroupSummaries.FirstOrDefault(); RefreshDocumentLinesView(); SelectedDocumentLine = line; RaiseCommandStates(); } private void ResetSelectedLineVerificationAsync() { var targetLines = GetVerificationTargetLines(); if (targetLines.Count == 0) { return; } var pendingLines = targetLines.Where(delegate(PsvDocumentLine line) { return line.IsPendingInsert; }).ToList(); var persistedCardIds = targetLines.Where(delegate(PsvDocumentLine line) { return !line.IsPendingInsert; }) .Select(delegate(PsvDocumentLine line) { return line.CardId; }) .Distinct() .ToList(); if (persistedCardIds.Count == 0) { foreach (var pendingLine in pendingLines) { ClearVerificationFromLineCore(pendingLine); pendingLine.IsBatchSelected = false; } RefreshAfterLineVerificationChanged(targetLines[0]); return; } RunBusyOperation(async delegate { await _service.ResetLineVerificationAsync(persistedCardIds); foreach (var pendingLine in pendingLines) { ClearVerificationFromLineCore(pendingLine); pendingLine.IsBatchSelected = false; } await ReloadSelectedDocumentLinesAsync(); }); } private void DeleteDocumentAsync() { if (SelectedDocument == null) { return; } var selectedDocument = SelectedDocument; if (!_dialogService.Confirm(string.Format("Удалить ПСВ \"{0}\"?", selectedDocument.DocumentNumber))) { return; } if (selectedDocument.IsDraft) { DeleteDraftDocument(selectedDocument); return; } RunBusyOperation(async delegate { var result = await _service.DeleteDocumentAsync(selectedDocument.DocumentNumber); _pendingLinesByDocumentKey.Remove(selectedDocument.DocumentKey); await RefreshDocumentsCoreAsync(null, null); _dialogService.ShowInfo( string.Format( "Удалено строк EKZMKFCTVL: {0}. Удалено строк EKZMK: {1}. Удалено связанных DMS: {2}.", result.DeletedEkzMkFctvlCount, result.DeletedEkzMkCount, result.DeletedDmsCount)); }); } private void DeleteDraftDocument(PsvDocumentSummary draft) { _draftDocuments.RemoveAll(delegate(PsvDocumentSummary item) { return item.DocumentKey == draft.DocumentKey; }); _pendingLinesByDocumentKey.Remove(draft.DocumentKey); Documents.Remove(draft); DocumentsView.Refresh(); SelectedDocument = Documents.Count > 0 ? Documents[0] : null; DocumentStatusText = BuildDocumentStatusText(Documents.Count); } private void PrintSelectedDocumentAsync() { if (SelectedDocument == null) { return; } var selectedDocument = SelectedDocument; if (selectedDocument.IsDraft) { _dialogService.ShowWarning("Черновик нельзя распечатать. Сначала сохраните ПСВ."); return; } if (string.IsNullOrWhiteSpace(selectedDocument.DocumentNumber)) { _dialogService.ShowWarning("Для печати не указан номер ПСВ."); return; } RunBusyOperation(async delegate { var persistedLines = await _service.LoadDocumentLinesAsync(selectedDocument.DocumentNumber); var linesToPrint = MergeDocumentLinesForPrint(selectedDocument, persistedLines); if (linesToPrint.Count == 0) { _dialogService.ShowWarning("В выбранном ПСВ нет строк для печати."); return; } _printService.PrintDocument(selectedDocument, linesToPrint); }); } private void DeleteSelectedGroupsAsync() { if (SelectedDocument == null) { return; } var targetGroups = GetDeleteTargetGroups(); if (targetGroups.Count == 0) { return; } var targetLines = DocumentLines .Where(delegate(PsvDocumentLine line) { return targetGroups.Any(delegate(PsvDocumentGroupSummary group) { return group.Matches(line); }); }) .ToList(); if (targetLines.Count == 0) { _dialogService.ShowWarning("Для удаления не найдено строк выбранных групп."); return; } if (!_dialogService.Confirm(string.Format( "Удалить из ПСВ \"{0}\" групп: {1}, строк приборов: {2}?", SelectedDocument.DocumentNumber, targetGroups.Count, 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 line.IsPendingInsert && targetGroups.Any(delegate(PsvDocumentGroupSummary group) { return group.Matches(line); }); }) .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 _service.DeleteDocumentGroupsAsync(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) { 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(); 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 _service.DeleteDocumentGroupsAsync(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) { 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(); 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 bool DocumentExistsInCollections(string documentNumber, string excludeDocumentKey) { return Documents.Any(delegate(PsvDocumentSummary document) { return !string.Equals(document.DocumentKey, excludeDocumentKey, StringComparison.OrdinalIgnoreCase) && string.Equals(document.DocumentNumber, documentNumber, StringComparison.OrdinalIgnoreCase); }); } private void PrintSelectedVerificationDocumentAsync() { var selectedLine = SelectedDocumentLine; if (selectedLine == null) { _dialogService.ShowWarning("Сначала выберите строку прибора."); return; } if (!HasPrintableVerificationDocument(selectedLine)) { _dialogService.ShowWarning("Для выбранной строки не указан печатаемый документ по поверке."); return; } RunBusyOperation(delegate { _printService.PrintVerificationDocument(selectedLine); return Task.FromResult(0); }); } private static void UpdateDocumentSummaryFromLines(PsvDocumentSummary document, IEnumerable lines) { if (document == null) { return; } var materializedLines = lines == null ? new List() : lines.ToList(); document.ItemCount = materializedLines.Count; document.PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }); document.FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }); document.IssuedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IssuedOn.HasValue; }); document.SerialNumbersText = BuildSerialNumbersText(materializedLines); } private async Task ExecuteBusyOperationAsync(Func operation) { if (IsBusy) { return; } try { IsBusy = true; await operation(); } catch (Exception ex) { _dialogService.ShowError(ex.Message); } finally { IsBusy = false; } } private async void RunBusyOperation(Func operation) { await ExecuteBusyOperationAsync(operation); } private bool FilterDocuments(object item) { var document = item as PsvDocumentSummary; if (document == null) { return false; } if (!string.IsNullOrWhiteSpace(DocumentFilterText) && !Contains(document.DocumentNumber, DocumentFilterText) && !Contains(document.CustomerName, DocumentFilterText) && !Contains(document.SerialNumbersText, DocumentFilterText)) { return false; } 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.RangeText, GroupFilterText) || Contains(group.RegistryNumber, GroupFilterText) || Contains(group.SerialNumbersText, GroupFilterText); } 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; if (line == null || SelectedDocumentGroup == null) { return false; } if (!SelectedDocumentGroup.Matches(line)) { return false; } if (string.IsNullOrWhiteSpace(GroupDetailFilterText)) { return true; } return Contains(line.SerialNumber, GroupDetailFilterText); } private PsvDocumentGroupSummary FindMatchingGroup(PsvDocumentGroupSummary group) { if (group == null) { return null; } return DocumentGroupSummaries.FirstOrDefault(delegate(PsvDocumentGroupSummary current) { return AreSameGroup(current, group); }); } private PsvDocumentGroupSummary FindMatchingVisibleGroup(PsvDocumentGroupSummary group) { if (group == null) { return null; } return GetVisibleDocumentGroups().FirstOrDefault(delegate(PsvDocumentGroupSummary current) { return AreSameGroup(current, group); }); } private List GetVisibleDocumentGroups() { return DocumentGroupsView == null ? new List() : DocumentGroupsView.Cast().OfType().ToList(); } private static bool AreSameGroup(PsvDocumentGroupSummary left, PsvDocumentGroupSummary right) { return left != null && right != null && 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.RegistryNumber ?? string.Empty, right.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase); } private void FillHeaderFromSelection() { if (SelectedDocument == null) { ClearHeader(); return; } DocumentNumberEditor = SelectedDocument.DocumentNumber; HeaderReceivedOn = SelectedDocument.AcceptedOn; HeaderIssuedOn = SelectedDocument.IssuedOn; HeaderDepartmentName = SelectedDocument.DepartmentName; SelectedCustomerId = SelectedDocument.CustomerId; } private List GetPendingLines(PsvDocumentSummary document) { if (document == null) { return new List(); } List lines; return _pendingLinesByDocumentKey.TryGetValue(document.DocumentKey, out lines) ? lines : new List(); } private List MergeDocumentLinesForPrint(PsvDocumentSummary document, IEnumerable persistedLines) { var mergedLines = new List(); if (persistedLines != null) { mergedLines.AddRange(persistedLines); } mergedLines.AddRange(GetPendingLines(document)); return mergedLines; } private PsvDocumentSummary CreateSavedDocumentSummaryForPrint(PsvDocumentSummary source, DocumentEditorResult request) { return new PsvDocumentSummary { DocumentKey = source == null ? null : source.DocumentKey, DocumentNumber = request == null ? string.Empty : request.DocumentNumber, AcceptedOn = request == null ? (DateTime?)null : request.AcceptedOn, IssuedOn = request == null ? (DateTime?)null : request.IssuedOn, CustomerId = request == null ? null : request.CustomerId, CustomerName = ResolveCustomerNameForPrint(source, request), DepartmentName = source == null ? string.Empty : source.DepartmentName, IsDraft = false }; } private string ResolveCustomerNameForPrint(PsvDocumentSummary source, DocumentEditorResult request) { if (source != null && !string.IsNullOrWhiteSpace(source.CustomerName)) { return source.CustomerName; } if (request != null && request.CustomerId.HasValue) { var customer = Customers.FirstOrDefault(delegate(CustomerReference item) { return item.CustomerId == request.CustomerId.Value; }); if (customer != null) { return customer.CustomerName; } } return string.Empty; } private void InsertDraftIntoCollection(PsvDocumentSummary draft) { var insertIndex = 0; while (insertIndex < Documents.Count && (Documents[insertIndex].AcceptedOn ?? DateTime.MinValue) >= (draft.AcceptedOn ?? DateTime.MinValue)) { insertIndex++; } Documents.Insert(insertIndex, draft); } private async Task LoadCustomersCoreAsync() { var customers = await _service.LoadCustomersAsync(); ClearCollections(Customers); foreach (var customer in customers) { Customers.Add(customer); } } private async void LoadSelectedDocumentAsync() { if (SelectedDocument == null) { ClearDocumentLines(); ClearDocumentGroups(); SelectedDocumentGroup = null; SelectedDocumentLine = null; LineStatusText = "Документ не выбран."; return; } try { await ReloadSelectedDocumentLinesAsync(); } catch (Exception ex) { _dialogService.ShowError(ex.Message); } } private async Task ReloadSelectedDocumentLinesAsync() { if (SelectedDocument == null) { ClearDocumentLines(); ClearDocumentGroups(); SelectedDocumentGroup = null; SelectedDocumentLine = null; return; } if (SelectedDocument.IsDraft) { ApplyDocumentLines(GetPendingLines(SelectedDocument), SelectedDocumentGroup); return; } LineStatusText = "Загрузка строк документа..."; var previousGroup = SelectedDocumentGroup; var documentNumber = SelectedDocument.DocumentNumber; var persistedLines = await _service.LoadDocumentLinesAsync(documentNumber); var mergedLines = persistedLines.Concat(GetPendingLines(SelectedDocument)).ToList(); ApplyDocumentLines(mergedLines, previousGroup); } private void OpenInstrumentPickerAsync() { if (SelectedDocument == null) { return; } if (!SelectedDocument.CustomerId.HasValue) { _dialogService.ShowWarning("Сначала выберите заказчика в ПСВ."); return; } OpenInstrumentPickerCoreAsync(SelectedDocument.CustomerId.Value, SelectedDocument.CustomerName); } private async void OpenInstrumentPickerCoreAsync(int customerId, string customerName) { IReadOnlyList instruments; try { IsBusy = true; instruments = await _service.LoadCustomerInstrumentsAsync(customerId); } catch (Exception ex) { _dialogService.ShowError(ex.Message); return; } finally { IsBusy = false; } var selectedItems = _dialogService.ShowInstrumentPickerDialog(customerName, instruments); if (selectedItems == null || selectedItems.Count == 0) { return; } AddSelectedInstruments(selectedItems); } private void OpenInstrumentTypePickerAsync() { if (SelectedDocument == null) { return; } if (!SelectedDocument.CustomerId.HasValue) { _dialogService.ShowWarning("Сначала выберите заказчика в ПСВ."); return; } OpenInstrumentTypePickerCoreAsync(SelectedDocument.CustomerName); } private async void OpenInstrumentTypePickerCoreAsync(string customerName) { IReadOnlyList instrumentTypes; try { IsBusy = true; instrumentTypes = await _service.LoadInstrumentTypesAsync(); } catch (Exception ex) { _dialogService.ShowError(ex.Message); return; } finally { IsBusy = false; } var result = _dialogService.ShowInstrumentTypeDialog(customerName, instrumentTypes); if (result == null || result.TypeItem == null) { return; } AddSelectedTypeInstrument(result); } private void AddSelectedInstruments(IReadOnlyList selectedItems) { if (SelectedDocument == null) { return; } List pendingLines; if (!_pendingLinesByDocumentKey.TryGetValue(SelectedDocument.DocumentKey, out pendingLines)) { pendingLines = new List(); _pendingLinesByDocumentKey[SelectedDocument.DocumentKey] = pendingLines; } var candidateLines = selectedItems .Where(delegate(AvailableInstrumentItem item) { return item != null; }) .Select(CreatePendingLine) .ToList(); List openDocumentConflicts; try { openDocumentConflicts = FindOpenDocumentConflicts(candidateLines); } catch (Exception ex) { _dialogService.ShowError(ex.Message); return; } var openConflictKeys = new HashSet( openDocumentConflicts.Select(delegate(OpenDocumentConflictInfo conflict) { return conflict.OpenDocumentConflictKey; }), StringComparer.OrdinalIgnoreCase); var duplicateKeys = new HashSet(DocumentLines.Select(delegate(PsvDocumentLine line) { return line.DuplicateKey; }), StringComparer.OrdinalIgnoreCase); var addedCount = 0; var skippedDuplicateCount = 0; var skippedOpenDocumentCount = 0; var skippedWithoutTemplateCount = 0; foreach (var item in selectedItems) { if (item == null) { continue; } if (openConflictKeys.Contains(PsvDocumentLine.BuildOpenDocumentConflictKey(item.TypeSizeId, item.SerialNumber))) { skippedOpenDocumentCount++; continue; } if (!item.HasTemplate) { skippedWithoutTemplateCount++; continue; } var duplicateKey = PsvDocumentLine.BuildDuplicateKey(item.InstrumentType, item.RangeText, item.RegistryNumber, item.SerialNumber); if (duplicateKeys.Contains(duplicateKey)) { skippedDuplicateCount++; continue; } pendingLines.Add(CreatePendingLine(item)); duplicateKeys.Add(duplicateKey); addedCount++; } if (SelectedDocument.IsDraft) { UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines); } LoadSelectedDocumentAsync(); var messages = new List(); if (addedCount > 0) { messages.Add(string.Format("Добавлено приборов: {0}.", addedCount)); } if (skippedDuplicateCount > 0) { messages.Add(string.Format("Исключено дублей: {0}.", skippedDuplicateCount)); } if (skippedWithoutTemplateCount > 0) { messages.Add(string.Format("Пропущено без источника данных для EKZMK: {0}.", skippedWithoutTemplateCount)); } 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 && skippedOpenDocumentCount > 0) { _dialogService.ShowWarning(message); } else { _dialogService.ShowInfo(message); } } RaiseCommandStates(); OnPropertyChanged("IsCustomerEditable"); } private void AddSelectedTypeInstrument(InstrumentTypeSelectionResult result) { if (SelectedDocument == null || result == null || result.TypeItem == null) { return; } List pendingLines; if (!_pendingLinesByDocumentKey.TryGetValue(SelectedDocument.DocumentKey, out pendingLines)) { pendingLines = new List(); _pendingLinesByDocumentKey[SelectedDocument.DocumentKey] = pendingLines; } var serialNumber = string.IsNullOrWhiteSpace(result.SerialNumber) ? string.Empty : result.SerialNumber.Trim(); if (string.IsNullOrWhiteSpace(serialNumber)) { _dialogService.ShowWarning("Введите заводской номер."); return; } if (!result.TypeItem.HasTemplate) { _dialogService.ShowWarning("Выбранный тип нельзя добавить: для него не найден шаблон EKZMK, период из TPRMCP или регистрационный период TIPS."); return; } var candidateLine = CreatePendingTypeLine(result.TypeItem, serialNumber); List openDocumentConflicts; try { openDocumentConflicts = FindOpenDocumentConflicts(new[] { candidateLine }); } catch (Exception ex) { _dialogService.ShowError(ex.Message); return; } if (openDocumentConflicts.Count > 0) { _dialogService.ShowWarning(BuildOpenDocumentConflictMessage(openDocumentConflicts)); return; } var duplicateKey = PsvDocumentLine.BuildDuplicateKey( result.TypeItem.InstrumentType, result.TypeItem.RangeText, result.TypeItem.RegistryNumber, serialNumber); if (DocumentLines.Any(delegate(PsvDocumentLine line) { return string.Equals(line.DuplicateKey, duplicateKey, StringComparison.OrdinalIgnoreCase); })) { _dialogService.ShowWarning("Такой прибор уже есть в ПСВ."); return; } pendingLines.Add(candidateLine); if (SelectedDocument.IsDraft) { UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines); } LoadSelectedDocumentAsync(); _dialogService.ShowInfo("Прибор по типу добавлен в ПСВ."); RaiseCommandStates(); OnPropertyChanged("IsCustomerEditable"); } private void ApplyDocumentLines(IEnumerable lines, PsvDocumentGroupSummary previousGroup) { var previousLine = SelectedDocumentLine; ClearDocumentLines(); foreach (var line in lines) { SubscribeToDocumentLine(line); DocumentLines.Add(line); } RebuildDocumentGroupSummaries(DocumentLines); SelectedDocumentGroup = FindMatchingVisibleGroup(previousGroup) ?? GetVisibleDocumentGroups().FirstOrDefault(); SelectedDocumentLine = previousLine == null ? null : DocumentLines.FirstOrDefault(delegate(PsvDocumentLine line) { if (previousLine.CardId > 0 && line.CardId > 0) { return line.CardId == previousLine.CardId; } return line.IsPendingInsert && previousLine.IsPendingInsert && string.Equals(line.DuplicateKey, previousLine.DuplicateKey, StringComparison.OrdinalIgnoreCase); }); HeaderInstrumentCount = DocumentLines.Count; RefreshDocumentGroupsView(); RefreshDocumentLinesView(); RaiseCommandStates(); } private void RaiseCommandStates() { ((RelayCommand)AddDocumentCommand).RaiseCanExecuteChanged(); ((RelayCommand)CloneLineVerificationCommand).RaiseCanExecuteChanged(); ((RelayCommand)DeleteDocumentCommand).RaiseCanExecuteChanged(); ((RelayCommand)DeleteSelectedLinesCommand).RaiseCanExecuteChanged(); ((RelayCommand)DeleteSelectedGroupsCommand).RaiseCanExecuteChanged(); ((RelayCommand)MarkLinePassedCommand).RaiseCanExecuteChanged(); ((RelayCommand)MarkLineRejectedCommand).RaiseCanExecuteChanged(); ((RelayCommand)OpenInstrumentPickerCommand).RaiseCanExecuteChanged(); ((RelayCommand)OpenInstrumentTypePickerCommand).RaiseCanExecuteChanged(); ((RelayCommand)PrintDocumentCommand).RaiseCanExecuteChanged(); ((RelayCommand)PrintVerificationDocumentCommand).RaiseCanExecuteChanged(); ((RelayCommand)RefreshDocumentsCommand).RaiseCanExecuteChanged(); ((RelayCommand)ResetLineVerificationCommand).RaiseCanExecuteChanged(); ((RelayCommand)SaveDocumentHeaderCommand).RaiseCanExecuteChanged(); } private void RebuildDocumentGroupSummaries(IEnumerable lines) { var checkedGroups = DocumentGroupSummaries .Where(delegate(PsvDocumentGroupSummary group) { return group.IsBatchSelected; }) .ToList(); ClearDocumentGroups(); var groups = lines.GroupBy(line => new { InstrumentType = line.InstrumentType ?? string.Empty, RangeText = line.RangeText ?? string.Empty, RegistryNumber = line.RegistryNumber ?? string.Empty }) .OrderBy(group => group.Key.InstrumentType) .ThenBy(group => group.Key.RegistryNumber) .ThenBy(group => group.Key.RangeText) .Select(group => new PsvDocumentGroupSummary { InstrumentType = group.Key.InstrumentType, RangeText = group.Key.RangeText, RegistryNumber = group.Key.RegistryNumber, SerialNumbersText = BuildSerialNumbersText(group), IsBatchSelected = checkedGroups.Any(delegate(PsvDocumentGroupSummary previous) { 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.RegistryNumber ?? string.Empty, group.Key.RegistryNumber, StringComparison.OrdinalIgnoreCase); }), InVerificationCount = group.Count(line => !line.IsPassed.HasValue), VerifiedCount = group.Count(line => line.IsPassed.HasValue), GoodCount = group.Count(line => line.IsPassed == true), RejectedCount = group.Count(line => line.IsPassed == false) }); foreach (var group in groups) { SubscribeToDocumentGroup(group); DocumentGroupSummaries.Add(group); } } 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() { DocumentLinesView.Refresh(); UpdateLineStatus(); RaiseCommandStates(); } private void RefreshDocumentsAsync(string documentKeyToSelect, string documentNumberToSelect) { RunBusyOperation(delegate { return RefreshDocumentsCoreAsync(documentKeyToSelect, documentNumberToSelect); }); } private async Task RefreshDocumentsCoreAsync(string documentKeyToSelect, string documentNumberToSelect) { DocumentStatusText = "Загрузка списка ПСВ..."; var databaseDocuments = await _service.LoadDocumentsAsync(ShowClosedDocuments); var currentDocumentKey = documentKeyToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentKey : null); var currentDocumentNumber = documentNumberToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentNumber : null); ClearCollections(Documents); foreach (var draft in _draftDocuments .Where(delegate(PsvDocumentSummary item) { return !ShowClosedDocuments; }) .OrderByDescending(delegate(PsvDocumentSummary item) { return item.AcceptedOn ?? DateTime.MinValue; })) { Documents.Add(draft); } foreach (var document in databaseDocuments) { if (_draftDocuments.Any(delegate(PsvDocumentSummary draft) { return string.Equals(draft.DocumentNumber, document.DocumentNumber, StringComparison.OrdinalIgnoreCase); })) { continue; } Documents.Add(document); } DocumentsView.Refresh(); SelectedDocument = Documents.FirstOrDefault(delegate(PsvDocumentSummary document) { if (!string.IsNullOrWhiteSpace(currentDocumentKey) && string.Equals(document.DocumentKey, currentDocumentKey, StringComparison.OrdinalIgnoreCase)) { return true; } return !string.IsNullOrWhiteSpace(currentDocumentNumber) && string.Equals(document.DocumentNumber, currentDocumentNumber, StringComparison.OrdinalIgnoreCase); }); if (SelectedDocument == null && Documents.Count > 0) { SelectedDocument = Documents[0]; } DocumentStatusText = BuildDocumentStatusText(Documents.Count); } private void SaveDocumentAsync() { RunBusyOperation(async delegate { if (SelectedDocument == null) { _dialogService.ShowWarning("Сначала выберите документ."); return; } var selectedDocument = SelectedDocument; if (!HeaderReceivedOn.HasValue) { _dialogService.ShowWarning("Укажите дату приемки."); return; } if (string.IsNullOrWhiteSpace(DocumentNumberEditor)) { _dialogService.ShowWarning("Введите номер ПСВ."); return; } if (selectedDocument.IsDraft && !SelectedCustomerId.HasValue) { _dialogService.ShowWarning("Для новой ПСВ сначала выберите заказчика."); return; } if (DocumentExistsInCollections(DocumentNumberEditor.Trim(), selectedDocument.DocumentKey) || await _service.DocumentNumberExistsAsync(DocumentNumberEditor.Trim(), selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber)) { _dialogService.ShowWarning("ПСВ с таким номером уже существует."); return; } var pendingLines = GetPendingLines(selectedDocument) .Where(delegate(PsvDocumentLine line) { return line != null && (line.InstrumentId > 0 || (line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber))); }) .ToList(); if (selectedDocument.IsDraft && pendingLines.Count == 0) { _dialogService.ShowWarning("Черновик нельзя сохранить без строк EKZMK."); return; } var openDocumentConflicts = FindPendingOpenDocumentConflicts(selectedDocument, pendingLines); if (openDocumentConflicts.Count > 0) { _dialogService.ShowWarning(BuildOpenDocumentConflictMessage(openDocumentConflicts)); return; } var request = new DocumentEditorResult { DocumentNumber = DocumentNumberEditor.Trim(), AcceptedOn = HeaderReceivedOn.Value, IssuedOn = HeaderIssuedOn, CustomerId = selectedDocument.CustomerId ?? SelectedCustomerId }; var currentDocumentNumber = selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber; var documentKey = selectedDocument.DocumentKey; var wasDraft = selectedDocument.IsDraft; var closingNow = !selectedDocument.IssuedOn.HasValue && request.IssuedOn.HasValue; var printDocument = closingNow ? CreateSavedDocumentSummaryForPrint(selectedDocument, request) : null; var result = await Task.Run(delegate { return _service.SaveDocument(currentDocumentNumber, request, pendingLines); }); _pendingLinesByDocumentKey.Remove(documentKey); if (wasDraft) { _draftDocuments.RemoveAll(delegate(PsvDocumentSummary draft) { return draft.DocumentKey == documentKey; }); } var messages = new List(); messages.Add(string.Format("Обновлено строк EKZMK: {0}.", result.UpdatedEkzMkCount)); messages.Add(string.Format("Добавлено строк EKZMK: {0}.", result.InsertedEkzMkCount)); if (result.SkippedDuplicateCount > 0) { messages.Add(string.Format("Исключено дублей: {0}.", result.SkippedDuplicateCount)); } if (result.SkippedWithoutTemplateCount > 0) { messages.Add(string.Format("Пропущено без источника данных для EKZMK: {0}.", result.SkippedWithoutTemplateCount)); } var messageText = string.Join(" ", messages.ToArray()); if (closingNow) { var prompt = messageText + " ПСВ закрыта. Распечатать приемо-сдаточную ведомость?"; if (_dialogService.Confirm(prompt)) { var printLines = await _service.LoadDocumentLinesAsync(result.DocumentNumber); if (printDocument != null) { printDocument.DocumentNumber = result.DocumentNumber; _printService.PrintDocument(printDocument, printLines.ToList()); } } } else { _dialogService.ShowInfo(messageText); } await RefreshDocumentsCoreAsync(null, closingNow ? null : result.DocumentNumber); }); } private void UpdateLineStatus() { var visibleGroupCount = GetVisibleDocumentGroups().Count; if (SelectedDocument == null) { DetailTableCountText = "Приборов в таблице: 0."; LineStatusText = "Документ не выбран."; return; } if (DocumentGroupSummaries.Count == 0) { DetailTableCountText = "Приборов в таблице: 0."; LineStatusText = SelectedDocument.IsDraft ? "Черновик пуст. Добавьте приборы через контекстное меню таблицы групп." : "В документе нет групп приборов."; return; } if (visibleGroupCount == 0) { DetailTableCountText = "Приборов в таблице: 0."; LineStatusText = string.IsNullOrWhiteSpace(GroupFilterText) ? "Выберите группу." : string.Format("Группы по фильтру \"{0}\" не найдены.", GroupFilterText.Trim()); return; } if (SelectedDocumentGroup == null) { DetailTableCountText = "Приборов в таблице: 0."; LineStatusText = string.Format("Групп: {0}/{1}. Выберите группу.", visibleGroupCount, DocumentGroupSummaries.Count); return; } var groupLineCount = DocumentLines.Count(delegate(PsvDocumentLine line) { return SelectedDocumentGroup.Matches(line); }); var filteredCount = DocumentLines.Count(delegate(PsvDocumentLine line) { return SelectedDocumentGroup.Matches(line) && (string.IsNullOrWhiteSpace(GroupDetailFilterText) || Contains(line.SerialNumber, GroupDetailFilterText)); }); var pendingCount = DocumentLines.Count(delegate(PsvDocumentLine line) { return line.IsPendingInsert; }); DetailTableCountText = string.Format("Приборов в таблице: {0}.", filteredCount); LineStatusText = string.Format( "Групп: {0}/{1}. Приборов в выбранной группе: {2}. Отображено по фильтру: {3}. Не сохранено строк: {4}.", visibleGroupCount, DocumentGroupSummaries.Count, groupLineCount, filteredCount, pendingCount); } } }