This commit is contained in:
Курнат Андрей
2026-03-23 06:07:50 +03:00
parent 234dac7d32
commit d54f5b8e22
12 changed files with 1772 additions and 0 deletions

View File

@@ -46,6 +46,8 @@
Click="TypeSizeDirectoryMenuItem_Click" />
<MenuItem Header="Экземпляры"
Click="EkzDirectoryMenuItem_Click" />
<MenuItem Header="Планирование"
Click="PlanningMenuItem_Click" />
<MenuItem Header="Отчеты"
Click="VerificationReportsMenuItem_Click" />
</Menu>

View File

@@ -100,6 +100,13 @@ namespace XLAB
window.ShowDialog();
}
private void PlanningMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new PlanningWindow();
window.Owner = this;
window.ShowDialog();
}
private void SpoiDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new SpoiDirectoryWindow();

View File

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

View File

@@ -0,0 +1,111 @@
<Window x:Class="XLAB.PlanningEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="360"
Width="980"
MinHeight="360"
MinWidth="860"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Прибор (EKZ)" />
<ComboBox Grid.Row="0"
Grid.Column="1"
Margin="0,0,0,8"
ItemsSource="{Binding InstrumentItems}"
SelectedValue="{Binding SelectedInstrumentId}"
SelectedValuePath="Id"
DisplayMemberPath="DisplayName"
IsTextSearchEnabled="True" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Период (TPRMCP)" />
<ComboBox Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,8"
ItemsSource="{Binding TemplateItems}"
SelectedValue="{Binding SelectedTemplateId}"
SelectedValuePath="Id"
DisplayMemberPath="DisplayName"
IsTextSearchEnabled="True" />
<TextBlock Grid.Row="2"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Плановая дата" />
<DatePicker Grid.Row="2"
Grid.Column="1"
Margin="0,0,0,8"
HorizontalAlignment="Left"
SelectedDate="{Binding PlannedOn, Mode=TwoWay}"
SelectedDateFormat="Short" />
<TextBlock Grid.Row="3"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Top"
Text="Выбранный прибор" />
<TextBlock Grid.Row="3"
Grid.Column="1"
Margin="0,0,0,8"
TextWrapping="Wrap"
Text="{Binding SelectedInstrumentDescription}" />
<TextBlock Grid.Row="4"
Grid.Column="0"
Margin="0,0,12,0"
VerticalAlignment="Top"
Text="Пояснение" />
<TextBlock Grid.Row="4"
Grid.Column="1"
Foreground="DimGray"
TextWrapping="Wrap"
Text="{Binding TemplateWarningMessage}" />
</Grid>
<DockPanel Grid.Row="1"
Margin="0,12,0,0">
<TextBlock DockPanel.Dock="Left"
VerticalAlignment="Center"
Foreground="Firebrick"
Text="{Binding ValidationMessage}" />
<StackPanel DockPanel.Dock="Right"
Orientation="Horizontal">
<Button Width="100"
Margin="0,0,8,0"
IsDefault="True"
Command="{Binding ConfirmCommand}"
Content="Сохранить" />
<Button Width="90"
Command="{Binding CancelCommand}"
Content="Отмена" />
</StackPanel>
</DockPanel>
</Grid>
</Window>

View File

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

View File

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

157
XLAB/PlanningModels.cs Normal file
View File

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

394
XLAB/PlanningService.cs Normal file
View File

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

161
XLAB/PlanningWindow.xaml Normal file
View File

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

View File

@@ -0,0 +1,33 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB
{
public partial class PlanningWindow : Window
{
private readonly PlanningWindowViewModel _viewModel;
public PlanningWindow()
{
InitializeComponent();
_viewModel = new PlanningWindowViewModel(new PlanningService(), new EkzDirectoryService(), new PlanningDialogService(this));
DataContext = _viewModel;
}
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row != null)
{
row.IsSelected = true;
row.Focus();
}
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await _viewModel.InitializeAsync();
}
}
}

View File

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

View File

@@ -112,6 +112,9 @@
</Compile>
<Compile Include="MainWindowViewModel.cs" />
<Compile Include="MvvmInfrastructure.cs" />
<Compile Include="PlanningDialogService.cs" />
<Compile Include="PlanningModels.cs" />
<Compile Include="PlanningService.cs" />
<Compile Include="PsvDataService.cs" />
<Compile Include="PsvPrintService.cs" />
<Compile Include="PsvModels.cs" />
@@ -284,6 +287,24 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="PrdspvEditWindowViewModel.cs" />
<Page Include="PlanningEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="PlanningEditWindow.xaml.cs">
<DependentUpon>PlanningEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="PlanningEditWindowViewModel.cs" />
<Page Include="PlanningWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="PlanningWindow.xaml.cs">
<DependentUpon>PlanningWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="PlanningWindowViewModel.cs" />
<Page Include="TprmcpEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>