using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using System.Windows.Input; namespace XLAB2 { internal sealed class EkzDirectoryWindowViewModel : ObservableObject { private readonly IEkzDirectoryDialogService _dialogService; private readonly EkzDirectoryService _service; private List _ekzCache; private bool _isApplyingFilter; private bool _isBusy; private string _searchText; private EkzDirectoryItem _selectedEkz; private int _selectedOwnerFilterId; private string _statusText; public EkzDirectoryWindowViewModel(EkzDirectoryService service, IEkzDirectoryDialogService dialogService) { _service = service; _dialogService = dialogService; _ekzCache = new List(); EkzItems = new ObservableCollection(); EkzMkItems = new ObservableCollection(); OwnerFilterItems = new ObservableCollection(); OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" }); AddEkzCommand = new RelayCommand(delegate { AddEkzAsync(); }, delegate { return !IsBusy; }); EditEkzCommand = new RelayCommand(delegate { EditEkzAsync(); }, delegate { return !IsBusy && SelectedEkz != null; }); DeleteEkzCommand = new RelayCommand(delegate { DeleteEkzWithPreviewAsync(); }, delegate { return !IsBusy && SelectedEkz != null; }); RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; }); UpdateStatus(); } public ICommand AddEkzCommand { get; private set; } public ICommand DeleteEkzCommand { get; private set; } public ICommand EditEkzCommand { get; private set; } public ObservableCollection EkzItems { get; private set; } public ObservableCollection EkzMkItems { get; private set; } public bool IsBusy { get { return _isBusy; } private set { if (SetProperty(ref _isBusy, value)) { RaiseCommandStates(); } } } public ObservableCollection OwnerFilterItems { get; private set; } public ICommand RefreshCommand { get; private set; } public string SearchText { get { return _searchText; } set { if (SetProperty(ref _searchText, value)) { ApplyFilter(SelectedEkz == null ? (int?)null : SelectedEkz.Id); } } } public EkzDirectoryItem SelectedEkz { get { return _selectedEkz; } set { if (SetProperty(ref _selectedEkz, value)) { RaiseCommandStates(); if (!_isApplyingFilter) { LoadEkzMkForSelection(); } UpdateStatus(); } } } public int SelectedOwnerFilterId { get { return _selectedOwnerFilterId; } set { if (SetProperty(ref _selectedOwnerFilterId, value)) { ApplyFilter(SelectedEkz == null ? (int?)null : SelectedEkz.Id); } } } public string StatusText { get { return _statusText; } private set { SetProperty(ref _statusText, value); } } public async Task InitializeAsync() { await ExecuteBusyOperationAsync(delegate { return RefreshCoreAsync(null); }); } private void AddEkzAsync() { var result = _dialogService.ShowEkzEditDialog(new EkzDirectoryItem(), true, _ekzCache.ToList(), _service); if (result == null) { return; } RunMutationOperation(async delegate { var createdId = await Task.Run(delegate { return _service.AddEkzItem(result); }); await RefreshCoreAsync(createdId); _dialogService.ShowInfo("Запись EKZ добавлена."); }); } private void ApplyFilter(int? preferredId) { var filteredItems = _ekzCache.Where(delegate(EkzDirectoryItem item) { return MatchesOwnerFilter(item) && MatchesSearch(item); }).ToList(); _isApplyingFilter = true; try { EkzItems.Clear(); foreach (var item in filteredItems) { EkzItems.Add(item); } SelectedEkz = preferredId.HasValue ? EkzItems.FirstOrDefault(delegate(EkzDirectoryItem item) { return item.Id == preferredId.Value; }) : EkzItems.FirstOrDefault(); } finally { _isApplyingFilter = false; } if (!IsBusy) { LoadEkzMkForSelection(); } UpdateStatus(); } private static EkzDirectoryItem CloneEkz(EkzDirectoryItem source) { return new EkzDirectoryItem { Id = source.Id, TypeSizeId = source.TypeSizeId, MeasurementAreaName = source.MeasurementAreaName, InstrumentName = source.InstrumentName, TypeName = source.TypeName, RangeText = source.RangeText, AccuracyText = source.AccuracyText, RegistryNumber = source.RegistryNumber, OwnerOrganizationId = source.OwnerOrganizationId, OwnerOrganizationName = source.OwnerOrganizationName, SerialNumber = source.SerialNumber, InventoryNumber = source.InventoryNumber, Notes = source.Notes }; } private async void DeleteEkzWithPreviewAsync() { if (SelectedEkz == null) { return; } var selected = SelectedEkz; EkzDeletePreview preview; try { IsBusy = true; preview = await Task.Run(delegate { return _service.GetEkzDeletePreview(selected.Id); }); } catch (InvalidOperationException ex) { _dialogService.ShowWarning(ex.Message); return; } catch (Exception ex) { _dialogService.ShowError(ex.Message); return; } finally { IsBusy = false; } if (preview == null) { return; } if (!preview.CanDelete) { _dialogService.ShowWarning(preview.WarningMessage); return; } if (!_dialogService.Confirm(BuildDeleteConfirmationMessage(selected, preview))) { return; } RunMutationOperation(async delegate { var result = await Task.Run(delegate { return _service.DeleteEkzItem(selected.Id); }); if (!result.IsDeleted) { _dialogService.ShowWarning(result.WarningMessage); return; } await RefreshCoreAsync(null); _dialogService.ShowInfo(BuildDeleteResultMessage(result)); }); } private async void DeleteEkzAsync() { if (SelectedEkz == null) { return; } var selected = SelectedEkz; EkzDeletePreview preview; try { IsBusy = true; preview = await Task.Run(delegate { return _service.GetEkzDeletePreview(selected.Id); }); } catch (InvalidOperationException ex) { _dialogService.ShowWarning(ex.Message); return; } catch (Exception ex) { _dialogService.ShowError(ex.Message); return; } finally { IsBusy = false; } if (preview == null) { return; } if (!preview.CanDelete) { _dialogService.ShowWarning(preview.WarningMessage); return; } if (!_dialogService.Confirm(string.Format("Удалить экземпляр \"{0}\"?", selected.SerialNumber))) { return; } RunMutationOperation(async delegate { var result = await Task.Run(delegate { return _service.DeleteEkzItem(selected.Id); }); if (!result.IsDeleted) { _dialogService.ShowWarning(result.WarningMessage); return; } await RefreshCoreAsync(null); _dialogService.ShowInfo("Запись EKZ удалена."); }); } private void EditEkzAsync() { if (SelectedEkz == null) { return; } var result = _dialogService.ShowEkzEditDialog(CloneEkz(SelectedEkz), false, _ekzCache.ToList(), _service); if (result == null) { return; } RunMutationOperation(async delegate { await Task.Run(delegate { _service.UpdateEkzItem(result); }); await RefreshCoreAsync(result.Id); _dialogService.ShowInfo("Запись EKZ обновлена."); }); } private async Task ExecuteBusyOperationAsync(Func operation) { try { IsBusy = true; await operation(); } catch (Exception ex) { _dialogService.ShowError(ex.Message); } finally { IsBusy = false; } } private async Task ExecuteMutationOperationAsync(Func operation) { try { IsBusy = true; await operation(); } catch (InvalidOperationException ex) { _dialogService.ShowWarning(ex.Message); } catch (Exception ex) { _dialogService.ShowError(ex.Message); } finally { IsBusy = false; } } private string[] GetSearchTokens() { return (SearchText ?? string.Empty) .Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) .Select(delegate(string token) { return token.Trim().ToUpperInvariant(); }) .Where(delegate(string token) { return token.Length > 0; }) .ToArray(); } private void LoadEkzMkForSelection() { if (IsBusy) { return; } RunBusyOperation(async delegate { if (SelectedEkz == null) { EkzMkItems.Clear(); UpdateStatus(); return; } await RefreshEkzMkCoreAsync(SelectedEkz.Id); }); } private bool MatchesOwnerFilter(EkzDirectoryItem item) { return SelectedOwnerFilterId <= 0 || (item != null && item.OwnerOrganizationId == SelectedOwnerFilterId); } private bool MatchesSearch(EkzDirectoryItem item) { var tokens = GetSearchTokens(); if (tokens.Length == 0) { return true; } var haystack = string.Join( " ", new[] { item == null ? null : item.Id.ToString(), item == null ? null : item.OwnerOrganizationName, item == null ? null : item.MeasurementAreaName, item == null ? null : item.InstrumentName, item == null ? null : item.TypeName, item == null ? null : item.RangeText, item == null ? null : item.AccuracyText, item == null ? null : item.RegistryNumber, item == null ? null : item.SerialNumber, item == null ? null : item.InventoryNumber, item == null ? null : item.Notes }.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })) .ToUpperInvariant(); return tokens.All(delegate(string token) { return haystack.IndexOf(token, StringComparison.Ordinal) >= 0; }); } private async Task RefreshCoreAsync(int? idToSelect) { var currentSelectedId = idToSelect ?? (SelectedEkz == null ? (int?)null : SelectedEkz.Id); var currentOwnerFilterId = SelectedOwnerFilterId; var ekzTask = Task.Run(delegate { return _service.LoadEkzItems(); }); var ownerTask = Task.Run(delegate { return _service.LoadFrpdReferences(); }); await Task.WhenAll(ekzTask, ownerTask); _ekzCache = ekzTask.Result.ToList(); RebuildOwnerFilters(ownerTask.Result, currentOwnerFilterId); ApplyFilter(currentSelectedId); if (SelectedEkz == null) { EkzMkItems.Clear(); UpdateStatus(); return; } await RefreshEkzMkCoreAsync(SelectedEkz.Id); UpdateStatus(); } private async Task RefreshEkzMkCoreAsync(int instrumentId) { var items = await Task.Run(delegate { return _service.LoadEkzMkItems(instrumentId); }); EkzMkItems.Clear(); foreach (var item in items) { EkzMkItems.Add(item); } } private void RebuildOwnerFilters(IReadOnlyList owners, int selectedId) { OwnerFilterItems.Clear(); OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" }); foreach (var owner in owners ?? Array.Empty()) { OwnerFilterItems.Add(owner); } _selectedOwnerFilterId = OwnerFilterItems.Any(delegate(DirectoryLookupItem item) { return item.Id == selectedId; }) ? selectedId : 0; OnPropertyChanged("SelectedOwnerFilterId"); } private void RefreshAsync() { RunBusyOperation(delegate { return RefreshCoreAsync(SelectedEkz == null ? (int?)null : SelectedEkz.Id); }); } private void RaiseCommandStates() { ((RelayCommand)AddEkzCommand).RaiseCanExecuteChanged(); ((RelayCommand)EditEkzCommand).RaiseCanExecuteChanged(); ((RelayCommand)DeleteEkzCommand).RaiseCanExecuteChanged(); ((RelayCommand)RefreshCommand).RaiseCanExecuteChanged(); } private async void RunBusyOperation(Func operation) { try { await ExecuteBusyOperationAsync(operation); } catch (Exception ex) { IsBusy = false; _dialogService.ShowError(ex.Message); } } private async void RunMutationOperation(Func operation) { try { await ExecuteMutationOperationAsync(operation); } catch (Exception ex) { IsBusy = false; _dialogService.ShowError(ex.Message); } } private static string BuildDeleteConfirmationMessage(EkzDirectoryItem selected, EkzDeletePreview preview) { return string.Format( "Удалить экземпляр \"{0}\"?{1}{1}{2}", selected == null || string.IsNullOrWhiteSpace(selected.SerialNumber) ? "(без номера)" : selected.SerialNumber, Environment.NewLine, preview == null ? string.Empty : preview.ConfirmationMessage ?? string.Empty); } private static string BuildDeleteResultMessage(EkzDeleteResult result) { var impacts = result == null || result.ImpactItems == null ? new List() : result.ImpactItems.Where(delegate(EkzDeleteImpactItem item) { return item != null && item.RowCount > 0; }).ToList(); if (impacts.Count == 0) { return "Запись EKZ удалена."; } return "Удалены записи: " + string.Join(", ", impacts.Select(delegate(EkzDeleteImpactItem item) { return string.Format("{0}: {1}", item.TableName, item.RowCount); })) + "."; } private void UpdateStatus() { var searchPrefix = string.IsNullOrWhiteSpace(SearchText) ? string.Empty : string.Format("Поиск: \"{0}\". ", SearchText.Trim()); var ownerName = OwnerFilterItems.FirstOrDefault(delegate(DirectoryLookupItem item) { return item.Id == SelectedOwnerFilterId; }); var ownerPrefix = SelectedOwnerFilterId <= 0 || ownerName == null ? string.Empty : string.Format("Владелец: \"{0}\". ", ownerName.Name); StatusText = string.Format( "{0}{1}EKZ: {2}/{3}. EKZMK: {4}.", searchPrefix, ownerPrefix, EkzItems.Count, _ekzCache.Count, EkzMkItems.Count); } } }