Compare commits

...

12 Commits

Author SHA1 Message Date
Курнат Андрей
a2b4762702 edit 2026-04-03 21:06:10 +03:00
Курнат Андрей
1b20b39aca edit 2026-04-03 20:04:58 +03:00
Курнат Андрей
22ed7d978a edit 2026-04-02 21:44:28 +03:00
Курнат Андрей
db15503315 edit 2026-04-01 19:33:59 +03:00
Курнат Андрей
339b9ab8aa edit 2026-03-25 19:08:46 +03:00
Курнат Андрей
054d10cb6d edit 2026-03-25 18:26:40 +03:00
Курнат Андрей
d46172beb9 edit 2026-03-24 22:31:05 +03:00
Курнат Андрей
74d793948e edit 2026-03-23 21:24:09 +03:00
Курнат Андрей
bf9f54f91c edit 2026-03-23 19:56:35 +03:00
Курнат Андрей
d54f5b8e22 edit 2026-03-23 06:07:50 +03:00
Курнат Андрей
234dac7d32 edit 2026-03-22 22:04:04 +03:00
Курнат Андрей
7bbca6ba55 edit 2026-03-22 21:44:29 +03:00
1654 changed files with 34637 additions and 471 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -17,6 +17,10 @@
<SolidColorBrush x:Key="AppMutedTextBrush" Color="#FF6B7B88" /> <SolidColorBrush x:Key="AppMutedTextBrush" Color="#FF6B7B88" />
<SolidColorBrush x:Key="AppButtonBrush" Color="#FFF7FAFD" /> <SolidColorBrush x:Key="AppButtonBrush" Color="#FFF7FAFD" />
<SolidColorBrush x:Key="AppButtonHoverBrush" Color="#FFE7F0F9" /> <SolidColorBrush x:Key="AppButtonHoverBrush" Color="#FFE7F0F9" />
<SolidColorBrush x:Key="AppMenuIconAccentBrush" Color="#FF4F7FA7" />
<SolidColorBrush x:Key="AppMenuIconSuccessBrush" Color="#FF47A772" />
<SolidColorBrush x:Key="AppMenuIconDangerBrush" Color="#FFC76868" />
<SolidColorBrush x:Key="AppMenuIconWarningBrush" Color="#FFCC9B52" />
<SolidColorBrush x:Key="AppSelectionBrush" Color="#FFD8E6F4" /> <SolidColorBrush x:Key="AppSelectionBrush" Color="#FFD8E6F4" />
<SolidColorBrush x:Key="AppSelectionTextBrush" Color="#FF17324A" /> <SolidColorBrush x:Key="AppSelectionTextBrush" Color="#FF17324A" />
<SolidColorBrush x:Key="OpenDocumentTenDaysBrush" Color="#FFF2F8EA" /> <SolidColorBrush x:Key="OpenDocumentTenDaysBrush" Color="#FFF2F8EA" />
@@ -118,6 +122,166 @@
<Style TargetType="{x:Type MenuItem}"> <Style TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" /> <Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="Header" Value="Добавить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Изменить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Удалить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Распечатать">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="1.5" Width="8" Height="4" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="5" Width="12" Height="5" RadiusX="1.5" RadiusY="1.5" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="4" Canvas.Top="8.5" Width="8" Height="5" Fill="#FFF9FCFE" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Добавить по заводским номерам">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="2" Canvas.Top="3" Width="8" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="7" Width="8" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="11" Width="6" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Ellipse Canvas.Left="10.5" Canvas.Top="6" Width="4" Height="4" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="12" Canvas.Top="4.5" Width="1" Height="7" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="9.5" Canvas.Top="7" Width="6" Height="1" Fill="{StaticResource AppMenuIconSuccessBrush}" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Добавить по типу">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="2" Canvas.Top="3" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="9" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="8" Canvas.Top="6" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="12" Canvas.Top="1.5" Width="1.5" Height="5" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="10.25" Canvas.Top="3.25" Width="5" Height="1.5" Fill="{StaticResource AppMenuIconSuccessBrush}" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Клонировать поверку в выбранные строки">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="3" Canvas.Top="4" Width="7" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="2" Width="7" Height="8" RadiusX="1" RadiusY="1" Fill="#FFEAF3FB" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Распечатать документ о поверке">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="1.5" Width="8" Height="4" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="5" Width="12" Height="5" RadiusX="1.5" RadiusY="1.5" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="4" Canvas.Top="8.5" Width="8" Height="5" Fill="#FFF9FCFE" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
<Ellipse Canvas.Left="10.5" Canvas.Top="9.5" Width="4" Height="4" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Path Fill="White" Data="M12.1,10.4 L12.9,11.2 L14.2,9.6 L14.8,10.1 L12.9,12.4 L11.5,11 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Годен">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Path Fill="White" Data="M5.1,8.2 L7.2,10.3 L11.6,5.6 L12.8,6.6 L7.3,12.3 L3.9,8.9 Z" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Забракован">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Path Stroke="White" StrokeThickness="1.8" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Data="M5.1,5.1 L10.9,10.9 M10.9,5.1 L5.1,10.9" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Отменить проверку">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconWarningBrush}" Data="M7.8,2.2 C10.8,2.2 13.2,4.6 13.2,7.6 C13.2,10.6 10.8,13 7.8,13 C5.5,13 3.6,11.6 2.8,9.5 L4.5,9.5 C5.2,10.8 6.4,11.5 7.8,11.5 C10,11.5 11.7,9.8 11.7,7.6 C11.7,5.4 10,3.7 7.8,3.7 C6.5,3.7 5.3,4.3 4.6,5.4 L6.7,5.4 L3.8,8.2 L1.1,5.4 L3.1,5.4 C4,3.4 5.8,2.2 7.8,2.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Виды клейм...">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Ellipse Canvas.Left="2.5" Canvas.Top="2.5" Width="11" Height="11" Fill="{StaticResource AppMenuIconWarningBrush}" />
<Path Fill="White" Data="M8,4 L8.9,6.2 L11.3,6.3 L9.4,7.8 L10.1,10.1 L8,8.8 L5.9,10.1 L6.6,7.8 L4.7,6.3 L7.1,6.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style> </Style>
<Style TargetType="{x:Type ListBox}"> <Style TargetType="{x:Type ListBox}">

View File

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

138
XLAB/EkzDirectoryModels.cs Normal file
View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
namespace XLAB
{
public sealed class EkzDirectoryItem
{
public int Id { get; set; }
public int TypeSizeId { get; set; }
public string MeasurementAreaName { get; set; }
public string InstrumentName { get; set; }
public string TypeName { get; set; }
public string RangeText { get; set; }
public string AccuracyText { get; set; }
public string RegistryNumber { get; set; }
public int OwnerOrganizationId { get; set; }
public string OwnerOrganizationName { get; set; }
public string SerialNumber { get; set; }
public string InventoryNumber { get; set; }
public string StickerNumbers { get; set; }
public string Notes { get; set; }
}
public sealed class EkzMkDirectoryItem
{
public int CardId { get; set; }
public int InstrumentId { get; set; }
public string VerificationTypeName { get; set; }
public string VerificationOrganizationName { get; set; }
public string DocumentNumber { get; set; }
public string VerificationDocumentNumber { get; set; }
public DateTime? VerificationDocumentDate { get; set; }
public string StickerNumber { get; set; }
public string VerifierName { get; set; }
public int PeriodMonths { get; set; }
public DateTime? AcceptedOn { get; set; }
public DateTime? PlannedOn { get; set; }
public DateTime? PerformedOn { get; set; }
public DateTime? IssuedOn { get; set; }
public bool? IsPassed { get; set; }
public string Notes { get; set; }
public string ResultText
{
get
{
if (!IsPassed.HasValue)
{
return string.Empty;
}
return IsPassed.Value ? "Годен" : "Не годен";
}
}
public string VerificationDocumentDisplay
{
get
{
if (string.IsNullOrWhiteSpace(VerificationDocumentNumber))
{
return VerificationDocumentDate.HasValue ? VerificationDocumentDate.Value.ToString("d") : string.Empty;
}
if (!VerificationDocumentDate.HasValue)
{
return VerificationDocumentNumber;
}
return string.Format("{0} от {1:d}", VerificationDocumentNumber, VerificationDocumentDate.Value);
}
}
}
internal sealed class EkzDeleteImpactItem
{
public string TableName { get; set; }
public int RowCount { get; set; }
}
internal sealed class EkzDeletePreview
{
public bool CanDelete { get; set; }
public IReadOnlyList<EkzDeleteImpactItem> ImpactItems { get; set; }
public string ConfirmationMessage { get; set; }
public string WarningMessage { get; set; }
}
internal sealed class EkzDeleteResult
{
public bool IsDeleted { get; set; }
public IReadOnlyList<EkzDeleteImpactItem> ImpactItems { get; set; }
public string WarningMessage { get; set; }
}
internal static class EkzDirectoryRules
{
public const int SerialNumberMaxLength = 30;
public const int InventoryNumberMaxLength = 30;
public const int NotesMaxLength = 8000;
}
}

858
XLAB/EkzDirectoryService.cs Normal file
View File

@@ -0,0 +1,858 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Data.SqlClient;
namespace XLAB
{
internal sealed class EkzDirectoryService
{
private static readonly string[] CascadingEkzChildTables = { "EKZMK", "EKZMCP" };
private static readonly string[] CascadingEkzMkChildTables = { "EKZMKFCTVL", "EKZMKDH", "EKZMKEKZK", "EKZMKND", "KSPELEKZMK" };
public int AddEkzItem(EkzDirectoryItem item)
{
var normalizedItem = NormalizeEkzItem(item);
const string sql = @"
INSERT INTO dbo.EKZ
(
IDTPRZ,
IDFRPDV,
KLSIPR,
NNZV,
NNIN,
DSEKZ,
GUIDEKZ,
IsDeleted
)
VALUES
(
@TypeSizeId,
@OwnerOrganizationId,
1,
@SerialNumber,
@InventoryNumber,
@Notes,
@Guid,
0
);
SELECT CAST(SCOPE_IDENTITY() AS int);";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureEkzIsUnique(connection, normalizedItem.TypeSizeId, normalizedItem.OwnerOrganizationId, normalizedItem.SerialNumber, null);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = normalizedItem.OwnerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = normalizedItem.SerialNumber;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@InventoryNumber", SqlDbType.VarChar, EkzDirectoryRules.InventoryNumberMaxLength, normalizedItem.InventoryNumber);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Notes", SqlDbType.VarChar, EkzDirectoryRules.NotesMaxLength, normalizedItem.Notes);
command.Parameters.Add("@Guid", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
return Convert.ToInt32(command.ExecuteScalar());
}
}
public EkzDeletePreview GetEkzDeletePreview(int id)
{
if (id <= 0)
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Не выбрана запись EKZ для удаления."
};
}
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
return BuildEkzDeletePreview(connection, null, id);
}
}
public EkzDeleteResult DeleteEkzItem(int id)
{
if (id <= 0)
{
throw new InvalidOperationException("Не выбрана запись EKZ для удаления.");
}
try
{
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
var preview = BuildEkzDeletePreview(connection, transaction, id);
if (!preview.CanDelete)
{
transaction.Rollback();
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = preview.ImpactItems ?? Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = preview.WarningMessage
};
}
var impactItems = new List<EkzDeleteImpactItem>();
AddImpactItem(impactItems, "EKZMKFCTVL", DeleteEkzMkFctvl(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKDH", DeleteEkzMkDh(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKEKZK", DeleteEkzMkEkzk(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKND", DeleteEkzMkNd(connection, transaction, id));
AddImpactItem(impactItems, "KSPELEKZMK", DeleteKspelEkzMk(connection, transaction, id));
AddImpactItem(impactItems, "DMS", DeleteEkzDms(connection, transaction, id));
AddImpactItem(impactItems, "EKZMK", DeleteEkzMk(connection, transaction, id));
AddImpactItem(impactItems, "EKZMCP", DeleteEkzMcp(connection, transaction, id));
var deletedEkzCount = DeleteEkz(connection, transaction, id);
if (deletedEkzCount == 0)
{
transaction.Rollback();
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Запись EKZ для удаления не найдена."
};
}
AddImpactItem(impactItems, "EKZ", deletedEkzCount);
transaction.Commit();
return new EkzDeleteResult
{
IsDeleted = true,
ImpactItems = OrderImpactItems(impactItems)
};
}
}
}
catch (SqlException ex) when (ex.Number == 547)
{
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = CreateEkzDeleteFailedMessage(ex)
};
}
}
public IReadOnlyList<EkzDirectoryItem> LoadEkzItems()
{
const string sql = @"
SELECT
z.IDEKZ AS Id,
z.IDTPRZ AS TypeSizeId,
areas.NMOI AS MeasurementAreaName,
names.NMTP AS InstrumentName,
tips.TP AS TypeName,
tprz.DPZN AS RangeText,
tprz.HRTC AS AccuracyText,
tprz.NNGSRS AS RegistryNumber,
z.IDFRPDV AS OwnerOrganizationId,
ownerOrg.NMFRPD AS OwnerOrganizationName,
z.NNZV AS SerialNumber,
z.NNIN AS InventoryNumber,
stickers.StickerNumbers AS StickerNumbers,
CAST(z.DSEKZ AS nvarchar(max)) AS Notes
FROM dbo.EKZ z
JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
JOIN dbo.FRPD ownerOrg ON ownerOrg.IDFRPD = z.IDFRPDV
OUTER APPLY
(
SELECT STUFF(
(
SELECT N' ' + sticker.StickerNumber
FROM
(
SELECT DISTINCT LTRIM(RTRIM(m.NNNKL)) AS StickerNumber
FROM dbo.EKZMK m
WHERE m.IDEKZ = z.IDEKZ
AND NULLIF(LTRIM(RTRIM(m.NNNKL)), N'') IS NOT NULL
) sticker
ORDER BY sticker.StickerNumber
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'),
1,
1,
N'') AS StickerNumbers
) stickers
WHERE ISNULL(z.IsDeleted, 0) = 0
ORDER BY ownerOrg.NMFRPD, areas.NMOI, names.NMTP, tips.TP, tprz.DPZN, z.NNZV, z.IDEKZ;";
var items = new List<EkzDirectoryItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new EkzDirectoryItem
{
Id = ReferenceDirectorySqlHelpers.GetInt32(reader, "Id"),
TypeSizeId = ReferenceDirectorySqlHelpers.GetInt32(reader, "TypeSizeId"),
MeasurementAreaName = ReferenceDirectorySqlHelpers.GetString(reader, "MeasurementAreaName"),
InstrumentName = ReferenceDirectorySqlHelpers.GetString(reader, "InstrumentName"),
TypeName = ReferenceDirectorySqlHelpers.GetString(reader, "TypeName"),
RangeText = ReferenceDirectorySqlHelpers.GetString(reader, "RangeText"),
AccuracyText = ReferenceDirectorySqlHelpers.GetString(reader, "AccuracyText"),
RegistryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "RegistryNumber"),
OwnerOrganizationId = ReferenceDirectorySqlHelpers.GetInt32(reader, "OwnerOrganizationId"),
OwnerOrganizationName = ReferenceDirectorySqlHelpers.GetString(reader, "OwnerOrganizationName"),
SerialNumber = ReferenceDirectorySqlHelpers.GetString(reader, "SerialNumber"),
InventoryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "InventoryNumber"),
StickerNumbers = ReferenceDirectorySqlHelpers.GetString(reader, "StickerNumbers"),
Notes = ReferenceDirectorySqlHelpers.GetString(reader, "Notes")
});
}
}
}
return items;
}
public IReadOnlyList<EkzMkDirectoryItem> LoadEkzMkItems(int instrumentId)
{
const string sql = @"
SELECT
m.IDEKZMK AS CardId,
m.IDEKZ AS InstrumentId,
verificationType.NMVDMK AS VerificationTypeName,
organization.NMFRPD AS VerificationOrganizationName,
m.NNZVPV AS DocumentNumber,
verificationDocument.NNDMS AS VerificationDocumentNumber,
verificationDocument.DTDMS AS VerificationDocumentDate,
m.NNNKL AS StickerNumber,
verifier.PRFIO AS VerifierName,
m.PRMK AS PeriodMonths,
m.DTPRM AS AcceptedOn,
m.DTMKPL AS PlannedOn,
m.DTMKFK AS PerformedOn,
m.DTVDM AS IssuedOn,
m.GDN AS IsPassed,
CAST(m.DSEKZMK AS nvarchar(max)) AS Notes
FROM dbo.EKZMK m
LEFT JOIN dbo.SPVDMK verificationType ON verificationType.IDSPVDMK = m.IDSPVDMK
LEFT JOIN dbo.FRPD organization ON organization.IDFRPD = m.IDFRPD
LEFT JOIN dbo.PRSN verifier ON verifier.IDPRSN = m.IDPRSN
OUTER APPLY
(
SELECT TOP (1)
d.NND AS NNDMS,
d.DTD AS DTDMS
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.FRDMS frdms ON frdms.IDFRDMS = d.IDFRDMS
JOIN dbo.SPVDD spvdd ON spvdd.IDSPVDD = frdms.IDSPVDD
WHERE d.IDOD = m.IDEKZMK
AND vdd.IDSPVDOD = 2
AND spvdd.IDSPVDD IN (2, 6, 8)
ORDER BY d.DTD DESC
) verificationDocument
WHERE m.IDEKZ = @InstrumentId
ORDER BY ISNULL(m.DTPRM, CONVERT(datetime, '19000101', 112)) DESC, m.IDEKZMK DESC;";
var items = new List<EkzMkDirectoryItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = instrumentId;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new EkzMkDirectoryItem
{
CardId = ReferenceDirectorySqlHelpers.GetInt32(reader, "CardId"),
InstrumentId = ReferenceDirectorySqlHelpers.GetInt32(reader, "InstrumentId"),
VerificationTypeName = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationTypeName"),
VerificationOrganizationName = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationOrganizationName"),
DocumentNumber = ReferenceDirectorySqlHelpers.GetString(reader, "DocumentNumber"),
VerificationDocumentNumber = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationDocumentNumber"),
VerificationDocumentDate = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "VerificationDocumentDate"),
StickerNumber = ReferenceDirectorySqlHelpers.GetString(reader, "StickerNumber"),
VerifierName = ReferenceDirectorySqlHelpers.GetString(reader, "VerifierName"),
PeriodMonths = ReferenceDirectorySqlHelpers.GetInt32(reader, "PeriodMonths"),
AcceptedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "AcceptedOn"),
PlannedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "PlannedOn"),
PerformedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "PerformedOn"),
IssuedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "IssuedOn"),
IsPassed = ReferenceDirectorySqlHelpers.GetNullableBoolean(reader, "IsPassed"),
Notes = ReferenceDirectorySqlHelpers.GetString(reader, "Notes")
});
}
}
}
return items;
}
public IReadOnlyList<DirectoryLookupItem> LoadFrpdReferences()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
fr.IDFRPD AS Id,
fr.NMFRPD AS Name
FROM dbo.FRPD fr
WHERE NULLIF(LTRIM(RTRIM(fr.NMFRPD)), '') IS NOT NULL
ORDER BY fr.NMFRPD, fr.IDFRPD;");
}
public IReadOnlyList<DirectoryLookupItem> LoadTypeSizeReferences()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
tprz.IDTPRZ AS Id,
LTRIM(RTRIM(
COALESCE(NULLIF(areas.NMOI, N'') + N' / ', N'')
+ COALESCE(NULLIF(names.NMTP, N'') + N' / ', N'')
+ COALESCE(NULLIF(tips.TP, N''), N'')
+ CASE WHEN NULLIF(LTRIM(RTRIM(tprz.DPZN)), N'') IS NULL THEN N'' ELSE N' / ' + tprz.DPZN END
+ CASE
WHEN NULLIF(LTRIM(RTRIM(CONVERT(nvarchar(50), tprz.NNGSRS))), N'') IS NULL THEN N''
ELSE N' / № ГР ' + CONVERT(nvarchar(50), tprz.NNGSRS)
END
)) AS Name
FROM dbo.TPRZ tprz
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
ORDER BY areas.NMOI, names.NMTP, tips.TP, tprz.DPZN, tprz.IDTPRZ;");
}
public void UpdateEkzItem(EkzDirectoryItem item)
{
var normalizedItem = NormalizeEkzItem(item);
if (normalizedItem.Id <= 0)
{
throw new InvalidOperationException("Не выбрана запись EKZ для изменения.");
}
const string sql = @"
UPDATE dbo.EKZ
SET IDTPRZ = @TypeSizeId,
IDFRPDV = @OwnerOrganizationId,
NNZV = @SerialNumber,
NNIN = @InventoryNumber,
DSEKZ = @Notes
WHERE IDEKZ = @Id;
SELECT @@ROWCOUNT;";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureEkzIsUnique(connection, normalizedItem.TypeSizeId, normalizedItem.OwnerOrganizationId, normalizedItem.SerialNumber, normalizedItem.Id);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = normalizedItem.OwnerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = normalizedItem.SerialNumber;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@InventoryNumber", SqlDbType.VarChar, EkzDirectoryRules.InventoryNumberMaxLength, normalizedItem.InventoryNumber);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Notes", SqlDbType.VarChar, EkzDirectoryRules.NotesMaxLength, normalizedItem.Notes);
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись EKZ для изменения не найдена.");
}
}
}
private static EkzDeletePreview BuildEkzDeletePreview(SqlConnection connection, SqlTransaction transaction, int id)
{
if (!EkzExists(connection, transaction, id))
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Запись EKZ для удаления не найдена."
};
}
var cardIds = LoadEkzMkCardIds(connection, transaction, id);
var blockers = new List<DeleteBlockerInfo>();
blockers.AddRange(ReferenceDirectorySqlHelpers.LoadDeleteBlockersFromForeignKeys(connection, transaction, "EKZ", id, CascadingEkzChildTables));
blockers.AddRange(LoadUnhandledDeleteBlockers(connection, transaction, "EKZMK", cardIds, CascadingEkzMkChildTables));
var impactItems = BuildImpactItems(connection, transaction, id, cardIds);
var mergedBlockers = MergeBlockers(blockers);
if (mergedBlockers.Count > 0)
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = impactItems,
WarningMessage = CreateEkzCascadeBlockedMessage(mergedBlockers)
};
}
return new EkzDeletePreview
{
CanDelete = true,
ImpactItems = impactItems,
ConfirmationMessage = CreateEkzDeleteConfirmationMessage(impactItems)
};
}
private static IReadOnlyList<EkzDeleteImpactItem> BuildImpactItems(SqlConnection connection, SqlTransaction transaction, int instrumentId, IReadOnlyCollection<int> cardIds)
{
var impactItems = new List<EkzDeleteImpactItem>();
AddImpactItem(impactItems, "EKZ", 1);
AddImpactItem(impactItems, "EKZMK", cardIds == null ? 0 : cardIds.Count);
AddImpactItem(impactItems, "DMS", CountEkzDms(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKFCTVL", CountEkzMkFctvl(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKDH", CountEkzMkDh(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKEKZK", CountEkzMkEkzk(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKND", CountEkzMkNd(connection, transaction, instrumentId));
AddImpactItem(impactItems, "KSPELEKZMK", CountKspelEkzMk(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMCP", CountEkzMcp(connection, transaction, instrumentId));
return OrderImpactItems(impactItems);
}
private static IReadOnlyList<EkzDeleteImpactItem> OrderImpactItems(IEnumerable<EkzDeleteImpactItem> impactItems)
{
var order = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
{ "EKZ", 1 },
{ "EKZMK", 2 },
{ "DMS", 3 },
{ "EKZMKFCTVL", 4 },
{ "EKZMKDH", 5 },
{ "EKZMKEKZK", 6 },
{ "EKZMKND", 7 },
{ "KSPELEKZMK", 8 },
{ "EKZMCP", 9 }
};
return (impactItems ?? Enumerable.Empty<EkzDeleteImpactItem>())
.Where(delegate(EkzDeleteImpactItem item) { return item != null && item.RowCount > 0; })
.OrderBy(delegate(EkzDeleteImpactItem item)
{
int value;
return order.TryGetValue(item.TableName ?? string.Empty, out value) ? value : int.MaxValue;
})
.ThenBy(delegate(EkzDeleteImpactItem item) { return item.TableName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static void AddImpactItem(ICollection<EkzDeleteImpactItem> impactItems, string tableName, int rowCount)
{
if (impactItems == null || string.IsNullOrWhiteSpace(tableName) || rowCount <= 0)
{
return;
}
impactItems.Add(new EkzDeleteImpactItem
{
TableName = tableName,
RowCount = rowCount
});
}
private static string CreateEkzDeleteConfirmationMessage(IEnumerable<EkzDeleteImpactItem> impactItems)
{
var items = OrderImpactItems(impactItems).ToList();
var lines = new List<string>();
if (items.Count == 0)
{
lines.Add("Будет физически удалена только запись EKZ.");
}
else
{
lines.Add("Будут физически удалены записи:");
foreach (var item in items)
{
lines.Add(string.Format("{0}: {1}", item.TableName, item.RowCount));
}
}
lines.Add(string.Empty);
lines.Add("Продолжить?");
return string.Join(Environment.NewLine, lines.ToArray());
}
private static string CreateEkzCascadeBlockedMessage(IEnumerable<DeleteBlockerInfo> blockers)
{
return string.Format(
"Экземпляр не может быть удалён автоматически. Есть связанные записи в таблицах, которые не входят в каскад удаления: {0}.",
FormatBlockerDetails(blockers));
}
private static string CreateEkzDeleteFailedMessage(SqlException ex)
{
var suffix = ex == null || string.IsNullOrWhiteSpace(ex.Message)
? string.Empty
: " " + ex.Message.Trim();
return "Экземпляр не может быть удалён из-за ограничений ссылочной целостности БД." + suffix;
}
private static string FormatBlockerDetails(IEnumerable<DeleteBlockerInfo> blockers)
{
var details = string.Join(", ", (blockers ?? Enumerable.Empty<DeleteBlockerInfo>())
.Where(delegate(DeleteBlockerInfo blocker) { return blocker != null && blocker.RowCount > 0; })
.OrderBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.Select(delegate(DeleteBlockerInfo blocker) { return string.Format("{0}: {1}", blocker.TableName, blocker.RowCount); }));
return string.IsNullOrWhiteSpace(details) ? "связанные данные" : details;
}
private static List<DeleteBlockerInfo> MergeBlockers(IEnumerable<DeleteBlockerInfo> blockers)
{
return (blockers ?? Enumerable.Empty<DeleteBlockerInfo>())
.Where(delegate(DeleteBlockerInfo blocker) { return blocker != null && blocker.RowCount > 0 && !string.IsNullOrWhiteSpace(blocker.TableName); })
.GroupBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, DeleteBlockerInfo> group)
{
return new DeleteBlockerInfo
{
TableName = group.Key,
RowCount = group.Sum(delegate(DeleteBlockerInfo blocker) { return blocker.RowCount; })
};
})
.OrderBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static List<DeleteBlockerInfo> LoadUnhandledDeleteBlockers(SqlConnection connection, SqlTransaction transaction, string parentTableName, IEnumerable<int> ids, IEnumerable<string> excludedChildTables)
{
var blockers = new List<DeleteBlockerInfo>();
foreach (var id in (ids ?? Enumerable.Empty<int>()).Where(delegate(int value) { return value > 0; }).Distinct())
{
blockers.AddRange(ReferenceDirectorySqlHelpers.LoadDeleteBlockersFromForeignKeys(connection, transaction, parentTableName, id, excludedChildTables));
}
return MergeBlockers(blockers);
}
private static bool EkzExists(SqlConnection connection, SqlTransaction transaction, int id)
{
const string sql = @"
SELECT COUNT(1)
FROM dbo.EKZ
WHERE IDEKZ = @Id;";
return ExecuteScalarForId(connection, transaction, sql, id) > 0;
}
private static List<int> LoadEkzMkCardIds(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
const string sql = @"
SELECT IDEKZMK
FROM dbo.EKZMK
WHERE IDEKZ = @InstrumentId
ORDER BY IDEKZMK;";
return ExecuteIdList(connection, transaction, sql, "@InstrumentId", instrumentId);
}
private static int CountEkzMcp(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMCP
WHERE IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkFctvl(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKFCTVL child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkDh(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKDH child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkEkzk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKEKZK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkNd(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKND child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountKspelEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.KSPELEKZMK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzDms(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.EKZMK m ON m.IDEKZMK = d.IDOD
WHERE m.IDEKZ = @InstrumentId
AND vdd.IDSPVDOD = 2;", instrumentId);
}
private static int DeleteEkzMcp(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE FROM dbo.EKZMCP
WHERE IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkFctvl(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKFCTVL child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkDh(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKDH child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkEkzk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKEKZK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkNd(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKND child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteKspelEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.KSPELEKZMK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzDms(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE d
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.EKZMK m ON m.IDEKZMK = d.IDOD
WHERE m.IDEKZ = @InstrumentId
AND vdd.IDSPVDOD = 2;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE FROM dbo.EKZMK
WHERE IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkz(SqlConnection connection, SqlTransaction transaction, int id)
{
return ExecuteScalarForId(connection, transaction, @"
DELETE FROM dbo.EKZ
WHERE IDEKZ = @Id;
SELECT @@ROWCOUNT;", id);
}
private static int ExecuteScalarForInstrument(SqlConnection connection, SqlTransaction transaction, string sql, int instrumentId)
{
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = instrumentId;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static int ExecuteScalarForId(SqlConnection connection, SqlTransaction transaction, string sql, int id)
{
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static List<int> ExecuteIdList(SqlConnection connection, SqlTransaction transaction, string sql, string parameterName, int parameterValue)
{
var result = new List<int>();
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add(parameterName, SqlDbType.Int).Value = parameterValue;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
result.Add(reader.GetInt32(0));
}
}
}
return result;
}
private static string NormalizeOptional(string value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
private static EkzDirectoryItem NormalizeEkzItem(EkzDirectoryItem item)
{
if (item == null)
{
throw new InvalidOperationException("Не переданы данные записи EKZ.");
}
var normalizedItem = new EkzDirectoryItem
{
Id = item.Id,
TypeSizeId = item.TypeSizeId,
OwnerOrganizationId = item.OwnerOrganizationId,
SerialNumber = NormalizeOptional(item.SerialNumber),
InventoryNumber = NormalizeOptional(item.InventoryNumber),
Notes = NormalizeOptional(item.Notes)
};
if (normalizedItem.TypeSizeId <= 0)
{
throw new InvalidOperationException("Не указан типоразмер СИ.");
}
if (normalizedItem.OwnerOrganizationId <= 0)
{
throw new InvalidOperationException("Не указана организация-владелец.");
}
if (string.IsNullOrWhiteSpace(normalizedItem.SerialNumber))
{
throw new InvalidOperationException("Не указан заводской номер.");
}
if (normalizedItem.SerialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength));
}
if (!string.IsNullOrWhiteSpace(normalizedItem.InventoryNumber) && normalizedItem.InventoryNumber.Length > EkzDirectoryRules.InventoryNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Инвентарный номер не должен превышать {0} символов.", EkzDirectoryRules.InventoryNumberMaxLength));
}
if (!string.IsNullOrWhiteSpace(normalizedItem.Notes) && normalizedItem.Notes.Length > EkzDirectoryRules.NotesMaxLength)
{
throw new InvalidOperationException(string.Format("Примечание не должно превышать {0} символов.", EkzDirectoryRules.NotesMaxLength));
}
return normalizedItem;
}
private static void EnsureEkzIsUnique(SqlConnection connection, int typeSizeId, int ownerOrganizationId, string serialNumber, int? excludeId)
{
const string sql = @"
SELECT TOP (1) z.IDEKZ
FROM dbo.EKZ z
WHERE z.IDTPRZ = @TypeSizeId
AND z.IDFRPDV = @OwnerOrganizationId
AND z.NNZV = @SerialNumber
AND ISNULL(z.IsDeleted, 0) = 0
AND (@ExcludeId IS NULL OR z.IDEKZ <> @ExcludeId)
ORDER BY z.IDEKZ DESC;";
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = ownerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = serialNumber;
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@ExcludeId", excludeId);
if (command.ExecuteScalar() != null)
{
throw new InvalidOperationException("Экземпляр с таким типоразмером, владельцем и заводским номером уже существует.");
}
}
}
}
}

View File

@@ -0,0 +1,176 @@
<Window x:Class="XLAB.EkzDirectoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Экземпляры"
Height="900"
Width="1540"
MinHeight="760"
MinWidth="1260"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="2.2*" />
<RowDefinition Height="1.6*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0"
Margin="0,0,0,12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<Button DockPanel.Dock="Right"
Width="110"
Margin="12,0,0,0"
Command="{Binding RefreshCommand}"
Content="Обновить" />
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,0,8,0"
VerticalAlignment="Center"
Text="Поиск" />
<TextBox Width="360"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DockPanel>
<StackPanel Grid.Row="1"
Margin="0,8,0,0"
Orientation="Horizontal">
<TextBlock Margin="0,0,8,0"
VerticalAlignment="Center"
Text="Организация-владелец" />
<ComboBox Width="420"
ItemsSource="{Binding OwnerFilterItems}"
SelectedValue="{Binding SelectedOwnerFilterId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
</Grid>
<GroupBox Grid.Row="1"
Header="Экземпляры (EKZ)">
<DataGrid ItemsSource="{Binding EkzItems}"
SelectedItem="{Binding SelectedEkz, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddEkzCommand}" />
<MenuItem Header="Изменить"
Command="{Binding EditEkzCommand}" />
<MenuItem Header="Удалить"
Command="{Binding DeleteEkzCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Организация-владелец"
Width="220"
Binding="{Binding OwnerOrganizationName}" />
<DataGridTextColumn Header="Область измерений"
Width="160"
Binding="{Binding MeasurementAreaName}" />
<DataGridTextColumn Header="Наименование"
Width="220"
Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип"
Width="180"
Binding="{Binding TypeName}" />
<DataGridTextColumn Header="Диапазон"
Width="220"
Binding="{Binding RangeText}" />
<DataGridTextColumn Header="Х-ка точности"
Width="150"
Binding="{Binding AccuracyText}" />
<DataGridTextColumn Header="№ Госреестра"
Width="120"
Binding="{Binding RegistryNumber}" />
<DataGridTextColumn Header="Заводской номер"
Width="140"
Binding="{Binding SerialNumber}" />
<DataGridTextColumn Header="Инвентарный номер"
Width="140"
Binding="{Binding InventoryNumber}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<GroupBox Grid.Row="2"
Margin="0,12,0,0"
Header="МК выбранного экземпляра (EKZMK)">
<DataGrid ItemsSource="{Binding EkzMkItems}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="ПСВ/Акт-справка"
Width="220"
Binding="{Binding DocumentNumber}" />
<DataGridTextColumn Header="Документ по поверке"
Width="180"
Binding="{Binding VerificationDocumentDisplay}" />
<DataGridTextColumn Header="Номер наклейки"
Width="140"
Binding="{Binding StickerNumber}" />
<DataGridTextColumn Header="Поверитель"
Width="180"
Binding="{Binding VerifierName}" />
<DataGridTextColumn Header="Вид МК"
Width="120"
Binding="{Binding VerificationTypeName}" />
<DataGridTextColumn Header="Организация"
Width="220"
Binding="{Binding VerificationOrganizationName}" />
<DataGridTextColumn Header="Период, мес."
Width="95"
Binding="{Binding PeriodMonths}" />
<DataGridTextColumn Header="Принят"
Width="95"
Binding="{Binding AcceptedOn, StringFormat=d}" />
<DataGridTextColumn Header="План"
Width="95"
Binding="{Binding PlannedOn, StringFormat=d}" />
<DataGridTextColumn Header="Поверен"
Width="95"
Binding="{Binding PerformedOn, StringFormat=d}" />
<DataGridTextColumn Header="Выдан"
Width="95"
Binding="{Binding IssuedOn, StringFormat=d}" />
<DataGridTextColumn Header="Результат"
Width="95"
Binding="{Binding ResultText}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<TextBlock Grid.Row="3"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<StackPanel Grid.Row="4"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
IsCancel="True"
Content="Закрыть" />
</StackPanel>
</Grid>
</Window>

View File

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

View File

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

109
XLAB/EkzEditWindow.xaml Normal file
View File

@@ -0,0 +1,109 @@
<Window x:Class="XLAB.EkzEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="340"
Width="860"
MinHeight="340"
MinWidth="760"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Типоразмер СИ" />
<ComboBox Grid.Row="0"
Grid.Column="1"
Margin="0,0,0,8"
ItemsSource="{Binding TypeSizeItems}"
SelectedValue="{Binding TypeSizeId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Организация-владелец" />
<ComboBox Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,8"
ItemsSource="{Binding OwnerItems}"
SelectedValue="{Binding OwnerOrganizationId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
<TextBlock Grid.Row="2"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Заводской номер" />
<TextBox Grid.Row="2"
Grid.Column="1"
Margin="0,0,0,8"
Text="{Binding SerialNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="3"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Инвентарный номер" />
<TextBox Grid.Row="3"
Grid.Column="1"
Margin="0,0,0,8"
Text="{Binding InventoryNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="4"
Grid.Column="0"
Margin="0,0,12,0"
VerticalAlignment="Top"
Text="Примечание" />
<TextBox Grid.Row="4"
Grid.Column="1"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
Text="{Binding Notes, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<DockPanel Grid.Row="1"
Margin="0,12,0,0">
<TextBlock DockPanel.Dock="Left"
VerticalAlignment="Center"
Foreground="Firebrick"
Text="{Binding ValidationMessage}" />
<StackPanel DockPanel.Dock="Right"
Orientation="Horizontal">
<Button Width="100"
Margin="0,0,8,0"
IsDefault="True"
Command="{Binding ConfirmCommand}"
Content="Сохранить" />
<Button Width="90"
Command="{Binding CancelCommand}"
Content="Отмена" />
</StackPanel>
</DockPanel>
</Grid>
</Window>

View File

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

View File

@@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB
{
internal sealed class EkzEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<EkzDirectoryItem> _existingItems;
private string _inventoryNumber;
private string _notes;
private int _ownerOrganizationId;
private string _serialNumber;
private int _typeSizeId;
private string _validationMessage;
public EkzEditWindowViewModel(EkzDirectoryItem seed, bool isNew, IReadOnlyList<EkzDirectoryItem> existingItems, EkzDirectoryService service)
{
var source = seed ?? new EkzDirectoryItem();
_existingItems = existingItems ?? Array.Empty<EkzDirectoryItem>();
Id = source.Id;
IsNew = isNew;
TypeSizeItems = service.LoadTypeSizeReferences();
OwnerItems = service.LoadFrpdReferences();
TypeSizeId = source.TypeSizeId;
OwnerOrganizationId = source.OwnerOrganizationId;
SerialNumber = source.SerialNumber ?? string.Empty;
InventoryNumber = source.InventoryNumber ?? string.Empty;
Notes = source.Notes ?? string.Empty;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public int Id { get; private set; }
public bool IsNew { get; private set; }
public string InventoryNumber
{
get { return _inventoryNumber; }
set { SetProperty(ref _inventoryNumber, value); }
}
public string Notes
{
get { return _notes; }
set { SetProperty(ref _notes, value); }
}
public IReadOnlyList<DirectoryLookupItem> OwnerItems { get; private set; }
public int OwnerOrganizationId
{
get { return _ownerOrganizationId; }
set { SetProperty(ref _ownerOrganizationId, value); }
}
public string SerialNumber
{
get { return _serialNumber; }
set { SetProperty(ref _serialNumber, value); }
}
public string Title
{
get { return IsNew ? "Новый экземпляр" : "Редактирование экземпляра"; }
}
public IReadOnlyList<DirectoryLookupItem> TypeSizeItems { get; private set; }
public int TypeSizeId
{
get { return _typeSizeId; }
set { SetProperty(ref _typeSizeId, value); }
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public EkzDirectoryItem ToResult()
{
return new EkzDirectoryItem
{
Id = Id,
TypeSizeId = TypeSizeId,
OwnerOrganizationId = OwnerOrganizationId,
SerialNumber = Normalize(SerialNumber),
InventoryNumber = Normalize(InventoryNumber),
Notes = Normalize(Notes)
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
var serialNumber = Normalize(SerialNumber);
var inventoryNumber = Normalize(InventoryNumber);
var notes = Normalize(Notes);
if (TypeSizeId <= 0)
{
ValidationMessage = "Укажите типоразмер СИ.";
return;
}
if (OwnerOrganizationId <= 0)
{
ValidationMessage = "Укажите организацию-владельца.";
return;
}
if (string.IsNullOrWhiteSpace(serialNumber))
{
ValidationMessage = "Укажите заводской номер.";
return;
}
if (serialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
ValidationMessage = string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength);
return;
}
if (!string.IsNullOrWhiteSpace(inventoryNumber) && inventoryNumber.Length > EkzDirectoryRules.InventoryNumberMaxLength)
{
ValidationMessage = string.Format("Инвентарный номер не должен превышать {0} символов.", EkzDirectoryRules.InventoryNumberMaxLength);
return;
}
if (!string.IsNullOrWhiteSpace(notes) && notes.Length > EkzDirectoryRules.NotesMaxLength)
{
ValidationMessage = string.Format("Примечание не должно превышать {0} символов.", EkzDirectoryRules.NotesMaxLength);
return;
}
var duplicate = _existingItems.FirstOrDefault(delegate(EkzDirectoryItem item)
{
return item != null
&& item.Id != Id
&& item.TypeSizeId == TypeSizeId
&& item.OwnerOrganizationId == OwnerOrganizationId
&& string.Equals(item.SerialNumber ?? string.Empty, serialNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
});
if (duplicate != null)
{
ValidationMessage = "Экземпляр с таким типоразмером, владельцем и заводским номером уже существует.";
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private static string Normalize(string value)
{
return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
}
}

View File

@@ -1,4 +1,4 @@
<Window x:Class="XLAB.MainWindow" <Window x:Class="XLAB.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Приемо-сдаточные ведомости" Title="Приемо-сдаточные ведомости"
@@ -44,6 +44,12 @@
Click="PrsnDirectoryMenuItem_Click" /> Click="PrsnDirectoryMenuItem_Click" />
<MenuItem Header="Типоразмеры" <MenuItem Header="Типоразмеры"
Click="TypeSizeDirectoryMenuItem_Click" /> Click="TypeSizeDirectoryMenuItem_Click" />
<MenuItem Header="Экземпляры"
Click="EkzDirectoryMenuItem_Click" />
<MenuItem Header="Планирование"
Click="PlanningMenuItem_Click" />
<MenuItem Header="Отчеты"
Click="VerificationReportsMenuItem_Click" />
</Menu> </Menu>
<Grid Grid.Row="1"> <Grid Grid.Row="1">
@@ -304,7 +310,28 @@
<GroupBox Grid.Row="1" Header="Группы приборов выбранного документа"> <GroupBox Grid.Row="1" Header="Группы приборов выбранного документа">
<Grid Margin="8"> <Grid Margin="8">
<DataGrid ItemsSource="{Binding DocumentGroupSummaries}" <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="290" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding GroupFilterText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Column="1"
Margin="12,0,0,0"
VerticalAlignment="Center"
Foreground="DimGray"
Text="Поиск по наименованию, типу, диапазону, характеристикам, госреестру или зав. №" />
</Grid>
<DataGrid Grid.Row="1"
ItemsSource="{Binding DocumentGroupsView}"
SelectedItem="{Binding SelectedDocumentGroup, Mode=TwoWay}" SelectedItem="{Binding SelectedDocumentGroup, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"
@@ -338,12 +365,18 @@
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTextColumn Header="Наименование"
Width="220"
Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип" <DataGridTextColumn Header="Тип"
Width="180" Width="160"
Binding="{Binding InstrumentType}" /> Binding="{Binding InstrumentType}" />
<DataGridTextColumn Header="Диапазон" <DataGridTextColumn Header="Диапазон"
Width="170" Width="160"
Binding="{Binding RangeText}" /> Binding="{Binding RangeText}" />
<DataGridTextColumn Header="Характеристики"
Width="160"
Binding="{Binding AccuracyText}" />
<DataGridTextColumn Header="Госреестр" <DataGridTextColumn Header="Госреестр"
Width="120" Width="120"
Binding="{Binding RegistryNumber}" /> Binding="{Binding RegistryNumber}" />
@@ -405,7 +438,7 @@
SelectedItem="{Binding SelectedDocumentLine, Mode=TwoWay}" SelectedItem="{Binding SelectedDocumentLine, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"
IsReadOnly="True" IsReadOnly="{Binding IsDocumentLinesReadOnly}"
HeadersVisibility="Column"> HeadersVisibility="Column">
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
@@ -446,22 +479,31 @@
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTextColumn Header="Зав. №" <DataGridTextColumn Header="Зав. №"
Width="120" Width="120"
Binding="{Binding SerialNumber}" /> Binding="{Binding SerialNumber}"
IsReadOnly="True" />
<DataGridTextColumn Header="Дата поверки" <DataGridTextColumn Header="Дата поверки"
Width="110" Width="110"
Binding="{Binding VerificationDateDisplay}" /> Binding="{Binding VerificationDateDisplay}"
IsReadOnly="True" />
<DataGridTextColumn Header="Поверитель" <DataGridTextColumn Header="Поверитель"
Width="180" Width="180"
Binding="{Binding VerifierName}" /> Binding="{Binding VerifierName}"
IsReadOnly="True" />
<DataGridTextColumn Header="Номер наклейки" <DataGridTextColumn Header="Номер наклейки"
Width="150" Width="150"
Binding="{Binding StickerNumber}" /> Binding="{Binding StickerNumber}"
IsReadOnly="True" />
<DataGridTextColumn Header="Результат поверки" <DataGridTextColumn Header="Результат поверки"
Width="140" Width="140"
Binding="{Binding ResultText}" /> Binding="{Binding ResultText}"
IsReadOnly="True" />
<DataGridTextColumn Header="Номер документа по поверке" <DataGridTextColumn Header="Номер документа по поверке"
Width="240" Width="240"
Binding="{Binding VerificationDocumentDisplay}" /> Binding="{Binding VerificationDocumentDisplay}"
IsReadOnly="True" />
<DataGridTextColumn Header="Комплектность"
Width="240"
Binding="{Binding Completeness, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</Grid> </Grid>

View File

@@ -86,6 +86,27 @@ namespace XLAB
window.ShowDialog(); window.ShowDialog();
} }
private void EkzDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new EkzDirectoryWindow();
window.Owner = this;
window.ShowDialog();
}
private void VerificationReportsMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new VerificationReportsWindow();
window.Owner = this;
window.ShowDialog();
}
private void PlanningMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new PlanningWindow();
window.Owner = this;
window.ShowDialog();
}
private void SpoiDirectoryMenuItem_Click(object sender, RoutedEventArgs e) private void SpoiDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{ {
var window = new SpoiDirectoryWindow(); var window = new SpoiDirectoryWindow();

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
@@ -20,6 +20,7 @@ namespace XLAB
private string _documentNumberEditor; private string _documentNumberEditor;
private string _documentStatusText; private string _documentStatusText;
private string _detailTableCountText; private string _detailTableCountText;
private string _groupFilterText;
private string _groupDetailFilterText; private string _groupDetailFilterText;
private string _headerDepartmentName; private string _headerDepartmentName;
private int _headerInstrumentCount; private int _headerInstrumentCount;
@@ -50,6 +51,9 @@ namespace XLAB
DocumentsView = CollectionViewSource.GetDefaultView(Documents); DocumentsView = CollectionViewSource.GetDefaultView(Documents);
DocumentsView.Filter = FilterDocuments; DocumentsView.Filter = FilterDocuments;
DocumentGroupsView = CollectionViewSource.GetDefaultView(DocumentGroupSummaries);
DocumentGroupsView.Filter = FilterDocumentGroups;
DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines); DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines);
DocumentLinesView.Filter = FilterDocumentLines; DocumentLinesView.Filter = FilterDocumentLines;
@@ -132,6 +136,8 @@ namespace XLAB
public ObservableCollection<PsvDocumentGroupSummary> DocumentGroupSummaries { get; private set; } public ObservableCollection<PsvDocumentGroupSummary> DocumentGroupSummaries { get; private set; }
public ICollectionView DocumentGroupsView { get; private set; }
public string DocumentStatusText public string DocumentStatusText
{ {
get { return _documentStatusText; } get { return _documentStatusText; }
@@ -154,6 +160,18 @@ namespace XLAB
public ICommand DeleteSelectedGroupsCommand { get; private set; } public ICommand DeleteSelectedGroupsCommand { get; private set; }
public string GroupFilterText
{
get { return _groupFilterText; }
set
{
if (SetProperty(ref _groupFilterText, value))
{
RefreshDocumentGroupsView();
}
}
}
public string GroupDetailFilterText public string GroupDetailFilterText
{ {
get { return _groupDetailFilterText; } get { return _groupDetailFilterText; }
@@ -188,6 +206,11 @@ namespace XLAB
} }
} }
public bool IsDocumentLinesReadOnly
{
get { return !CanModifySelectedDocument(); }
}
public DateTime? HeaderIssuedOn public DateTime? HeaderIssuedOn
{ {
get { return _headerIssuedOn; } get { return _headerIssuedOn; }
@@ -210,6 +233,7 @@ namespace XLAB
RaiseCommandStates(); RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsCustomerEditable");
OnPropertyChanged("IsDocumentHeaderEditable"); OnPropertyChanged("IsDocumentHeaderEditable");
OnPropertyChanged("IsDocumentLinesReadOnly");
} }
} }
} }
@@ -275,6 +299,7 @@ namespace XLAB
RaiseCommandStates(); RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsCustomerEditable");
OnPropertyChanged("IsDocumentHeaderEditable"); OnPropertyChanged("IsDocumentHeaderEditable");
OnPropertyChanged("IsDocumentLinesReadOnly");
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
} }
} }
@@ -466,6 +491,40 @@ namespace XLAB
return document != null && document.IssuedOn.HasValue; return document != null && document.IssuedOn.HasValue;
} }
private static string BuildSerialNumbersText(IEnumerable<PsvDocumentLine> lines)
{
var serialNumbers = (lines ?? Enumerable.Empty<PsvDocumentLine>())
.Select(delegate(PsvDocumentLine line)
{
return line == null || string.IsNullOrWhiteSpace(line.SerialNumber)
? null
: line.SerialNumber.Trim();
})
.Where(delegate(string serialNumber) { return !string.IsNullOrWhiteSpace(serialNumber); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string serialNumber) { return serialNumber; }, StringComparer.OrdinalIgnoreCase)
.ToList();
return serialNumbers.Count == 0 ? string.Empty : string.Join(", ", serialNumbers.ToArray());
}
private static string BuildInstrumentNamesText(IEnumerable<PsvDocumentLine> lines)
{
var instrumentNames = (lines ?? Enumerable.Empty<PsvDocumentLine>())
.Select(delegate(PsvDocumentLine line)
{
return line == null || string.IsNullOrWhiteSpace(line.InstrumentName)
? null
: line.InstrumentName.Trim();
})
.Where(delegate(string instrumentName) { return !string.IsNullOrWhiteSpace(instrumentName); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string instrumentName) { return instrumentName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
return instrumentNames.Count == 0 ? string.Empty : string.Join("; ", instrumentNames.ToArray());
}
private static bool HasVerificationData(PsvDocumentLine line) private static bool HasVerificationData(PsvDocumentLine line)
{ {
return line != null return line != null
@@ -901,14 +960,14 @@ namespace XLAB
foreach (var pendingLinesByDocument in _pendingLinesByDocumentKey) foreach (var pendingLinesByDocument in _pendingLinesByDocumentKey)
{ {
if (string.Equals(pendingLinesByDocument.Key, currentDocument.DocumentKey, StringComparison.OrdinalIgnoreCase)) if (MatchesPendingLinesStorageKey(currentDocument, pendingLinesByDocument.Key))
{ {
continue; continue;
} }
var otherDocument = Documents.FirstOrDefault(delegate(PsvDocumentSummary document) var otherDocument = Documents.FirstOrDefault(delegate(PsvDocumentSummary document)
{ {
return string.Equals(document.DocumentKey, pendingLinesByDocument.Key, StringComparison.OrdinalIgnoreCase); return MatchesPendingLinesStorageKey(document, pendingLinesByDocument.Key);
}); });
if (otherDocument == null if (otherDocument == null
@@ -1128,7 +1187,7 @@ namespace XLAB
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
await Task.Run(delegate { _service.SaveLineVerification(persistedCardIds, result); }); await _service.SaveLineVerificationAsync(persistedCardIds, result);
foreach (var pendingLine in pendingLines) foreach (var pendingLine in pendingLines)
{ {
@@ -1212,8 +1271,8 @@ namespace XLAB
try try
{ {
IsBusy = true; IsBusy = true;
verifiers = await Task.Run(delegate { return _service.LoadVerifiers(); }); verifiers = await _service.LoadVerifiersAsync();
documentForms = await Task.Run(delegate { return _service.LoadVerificationDocumentForms(isPassed); }); documentForms = await _service.LoadVerificationDocumentFormsAsync(isPassed);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1252,7 +1311,7 @@ namespace XLAB
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
await Task.Run(delegate { _service.SaveLineVerification(persistedCardIds, result); }); await _service.SaveLineVerificationAsync(persistedCardIds, result);
foreach (var pendingLine in pendingLines) foreach (var pendingLine in pendingLines)
{ {
@@ -1302,7 +1361,7 @@ namespace XLAB
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
await Task.Run(delegate { _service.ResetLineVerification(persistedCardIds); }); await _service.ResetLineVerificationAsync(persistedCardIds);
foreach (var pendingLine in pendingLines) foreach (var pendingLine in pendingLines)
{ {
@@ -1335,8 +1394,8 @@ namespace XLAB
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
var result = await Task.Run(delegate { return _service.DeleteDocument(selectedDocument.DocumentNumber); }); var result = await _service.DeleteDocumentAsync(selectedDocument.DocumentNumber);
_pendingLinesByDocumentKey.Remove(selectedDocument.DocumentKey); _pendingLinesByDocumentKey.Remove(GetPendingLinesStorageKey(selectedDocument));
await RefreshDocumentsCoreAsync(null, null); await RefreshDocumentsCoreAsync(null, null);
_dialogService.ShowInfo( _dialogService.ShowInfo(
string.Format( string.Format(
@@ -1350,7 +1409,7 @@ namespace XLAB
private void DeleteDraftDocument(PsvDocumentSummary draft) private void DeleteDraftDocument(PsvDocumentSummary draft)
{ {
_draftDocuments.RemoveAll(delegate(PsvDocumentSummary item) { return item.DocumentKey == draft.DocumentKey; }); _draftDocuments.RemoveAll(delegate(PsvDocumentSummary item) { return item.DocumentKey == draft.DocumentKey; });
_pendingLinesByDocumentKey.Remove(draft.DocumentKey); _pendingLinesByDocumentKey.Remove(GetPendingLinesStorageKey(draft));
Documents.Remove(draft); Documents.Remove(draft);
DocumentsView.Refresh(); DocumentsView.Refresh();
SelectedDocument = Documents.Count > 0 ? Documents[0] : null; SelectedDocument = Documents.Count > 0 ? Documents[0] : null;
@@ -1379,7 +1438,7 @@ namespace XLAB
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
var persistedLines = await Task.Run(delegate { return _service.LoadDocumentLines(selectedDocument.DocumentNumber); }); var persistedLines = await _service.LoadDocumentLinesAsync(selectedDocument.DocumentNumber);
var linesToPrint = MergeDocumentLinesForPrint(selectedDocument, persistedLines); var linesToPrint = MergeDocumentLinesForPrint(selectedDocument, persistedLines);
if (linesToPrint.Count == 0) if (linesToPrint.Count == 0)
{ {
@@ -1457,10 +1516,7 @@ namespace XLAB
if (persistedCardIds.Count > 0) if (persistedCardIds.Count > 0)
{ {
deletedResult = await Task.Run(delegate deletedResult = await _service.DeleteDocumentGroupsAsync(selectedDocumentNumber, persistedCardIds);
{
return _service.DeleteDocumentGroups(selectedDocumentNumber, persistedCardIds);
});
} }
var remainingPendingCount = pendingLines.Count; var remainingPendingCount = pendingLines.Count;
@@ -1565,10 +1621,7 @@ namespace XLAB
if (persistedCardIds.Count > 0) if (persistedCardIds.Count > 0)
{ {
deletedResult = await Task.Run(delegate deletedResult = await _service.DeleteDocumentGroupsAsync(selectedDocumentNumber, persistedCardIds);
{
return _service.DeleteDocumentGroups(selectedDocumentNumber, persistedCardIds);
});
} }
var remainingPendingCount = pendingLines.Count; var remainingPendingCount = pendingLines.Count;
@@ -1668,6 +1721,7 @@ namespace XLAB
document.PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }); document.PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; });
document.FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }); document.FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; });
document.IssuedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IssuedOn.HasValue; }); document.IssuedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IssuedOn.HasValue; });
document.SerialNumbersText = BuildSerialNumbersText(materializedLines);
} }
private async Task ExecuteBusyOperationAsync(Func<Task> operation) private async Task ExecuteBusyOperationAsync(Func<Task> operation)
@@ -1707,7 +1761,8 @@ namespace XLAB
if (!string.IsNullOrWhiteSpace(DocumentFilterText) if (!string.IsNullOrWhiteSpace(DocumentFilterText)
&& !Contains(document.DocumentNumber, DocumentFilterText) && !Contains(document.DocumentNumber, DocumentFilterText)
&& !Contains(document.CustomerName, DocumentFilterText)) && !Contains(document.CustomerName, DocumentFilterText)
&& !Contains(document.SerialNumbersText, DocumentFilterText))
{ {
return false; return false;
} }
@@ -1715,6 +1770,27 @@ namespace XLAB
return true; return true;
} }
private bool FilterDocumentGroups(object item)
{
var group = item as PsvDocumentGroupSummary;
if (group == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(GroupFilterText))
{
return true;
}
return Contains(group.InstrumentType, GroupFilterText)
|| Contains(group.InstrumentName, GroupFilterText)
|| Contains(group.RangeText, GroupFilterText)
|| Contains(group.AccuracyText, GroupFilterText)
|| Contains(group.RegistryNumber, GroupFilterText)
|| Contains(group.SerialNumbersText, GroupFilterText);
}
private string BuildDocumentStatusText(int count) private string BuildDocumentStatusText(int count)
{ {
if (ShowClosedDocuments) if (ShowClosedDocuments)
@@ -1759,12 +1835,33 @@ namespace XLAB
}); });
} }
private PsvDocumentGroupSummary FindMatchingVisibleGroup(PsvDocumentGroupSummary group)
{
if (group == null)
{
return null;
}
return GetVisibleDocumentGroups().FirstOrDefault(delegate(PsvDocumentGroupSummary current)
{
return AreSameGroup(current, group);
});
}
private List<PsvDocumentGroupSummary> GetVisibleDocumentGroups()
{
return DocumentGroupsView == null
? new List<PsvDocumentGroupSummary>()
: DocumentGroupsView.Cast<object>().OfType<PsvDocumentGroupSummary>().ToList();
}
private static bool AreSameGroup(PsvDocumentGroupSummary left, PsvDocumentGroupSummary right) private static bool AreSameGroup(PsvDocumentGroupSummary left, PsvDocumentGroupSummary right)
{ {
return left != null return left != null
&& right != null && right != null
&& string.Equals(left.InstrumentType ?? string.Empty, right.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(left.InstrumentType ?? string.Empty, right.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(left.RangeText ?? string.Empty, right.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(left.RangeText ?? string.Empty, right.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(left.AccuracyText ?? string.Empty, right.AccuracyText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(left.RegistryNumber ?? string.Empty, right.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase); && string.Equals(left.RegistryNumber ?? string.Empty, right.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
} }
@@ -1790,12 +1887,41 @@ namespace XLAB
return new List<PsvDocumentLine>(); return new List<PsvDocumentLine>();
} }
var pendingKey = GetPendingLinesStorageKey(document);
if (string.IsNullOrWhiteSpace(pendingKey))
{
return new List<PsvDocumentLine>();
}
List<PsvDocumentLine> lines; List<PsvDocumentLine> lines;
return _pendingLinesByDocumentKey.TryGetValue(document.DocumentKey, out lines) return _pendingLinesByDocumentKey.TryGetValue(pendingKey, out lines)
? lines ? lines
: new List<PsvDocumentLine>(); : new List<PsvDocumentLine>();
} }
private static string GetPendingLinesStorageKey(PsvDocumentSummary document)
{
if (document == null)
{
return string.Empty;
}
if (!document.IsDraft && !string.IsNullOrWhiteSpace(document.DocumentNumber))
{
return document.DocumentNumber.Trim();
}
return string.IsNullOrWhiteSpace(document.DocumentKey)
? string.Empty
: document.DocumentKey.Trim();
}
private static bool MatchesPendingLinesStorageKey(PsvDocumentSummary document, string pendingKey)
{
return !string.IsNullOrWhiteSpace(pendingKey)
&& string.Equals(GetPendingLinesStorageKey(document), pendingKey, StringComparison.OrdinalIgnoreCase);
}
private List<PsvDocumentLine> MergeDocumentLinesForPrint(PsvDocumentSummary document, IEnumerable<PsvDocumentLine> persistedLines) private List<PsvDocumentLine> MergeDocumentLinesForPrint(PsvDocumentSummary document, IEnumerable<PsvDocumentLine> persistedLines)
{ {
var mergedLines = new List<PsvDocumentLine>(); var mergedLines = new List<PsvDocumentLine>();
@@ -1856,7 +1982,7 @@ namespace XLAB
private async Task LoadCustomersCoreAsync() private async Task LoadCustomersCoreAsync()
{ {
var customers = await Task.Run(delegate { return _service.LoadCustomers(); }); var customers = await _service.LoadCustomersAsync();
ClearCollections(Customers); ClearCollections(Customers);
foreach (var customer in customers) foreach (var customer in customers)
{ {
@@ -1907,7 +2033,7 @@ namespace XLAB
var previousGroup = SelectedDocumentGroup; var previousGroup = SelectedDocumentGroup;
var documentNumber = SelectedDocument.DocumentNumber; var documentNumber = SelectedDocument.DocumentNumber;
var persistedLines = await Task.Run(delegate { return _service.LoadDocumentLines(documentNumber); }); var persistedLines = await _service.LoadDocumentLinesAsync(documentNumber);
var mergedLines = persistedLines.Concat(GetPendingLines(SelectedDocument)).ToList(); var mergedLines = persistedLines.Concat(GetPendingLines(SelectedDocument)).ToList();
ApplyDocumentLines(mergedLines, previousGroup); ApplyDocumentLines(mergedLines, previousGroup);
@@ -1936,7 +2062,7 @@ namespace XLAB
try try
{ {
IsBusy = true; IsBusy = true;
instruments = await Task.Run(delegate { return _service.LoadCustomerInstruments(customerId); }); instruments = await _service.LoadCustomerInstrumentsAsync(customerId);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1980,7 +2106,7 @@ namespace XLAB
try try
{ {
IsBusy = true; IsBusy = true;
instrumentTypes = await Task.Run(delegate { return _service.LoadInstrumentTypes(); }); instrumentTypes = await _service.LoadInstrumentTypesAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -2008,11 +2134,17 @@ namespace XLAB
return; return;
} }
var pendingKey = GetPendingLinesStorageKey(SelectedDocument);
if (string.IsNullOrWhiteSpace(pendingKey))
{
return;
}
List<PsvDocumentLine> pendingLines; List<PsvDocumentLine> pendingLines;
if (!_pendingLinesByDocumentKey.TryGetValue(SelectedDocument.DocumentKey, out pendingLines)) if (!_pendingLinesByDocumentKey.TryGetValue(pendingKey, out pendingLines))
{ {
pendingLines = new List<PsvDocumentLine>(); pendingLines = new List<PsvDocumentLine>();
_pendingLinesByDocumentKey[SelectedDocument.DocumentKey] = pendingLines; _pendingLinesByDocumentKey[pendingKey] = pendingLines;
} }
var candidateLines = selectedItems var candidateLines = selectedItems
@@ -2074,7 +2206,7 @@ namespace XLAB
if (SelectedDocument.IsDraft) if (SelectedDocument.IsDraft)
{ {
SelectedDocument.ItemCount = pendingLines.Count; UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines);
} }
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
@@ -2125,17 +2257,28 @@ namespace XLAB
return; return;
} }
List<PsvDocumentLine> pendingLines; var pendingKey = GetPendingLinesStorageKey(SelectedDocument);
if (!_pendingLinesByDocumentKey.TryGetValue(SelectedDocument.DocumentKey, out pendingLines)) if (string.IsNullOrWhiteSpace(pendingKey))
{ {
pendingLines = new List<PsvDocumentLine>(); return;
_pendingLinesByDocumentKey[SelectedDocument.DocumentKey] = pendingLines;
} }
var serialNumber = string.IsNullOrWhiteSpace(result.SerialNumber) ? string.Empty : result.SerialNumber.Trim(); List<PsvDocumentLine> pendingLines;
if (string.IsNullOrWhiteSpace(serialNumber)) if (!_pendingLinesByDocumentKey.TryGetValue(pendingKey, out pendingLines))
{ {
_dialogService.ShowWarning("Введите заводской номер."); pendingLines = new List<PsvDocumentLine>();
_pendingLinesByDocumentKey[pendingKey] = pendingLines;
}
var serialNumbers = (result.SerialNumbers ?? Array.Empty<string>())
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Select(delegate(string value) { return value.Trim(); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (serialNumbers.Count == 0)
{
_dialogService.ShowWarning("Введите хотя бы один заводской номер.");
return; return;
} }
@@ -2145,11 +2288,34 @@ namespace XLAB
return; return;
} }
var candidateLine = CreatePendingTypeLine(result.TypeItem, serialNumber); var validSerialNumbers = new List<string>();
var skippedInvalidLengthCount = 0;
foreach (var serialNumber in serialNumbers)
{
if (serialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
skippedInvalidLengthCount++;
continue;
}
validSerialNumbers.Add(serialNumber);
}
if (validSerialNumbers.Count == 0)
{
_dialogService.ShowWarning(string.Format("Каждый заводской номер должен содержать не более {0} символов.", EkzDirectoryRules.SerialNumberMaxLength));
return;
}
var candidateLines = validSerialNumbers
.Select(delegate(string serialNumber) { return CreatePendingTypeLine(result.TypeItem, serialNumber); })
.ToList();
List<OpenDocumentConflictInfo> openDocumentConflicts; List<OpenDocumentConflictInfo> openDocumentConflicts;
try try
{ {
openDocumentConflicts = FindOpenDocumentConflicts(new[] { candidateLine }); openDocumentConflicts = FindOpenDocumentConflicts(candidateLines);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -2157,36 +2323,88 @@ namespace XLAB
return; return;
} }
if (openDocumentConflicts.Count > 0) var openConflictKeys = new HashSet<string>(
openDocumentConflicts.Select(delegate(OpenDocumentConflictInfo conflict) { return conflict.OpenDocumentConflictKey; }),
StringComparer.OrdinalIgnoreCase);
var duplicateKeys = new HashSet<string>(DocumentLines.Select(delegate(PsvDocumentLine line) { return line.DuplicateKey; }), StringComparer.OrdinalIgnoreCase);
var addedCount = 0;
var skippedDuplicateCount = 0;
var skippedOpenDocumentCount = 0;
foreach (var serialNumber in validSerialNumbers)
{ {
_dialogService.ShowWarning(BuildOpenDocumentConflictMessage(openDocumentConflicts)); if (openConflictKeys.Contains(PsvDocumentLine.BuildOpenDocumentConflictKey(result.TypeItem.TypeSizeId, serialNumber)))
return; {
skippedOpenDocumentCount++;
continue;
}
var duplicateKey = PsvDocumentLine.BuildDuplicateKey(
result.TypeItem.InstrumentType,
result.TypeItem.RangeText,
result.TypeItem.RegistryNumber,
serialNumber);
if (duplicateKeys.Contains(duplicateKey))
{
skippedDuplicateCount++;
continue;
}
pendingLines.Add(CreatePendingTypeLine(result.TypeItem, serialNumber));
duplicateKeys.Add(duplicateKey);
addedCount++;
} }
var duplicateKey = PsvDocumentLine.BuildDuplicateKey( if (addedCount == 0 && skippedDuplicateCount > 0 && skippedOpenDocumentCount == 0 && skippedInvalidLengthCount == 0)
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("Такие приборы уже есть в ПСВ.");
}))
{
_dialogService.ShowWarning("Такой прибор уже есть в ПСВ.");
return; return;
} }
pendingLines.Add(candidateLine);
if (SelectedDocument.IsDraft) if (SelectedDocument.IsDraft)
{ {
SelectedDocument.ItemCount = pendingLines.Count; UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines);
} }
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
_dialogService.ShowInfo("Прибор по типу добавлен в ПСВ.");
var messages = new List<string>();
if (addedCount > 0)
{
messages.Add(string.Format("Добавлено приборов по типу: {0}.", addedCount));
}
if (skippedDuplicateCount > 0)
{
messages.Add(string.Format("Исключено дублей: {0}.", skippedDuplicateCount));
}
if (skippedInvalidLengthCount > 0)
{
messages.Add(string.Format("Пропущено из-за длины зав. № более {0} символов: {1}.", EkzDirectoryRules.SerialNumberMaxLength, skippedInvalidLengthCount));
}
if (skippedOpenDocumentCount > 0)
{
messages.Add(string.Format("Пропущено из-за других открытых ПСВ: {0}.", skippedOpenDocumentCount));
messages.Add(BuildOpenDocumentConflictMessage(openDocumentConflicts));
}
if (messages.Count > 0)
{
var message = string.Join(" ", messages.ToArray());
if (addedCount == 0)
{
_dialogService.ShowWarning(message);
}
else
{
_dialogService.ShowInfo(message);
}
}
RaiseCommandStates(); RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsCustomerEditable");
} }
@@ -2202,7 +2420,8 @@ namespace XLAB
} }
RebuildDocumentGroupSummaries(DocumentLines); RebuildDocumentGroupSummaries(DocumentLines);
SelectedDocumentGroup = FindMatchingGroup(previousGroup) ?? DocumentGroupSummaries.FirstOrDefault(); SelectedDocumentGroup = FindMatchingVisibleGroup(previousGroup)
?? GetVisibleDocumentGroups().FirstOrDefault();
SelectedDocumentLine = previousLine == null SelectedDocumentLine = previousLine == null
? null ? null
: DocumentLines.FirstOrDefault(delegate(PsvDocumentLine line) : DocumentLines.FirstOrDefault(delegate(PsvDocumentLine line)
@@ -2217,6 +2436,7 @@ namespace XLAB
&& string.Equals(line.DuplicateKey, previousLine.DuplicateKey, StringComparison.OrdinalIgnoreCase); && string.Equals(line.DuplicateKey, previousLine.DuplicateKey, StringComparison.OrdinalIgnoreCase);
}); });
HeaderInstrumentCount = DocumentLines.Count; HeaderInstrumentCount = DocumentLines.Count;
RefreshDocumentGroupsView();
RefreshDocumentLinesView(); RefreshDocumentLinesView();
RaiseCommandStates(); RaiseCommandStates();
} }
@@ -2251,20 +2471,26 @@ namespace XLAB
{ {
InstrumentType = line.InstrumentType ?? string.Empty, InstrumentType = line.InstrumentType ?? string.Empty,
RangeText = line.RangeText ?? string.Empty, RangeText = line.RangeText ?? string.Empty,
AccuracyText = line.AccuracyText ?? string.Empty,
RegistryNumber = line.RegistryNumber ?? string.Empty RegistryNumber = line.RegistryNumber ?? string.Empty
}) })
.OrderBy(group => group.Key.InstrumentType) .OrderBy(group => group.Key.InstrumentType)
.ThenBy(group => group.Key.RegistryNumber) .ThenBy(group => group.Key.RegistryNumber)
.ThenBy(group => group.Key.RangeText) .ThenBy(group => group.Key.RangeText)
.ThenBy(group => group.Key.AccuracyText)
.Select(group => new PsvDocumentGroupSummary .Select(group => new PsvDocumentGroupSummary
{ {
InstrumentName = BuildInstrumentNamesText(group),
InstrumentType = group.Key.InstrumentType, InstrumentType = group.Key.InstrumentType,
RangeText = group.Key.RangeText, RangeText = group.Key.RangeText,
AccuracyText = group.Key.AccuracyText,
RegistryNumber = group.Key.RegistryNumber, RegistryNumber = group.Key.RegistryNumber,
SerialNumbersText = BuildSerialNumbersText(group),
IsBatchSelected = checkedGroups.Any(delegate(PsvDocumentGroupSummary previous) IsBatchSelected = checkedGroups.Any(delegate(PsvDocumentGroupSummary previous)
{ {
return string.Equals(previous.InstrumentType ?? string.Empty, group.Key.InstrumentType, StringComparison.OrdinalIgnoreCase) return string.Equals(previous.InstrumentType ?? string.Empty, group.Key.InstrumentType, StringComparison.OrdinalIgnoreCase)
&& string.Equals(previous.RangeText ?? string.Empty, group.Key.RangeText, StringComparison.OrdinalIgnoreCase) && string.Equals(previous.RangeText ?? string.Empty, group.Key.RangeText, StringComparison.OrdinalIgnoreCase)
&& string.Equals(previous.AccuracyText ?? string.Empty, group.Key.AccuracyText, StringComparison.OrdinalIgnoreCase)
&& string.Equals(previous.RegistryNumber ?? string.Empty, group.Key.RegistryNumber, StringComparison.OrdinalIgnoreCase); && string.Equals(previous.RegistryNumber ?? string.Empty, group.Key.RegistryNumber, StringComparison.OrdinalIgnoreCase);
}), }),
InVerificationCount = group.Count(line => !line.IsPassed.HasValue), InVerificationCount = group.Count(line => !line.IsPassed.HasValue),
@@ -2280,6 +2506,36 @@ namespace XLAB
} }
} }
private void RefreshDocumentGroupsView()
{
if (DocumentGroupsView != null)
{
DocumentGroupsView.Refresh();
}
var selectedVisibleGroup = FindMatchingVisibleGroup(SelectedDocumentGroup);
if (selectedVisibleGroup != null)
{
if (!ReferenceEquals(SelectedDocumentGroup, selectedVisibleGroup))
{
SelectedDocumentGroup = selectedVisibleGroup;
return;
}
RefreshDocumentLinesView();
return;
}
var firstVisibleGroup = GetVisibleDocumentGroups().FirstOrDefault();
if (!ReferenceEquals(SelectedDocumentGroup, firstVisibleGroup))
{
SelectedDocumentGroup = firstVisibleGroup;
return;
}
RefreshDocumentLinesView();
}
private void RefreshDocumentLinesView() private void RefreshDocumentLinesView()
{ {
DocumentLinesView.Refresh(); DocumentLinesView.Refresh();
@@ -2296,7 +2552,7 @@ namespace XLAB
{ {
DocumentStatusText = "Загрузка списка ПСВ..."; DocumentStatusText = "Загрузка списка ПСВ...";
var databaseDocuments = await Task.Run(delegate { return _service.LoadDocuments(ShowClosedDocuments); }); var databaseDocuments = await _service.LoadDocumentsAsync(ShowClosedDocuments);
var currentDocumentKey = documentKeyToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentKey : null); var currentDocumentKey = documentKeyToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentKey : null);
var currentDocumentNumber = documentNumberToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentNumber : null); var currentDocumentNumber = documentNumberToSelect ?? (SelectedDocument != null ? SelectedDocument.DocumentNumber : null);
@@ -2375,7 +2631,7 @@ namespace XLAB
} }
if (DocumentExistsInCollections(DocumentNumberEditor.Trim(), selectedDocument.DocumentKey) if (DocumentExistsInCollections(DocumentNumberEditor.Trim(), selectedDocument.DocumentKey)
|| _service.DocumentNumberExists(DocumentNumberEditor.Trim(), selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber)) || await _service.DocumentNumberExistsAsync(DocumentNumberEditor.Trim(), selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber))
{ {
_dialogService.ShowWarning("ПСВ с таким номером уже существует."); _dialogService.ShowWarning("ПСВ с таким номером уже существует.");
return; return;
@@ -2412,12 +2668,14 @@ namespace XLAB
var currentDocumentNumber = selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber; var currentDocumentNumber = selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber;
var documentKey = selectedDocument.DocumentKey; var documentKey = selectedDocument.DocumentKey;
var documentPendingKey = GetPendingLinesStorageKey(selectedDocument);
var wasDraft = selectedDocument.IsDraft; var wasDraft = selectedDocument.IsDraft;
var closingNow = !selectedDocument.IssuedOn.HasValue && request.IssuedOn.HasValue; var closingNow = !selectedDocument.IssuedOn.HasValue && request.IssuedOn.HasValue;
var printDocument = closingNow ? CreateSavedDocumentSummaryForPrint(selectedDocument, request) : null; var printDocument = closingNow ? CreateSavedDocumentSummaryForPrint(selectedDocument, request) : null;
var result = await Task.Run(delegate { return _service.SaveDocument(currentDocumentNumber, request, pendingLines); }); var documentLinesSnapshot = DocumentLines.ToList();
var result = await Task.Run(delegate { return _service.SaveDocument(currentDocumentNumber, request, documentLinesSnapshot); });
_pendingLinesByDocumentKey.Remove(documentKey); _pendingLinesByDocumentKey.Remove(documentPendingKey);
if (wasDraft) if (wasDraft)
{ {
_draftDocuments.RemoveAll(delegate(PsvDocumentSummary draft) { return draft.DocumentKey == documentKey; }); _draftDocuments.RemoveAll(delegate(PsvDocumentSummary draft) { return draft.DocumentKey == documentKey; });
@@ -2443,7 +2701,7 @@ namespace XLAB
var prompt = messageText + " ПСВ закрыта. Распечатать приемо-сдаточную ведомость?"; var prompt = messageText + " ПСВ закрыта. Распечатать приемо-сдаточную ведомость?";
if (_dialogService.Confirm(prompt)) if (_dialogService.Confirm(prompt))
{ {
var printLines = await Task.Run(delegate { return _service.LoadDocumentLines(result.DocumentNumber); }); var printLines = await _service.LoadDocumentLinesAsync(result.DocumentNumber);
if (printDocument != null) if (printDocument != null)
{ {
printDocument.DocumentNumber = result.DocumentNumber; printDocument.DocumentNumber = result.DocumentNumber;
@@ -2462,6 +2720,8 @@ namespace XLAB
private void UpdateLineStatus() private void UpdateLineStatus()
{ {
var visibleGroupCount = GetVisibleDocumentGroups().Count;
if (SelectedDocument == null) if (SelectedDocument == null)
{ {
DetailTableCountText = "Приборов в таблице: 0."; DetailTableCountText = "Приборов в таблице: 0.";
@@ -2478,10 +2738,19 @@ namespace XLAB
return; return;
} }
if (visibleGroupCount == 0)
{
DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = string.IsNullOrWhiteSpace(GroupFilterText)
? "Выберите группу."
: string.Format("Группы по фильтру \"{0}\" не найдены.", GroupFilterText.Trim());
return;
}
if (SelectedDocumentGroup == null) if (SelectedDocumentGroup == null)
{ {
DetailTableCountText = "Приборов в таблице: 0."; DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = string.Format("Групп: {0}. Выберите группу.", DocumentGroupSummaries.Count); LineStatusText = string.Format("Групп: {0}/{1}. Выберите группу.", visibleGroupCount, DocumentGroupSummaries.Count);
return; return;
} }
@@ -2495,7 +2764,8 @@ namespace XLAB
DetailTableCountText = string.Format("Приборов в таблице: {0}.", filteredCount); DetailTableCountText = string.Format("Приборов в таблице: {0}.", filteredCount);
LineStatusText = string.Format( LineStatusText = string.Format(
"Групп: {0}. Приборов в выбранной группе: {1}. Отображено по фильтру: {2}. Не сохранено строк: {3}.", "Групп: {0}/{1}. Приборов в выбранной группе: {2}. Отображено по фильтру: {3}. Не сохранено строк: {4}.",
visibleGroupCount,
DocumentGroupSummaries.Count, DocumentGroupSummaries.Count,
groupLineCount, groupLineCount,
filteredCount, filteredCount,

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

@@ -4,11 +4,15 @@ using System.Configuration;
using System.Data; using System.Data;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace XLAB namespace XLAB
{ {
internal sealed class PsvDataService internal sealed class PsvDataService
{ {
private const int EkzMkCompletenessMaxLength = 600;
public bool DocumentNumberExists(string documentNumber, string excludeDocumentNumber) public bool DocumentNumberExists(string documentNumber, string excludeDocumentNumber)
{ {
var normalizedNumber = NormalizeDocumentNumber(documentNumber); var normalizedNumber = NormalizeDocumentNumber(documentNumber);
@@ -34,6 +38,15 @@ WHERE NNZVPV = @DocumentNumber
} }
} }
public Task<bool> DocumentNumberExistsAsync(string documentNumber, string excludeDocumentNumber, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return DocumentNumberExists(documentNumber, excludeDocumentNumber);
}, cancellationToken);
}
public IReadOnlyList<CustomerReference> LoadCustomers() public IReadOnlyList<CustomerReference> LoadCustomers()
{ {
const string sql = @" const string sql = @"
@@ -558,11 +571,84 @@ ORDER BY " + (loadClosedDocumentsForCurrentYear
}); });
} }
} }
var serialNumbersByDocument = LoadDocumentSerialNumbers(connection, loadClosedDocumentsForCurrentYear, currentYearStart, nextYearStart);
foreach (var document in documents)
{
string serialNumbersText;
if (serialNumbersByDocument.TryGetValue(document.DocumentNumber, out serialNumbersText))
{
document.SerialNumbersText = serialNumbersText;
}
}
} }
return documents; return documents;
} }
private Dictionary<string, string> LoadDocumentSerialNumbers(SqlConnection connection, bool loadClosedDocumentsForCurrentYear, DateTime currentYearStart, DateTime nextYearStart)
{
var sql = @"
WITH filteredDocuments AS
(
SELECT m.NNZVPV
FROM dbo.EKZMK m
WHERE NULLIF(LTRIM(RTRIM(m.NNZVPV)), N'') IS NOT NULL
GROUP BY m.NNZVPV
HAVING " + (loadClosedDocumentsForCurrentYear
? "MAX(m.DTVDM) >= @IssuedFrom AND MAX(m.DTVDM) < @IssuedTo"
: "MAX(m.DTVDM) IS NULL") + @"
)
SELECT
m.NNZVPV AS DocumentNumber,
z.NNZV AS SerialNumber
FROM dbo.EKZMK m
JOIN filteredDocuments documents ON documents.NNZVPV = m.NNZVPV
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
WHERE NULLIF(LTRIM(RTRIM(z.NNZV)), N'') IS NOT NULL
ORDER BY m.NNZVPV, z.NNZV;";
var serialNumbersByDocument = new Dictionary<string, SortedSet<string>>(StringComparer.OrdinalIgnoreCase);
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60;
if (loadClosedDocumentsForCurrentYear)
{
command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart;
command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart;
}
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var documentNumber = GetString(reader, "DocumentNumber");
var serialNumber = GetString(reader, "SerialNumber");
if (string.IsNullOrWhiteSpace(documentNumber) || string.IsNullOrWhiteSpace(serialNumber))
{
continue;
}
SortedSet<string> serialNumbers;
if (!serialNumbersByDocument.TryGetValue(documentNumber, out serialNumbers))
{
serialNumbers = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
serialNumbersByDocument[documentNumber] = serialNumbers;
}
serialNumbers.Add(serialNumber.Trim());
}
}
}
return serialNumbersByDocument.ToDictionary(
delegate(KeyValuePair<string, SortedSet<string>> pair) { return pair.Key; },
delegate(KeyValuePair<string, SortedSet<string>> pair) { return string.Join(", ", pair.Value); },
StringComparer.OrdinalIgnoreCase);
}
public IReadOnlyList<PsvDocumentLine> LoadDocumentLines(string documentNumber) public IReadOnlyList<PsvDocumentLine> LoadDocumentLines(string documentNumber)
{ {
const string sql = @" const string sql = @"
@@ -1025,7 +1111,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
} }
} }
public DocumentSaveResult SaveDocument(string currentDocumentNumber, DocumentEditorResult document, IEnumerable<PsvDocumentLine> pendingLines) public DocumentSaveResult SaveDocument(string currentDocumentNumber, DocumentEditorResult document, IEnumerable<PsvDocumentLine> documentLines)
{ {
if (document == null) if (document == null)
{ {
@@ -1040,14 +1126,23 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
document.DocumentNumber = normalizedNumber; document.DocumentNumber = normalizedNumber;
var distinctPendingLines = pendingLines == null var materializedDocumentLines = documentLines == null
? new List<PsvDocumentLine>() ? new List<PsvDocumentLine>()
: pendingLines : documentLines
.Where(IsPendingLineReadyForSave) .Where(delegate(PsvDocumentLine line) { return line != null; })
.GroupBy(GetPendingLineSaveKey, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, PsvDocumentLine> group) { return group.First(); })
.ToList(); .ToList();
var distinctPendingLines = materializedDocumentLines
.Where(IsPendingLineReadyForSave)
.GroupBy(GetPendingLineSaveKey, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, PsvDocumentLine> group) { return group.First(); })
.ToList();
var persistedLines = materializedDocumentLines
.Where(delegate(PsvDocumentLine line) { return !line.IsPendingInsert && line.CardId > 0; })
.GroupBy(delegate(PsvDocumentLine line) { return line.CardId; })
.Select(delegate(IGrouping<int, PsvDocumentLine> group) { return group.First(); })
.ToList();
using (var connection = CreateConnection()) using (var connection = CreateConnection())
{ {
connection.Open(); connection.Open();
@@ -1081,6 +1176,12 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
{ {
throw new InvalidOperationException("Строки EKZMK для выбранного ПСВ не найдены."); throw new InvalidOperationException("Строки EKZMK для выбранного ПСВ не найдены.");
} }
var updatedCompletenessCount = UpdateDocumentLineCompleteness(connection, transaction, normalizedNumber, persistedLines);
if (updatedCompletenessCount > updatedEkzMkCount)
{
updatedEkzMkCount = updatedCompletenessCount;
}
} }
var insertedEkzMkCount = 0; var insertedEkzMkCount = 0;
@@ -1122,13 +1223,19 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
continue; continue;
} }
if (!verificationTypeLoaded) var effectiveVerificationTypeId = template.IdSpvdmk;
if (!effectiveVerificationTypeId.HasValue || effectiveVerificationTypeId.Value <= 0)
{ {
verificationTypeId = LoadVerificationTypeId(connection, transaction); if (!verificationTypeLoaded)
verificationTypeLoaded = true; {
verificationTypeId = LoadVerificationTypeId(connection, transaction);
verificationTypeLoaded = true;
}
effectiveVerificationTypeId = verificationTypeId;
} }
var cardId = InsertEkzMk(connection, transaction, verificationTypeId, document, normalizedNumber, instrumentId, template, pendingLine); var cardId = InsertEkzMk(connection, transaction, effectiveVerificationTypeId.Value, document, normalizedNumber, instrumentId, template, pendingLine);
if (!string.IsNullOrWhiteSpace(pendingLine.VerificationDocumentNumber)) if (!string.IsNullOrWhiteSpace(pendingLine.VerificationDocumentNumber))
{ {
if (!pendingLine.VerificationDocumentFormId.HasValue || !pendingLine.VerificationDocumentLinkTypeId.HasValue) if (!pendingLine.VerificationDocumentFormId.HasValue || !pendingLine.VerificationDocumentLinkTypeId.HasValue)
@@ -1277,6 +1384,123 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
} }
} }
public Task<IReadOnlyList<CustomerReference>> LoadCustomersAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<CustomerReference>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadCustomers();
}, cancellationToken);
}
public Task<IReadOnlyList<PersonReference>> LoadVerifiersAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<PersonReference>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadVerifiers();
}, cancellationToken);
}
public Task<IReadOnlyList<DocumentFormReference>> LoadVerificationDocumentFormsAsync(bool isPassed, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<DocumentFormReference>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadVerificationDocumentForms(isPassed);
}, cancellationToken);
}
public Task<IReadOnlyList<PsvDocumentSummary>> LoadDocumentsAsync(bool loadClosedDocumentsForCurrentYear, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<PsvDocumentSummary>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadDocuments(loadClosedDocumentsForCurrentYear);
}, cancellationToken);
}
public Task<IReadOnlyList<PsvDocumentLine>> LoadDocumentLinesAsync(string documentNumber, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<PsvDocumentLine>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadDocumentLines(documentNumber);
}, cancellationToken);
}
public Task<IReadOnlyList<AvailableInstrumentItem>> LoadCustomerInstrumentsAsync(int customerId, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<AvailableInstrumentItem>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadCustomerInstruments(customerId);
}, cancellationToken);
}
public Task<IReadOnlyList<AvailableInstrumentItem>> LoadInstrumentTypesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run<IReadOnlyList<AvailableInstrumentItem>>(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return LoadInstrumentTypes();
}, cancellationToken);
}
public Task ResetLineVerificationAsync(int cardId, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
ResetLineVerification(cardId);
}, cancellationToken);
}
public Task SaveLineVerificationAsync(int cardId, VerificationEditResult result, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
SaveLineVerification(cardId, result);
}, cancellationToken);
}
public Task ResetLineVerificationAsync(IEnumerable<int> cardIds, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
ResetLineVerification(cardIds);
}, cancellationToken);
}
public Task SaveLineVerificationAsync(IEnumerable<int> cardIds, VerificationEditResult result, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
SaveLineVerification(cardIds, result);
}, cancellationToken);
}
public Task<DocumentDeleteResult> DeleteDocumentAsync(string documentNumber, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return DeleteDocument(documentNumber);
}, cancellationToken);
}
public Task<DocumentGroupDeleteResult> DeleteDocumentGroupsAsync(string documentNumber, IEnumerable<int> cardIds, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(delegate
{
cancellationToken.ThrowIfCancellationRequested();
return DeleteDocumentGroups(documentNumber, cardIds);
}, cancellationToken);
}
private static string GetPendingLineSaveKey(PsvDocumentLine line) private static string GetPendingLineSaveKey(PsvDocumentLine line)
{ {
if (line == null) if (line == null)
@@ -1292,6 +1516,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
private static bool IsPendingLineReadyForSave(PsvDocumentLine line) private static bool IsPendingLineReadyForSave(PsvDocumentLine line)
{ {
return line != null return line != null
&& line.IsPendingInsert
&& (line.InstrumentId > 0 && (line.InstrumentId > 0
|| (line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber))); || (line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber)));
} }
@@ -1319,6 +1544,11 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
throw new InvalidOperationException("Для новой строки ПСВ не указан заводской номер."); throw new InvalidOperationException("Для новой строки ПСВ не указан заводской номер.");
} }
if (serialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength));
}
if (!document.CustomerId.HasValue) if (!document.CustomerId.HasValue)
{ {
throw new InvalidOperationException("Для добавления прибора по типу должен быть выбран заказчик ПСВ."); throw new InvalidOperationException("Для добавления прибора по типу должен быть выбран заказчик ПСВ.");
@@ -1934,7 +2164,7 @@ VALUES
@idsptsmp, @idsptsmp,
@idspssmp, @idspssmp,
@GUIDEKZMK, @GUIDEKZMK,
NULL, @DSEKZMK,
NULL, NULL,
NULL, NULL,
NULL, NULL,
@@ -1988,6 +2218,9 @@ SELECT CAST(SCOPE_IDENTITY() AS int);";
command.Parameters.Add("@idsptsmp", SqlDbType.Int).Value = (object)template.IdSptsmp ?? DBNull.Value; command.Parameters.Add("@idsptsmp", SqlDbType.Int).Value = (object)template.IdSptsmp ?? DBNull.Value;
command.Parameters.Add("@idspssmp", SqlDbType.Int).Value = (object)template.IdSpssmp ?? DBNull.Value; command.Parameters.Add("@idspssmp", SqlDbType.Int).Value = (object)template.IdSpssmp ?? DBNull.Value;
command.Parameters.Add("@GUIDEKZMK", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid(); command.Parameters.Add("@GUIDEKZMK", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
command.Parameters.Add("@DSEKZMK", SqlDbType.NVarChar, EkzMkCompletenessMaxLength).Value = pendingLine == null
? DBNull.Value
: (object)NormalizeOptionalCompleteness(pendingLine.Notes) ?? DBNull.Value;
command.Parameters.Add("@NRVRMNDmp", SqlDbType.Decimal).Value = (object)template.Nrvrmndmp ?? DBNull.Value; command.Parameters.Add("@NRVRMNDmp", SqlDbType.Decimal).Value = (object)template.Nrvrmndmp ?? DBNull.Value;
command.Parameters["@NRVRMNDmp"].Precision = 10; command.Parameters["@NRVRMNDmp"].Precision = 10;
command.Parameters["@NRVRMNDmp"].Scale = 2; command.Parameters["@NRVRMNDmp"].Scale = 2;
@@ -2246,8 +2479,17 @@ WHERE z.IDEKZ = @InstrumentId;";
const string sql = @" const string sql = @"
SELECT TOP (1) IDSPVDMK SELECT TOP (1) IDSPVDMK
FROM dbo.SPVDMK FROM dbo.SPVDMK
WHERE OBVDMK = N'П' OR NMVDMK = N'Поверка' WHERE UPPER(LTRIM(RTRIM(ISNULL(OBVDMK, N'')))) = N'П'
ORDER BY CASE WHEN OBVDMK = N'П' THEN 0 ELSE 1 END, IDSPVDMK;"; OR UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) = N'ПОВЕРКА'
OR UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) LIKE N'ПОВЕРК%'
OR UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) LIKE N'%ПОВЕРК%'
ORDER BY CASE
WHEN UPPER(LTRIM(RTRIM(ISNULL(OBVDMK, N'')))) = N'П' THEN 0
WHEN UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) = N'ПОВЕРКА' THEN 1
WHEN UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) LIKE N'ПОВЕРК%' THEN 2
ELSE 3
END,
IDSPVDMK;";
using (var command = new SqlCommand(sql, connection, transaction)) using (var command = new SqlCommand(sql, connection, transaction))
{ {
@@ -2277,6 +2519,7 @@ WITH TemplateCandidates AS
m.IDSPMU, m.IDSPMU,
m.IDGRSI, m.IDGRSI,
m.IDKSPRL, m.IDKSPRL,
m.IDSPVDMK,
m.IDSPVDMC, m.IDSPVDMC,
m.IDFRPD, m.IDFRPD,
m.IDSPMPOB, m.IDSPMPOB,
@@ -2315,6 +2558,7 @@ WITH TemplateCandidates AS
m.IDSPMU, m.IDSPMU,
m.IDGRSI, m.IDGRSI,
m.IDKSPRL, m.IDKSPRL,
m.IDSPVDMK,
m.IDSPVDMC, m.IDSPVDMC,
m.IDFRPD, m.IDFRPD,
m.IDSPMPOB, m.IDSPMPOB,
@@ -2363,6 +2607,7 @@ ORDER BY Priority;";
IdSpmu = GetNullableInt32(reader, "IDSPMU"), IdSpmu = GetNullableInt32(reader, "IDSPMU"),
IdGrsi = GetNullableInt32(reader, "IDGRSI"), IdGrsi = GetNullableInt32(reader, "IDGRSI"),
IdKsprl = GetNullableInt32(reader, "IDKSPRL"), IdKsprl = GetNullableInt32(reader, "IDKSPRL"),
IdSpvdmk = GetNullableInt32(reader, "IDSPVDMK"),
IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"), IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"),
IdFrpd = GetInt32(reader, "IDFRPD"), IdFrpd = GetInt32(reader, "IDFRPD"),
IdSpmpob = GetNullableInt32(reader, "IDSPMPOB"), IdSpmpob = GetNullableInt32(reader, "IDSPMPOB"),
@@ -2464,6 +2709,7 @@ WHERE z.IDEKZ = @InstrumentId
IdSpmu = null, IdSpmu = null,
IdGrsi = GetNullableInt32(reader, "IDGRSI"), IdGrsi = GetNullableInt32(reader, "IDGRSI"),
IdKsprl = null, IdKsprl = null,
IdSpvdmk = null,
IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"), IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"),
IdFrpd = GetInt32(reader, "IDFRPD"), IdFrpd = GetInt32(reader, "IDFRPD"),
IdSpmpob = null, IdSpmpob = null,
@@ -2494,6 +2740,75 @@ WHERE z.IDEKZ = @InstrumentId
} }
} }
private static int UpdateDocumentLineCompleteness(
SqlConnection connection,
SqlTransaction transaction,
string documentNumber,
IEnumerable<PsvDocumentLine> persistedLines)
{
var materializedLines = persistedLines == null
? new List<PsvDocumentLine>()
: persistedLines
.Where(delegate(PsvDocumentLine line) { return line != null && line.CardId > 0; })
.GroupBy(delegate(PsvDocumentLine line) { return line.CardId; })
.Select(delegate(IGrouping<int, PsvDocumentLine> group) { return group.First(); })
.ToList();
var updatedCount = 0;
foreach (var line in materializedLines)
{
updatedCount += UpdateDocumentLineCompletenessCore(
connection,
transaction,
documentNumber,
line.CardId,
NormalizeOptionalCompleteness(line.Notes));
}
return updatedCount;
}
private static int UpdateDocumentLineCompletenessCore(
SqlConnection connection,
SqlTransaction transaction,
string documentNumber,
int cardId,
string completeness)
{
const string sql = @"
UPDATE dbo.EKZMK
SET DSEKZMK = @Completeness
WHERE NNZVPV = @DocumentNumber
AND IDEKZMK = @CardId;
SELECT @@ROWCOUNT;";
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = 60;
command.Parameters.Add("@DocumentNumber", SqlDbType.NVarChar, 60).Value = documentNumber;
command.Parameters.Add("@CardId", SqlDbType.Int).Value = cardId;
command.Parameters.Add("@Completeness", SqlDbType.NVarChar, EkzMkCompletenessMaxLength).Value = (object)completeness ?? DBNull.Value;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static string NormalizeOptionalCompleteness(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
var normalizedValue = value.Trim();
if (normalizedValue.Length > EkzMkCompletenessMaxLength)
{
throw new InvalidOperationException(string.Format("Комплектность не должна превышать {0} символов.", EkzMkCompletenessMaxLength));
}
return normalizedValue;
}
private static string NormalizeDocumentNumber(string value) private static string NormalizeDocumentNumber(string value)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))

View File

@@ -1,4 +1,4 @@
using System; using System;
namespace XLAB namespace XLAB
{ {
@@ -40,6 +40,7 @@ namespace XLAB
private DateTime? _issuedOn; private DateTime? _issuedOn;
private int _itemCount; private int _itemCount;
private int _passedCount; private int _passedCount;
private string _serialNumbersText;
public DateTime? AcceptedOn public DateTime? AcceptedOn
{ {
@@ -89,6 +90,12 @@ namespace XLAB
set { SetProperty(ref _documentNumber, value); } set { SetProperty(ref _documentNumber, value); }
} }
public string SerialNumbersText
{
get { return _serialNumbersText; }
set { SetProperty(ref _serialNumbersText, value); }
}
public int FailedCount public int FailedCount
{ {
get { return _failedCount; } get { return _failedCount; }
@@ -197,6 +204,7 @@ namespace XLAB
public sealed class PsvDocumentLine : ObservableObject public sealed class PsvDocumentLine : ObservableObject
{ {
private bool _isBatchSelected; private bool _isBatchSelected;
private string _notes;
public int CardId { get; set; } public int CardId { get; set; }
@@ -250,7 +258,23 @@ namespace XLAB
public string RejectionReason { get; set; } public string RejectionReason { get; set; }
public string Notes { get; set; } public string Notes
{
get { return _notes; }
set
{
if (SetProperty(ref _notes, value))
{
OnPropertyChanged("Completeness");
}
}
}
public string Completeness
{
get { return Notes; }
set { Notes = value; }
}
public bool IsPendingInsert { get; set; } public bool IsPendingInsert { get; set; }
@@ -340,12 +364,18 @@ namespace XLAB
{ {
private bool _isBatchSelected; private bool _isBatchSelected;
public string InstrumentName { get; set; }
public string InstrumentType { get; set; } public string InstrumentType { get; set; }
public string RangeText { get; set; } public string RangeText { get; set; }
public string AccuracyText { get; set; }
public string RegistryNumber { get; set; } public string RegistryNumber { get; set; }
public string SerialNumbersText { get; set; }
public int InVerificationCount { get; set; } public int InVerificationCount { get; set; }
public int VerifiedCount { get; set; } public int VerifiedCount { get; set; }
@@ -365,6 +395,7 @@ namespace XLAB
return line != null return line != null
&& string.Equals(InstrumentType ?? string.Empty, line.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(InstrumentType ?? string.Empty, line.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(RangeText ?? string.Empty, line.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(RangeText ?? string.Empty, line.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(AccuracyText ?? string.Empty, line.AccuracyText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(RegistryNumber ?? string.Empty, line.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase); && string.Equals(RegistryNumber ?? string.Empty, line.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
} }
} }
@@ -499,7 +530,7 @@ namespace XLAB
{ {
public AvailableInstrumentItem TypeItem { get; set; } public AvailableInstrumentItem TypeItem { get; set; }
public string SerialNumber { get; set; } public System.Collections.Generic.IReadOnlyList<string> SerialNumbers { get; set; }
} }
public sealed class VerificationEditSeed public sealed class VerificationEditSeed
@@ -631,6 +662,8 @@ namespace XLAB
public int? IdSptsmp { get; set; } public int? IdSptsmp { get; set; }
public int? IdSpvdmk { get; set; }
public int? IdSpvdkl { get; set; } public int? IdSpvdkl { get; set; }
public int? IdSpvdsbmk { get; set; } public int? IdSpvdsbmk { get; set; }

View File

@@ -3,15 +3,21 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace XLAB namespace XLAB
{ {
internal sealed class PsvPrintService internal sealed class PsvPrintService
{ {
private const int ClosePsvTableColumnCount = 12;
private const int OpenPsvTableColumnCount = 7;
private const int PrintDialogId = 88; private const int PrintDialogId = 88;
private const int WordActiveEndAdjustedPageNumber = 1;
private const int WordAlertsNone = 0; private const int WordAlertsNone = 0;
private const int WordCloseDoNotSaveChanges = 0; private const int WordCloseDoNotSaveChanges = 0;
private const int WordParagraphTrue = -1;
private const int WordStatisticPages = 2;
public void PrintDocument(PsvDocumentSummary document, IReadOnlyList<PsvDocumentLine> lines) public void PrintDocument(PsvDocumentSummary document, IReadOnlyList<PsvDocumentLine> lines)
{ {
@@ -165,7 +171,7 @@ namespace XLAB
ReplacePlaceholder(document, "next", FormatDate(dueDate)); ReplacePlaceholder(document, "next", FormatDate(dueDate));
} }
FillTable(document, groupedLines); FillTable(document, groupedLines, summary.IssuedOn.HasValue);
} }
private static void PopulateVerificationTemplate(dynamic document, PsvDocumentLine line) private static void PopulateVerificationTemplate(dynamic document, PsvDocumentLine line)
@@ -187,13 +193,14 @@ namespace XLAB
ReplacePlaceholder(document, "date", FormatDate(verificationDate)); ReplacePlaceholder(document, "date", FormatDate(verificationDate));
} }
private static void FillTable(dynamic document, IReadOnlyList<PrintedGroupRow> rowsToPrint) private static void FillTable(dynamic document, IReadOnlyList<PrintedGroupRow> rowsToPrint, bool isClosedDocument)
{ {
dynamic table = null; dynamic table = null;
try try
{ {
table = document.Tables.Item(1); table = document.Tables.Item(1);
EnsurePsvTableLayout(table, isClosedDocument ? ClosePsvTableColumnCount : OpenPsvTableColumnCount);
for (var index = 0; index < rowsToPrint.Count; index++) for (var index = 0; index < rowsToPrint.Count; index++)
{ {
@@ -209,15 +216,28 @@ namespace XLAB
SetCellText(row, 4, rowData.RangeText, false); SetCellText(row, 4, rowData.RangeText, false);
SetCellText(row, 5, rowData.SerialNumberText, false); SetCellText(row, 5, rowData.SerialNumberText, false);
SetCellText(row, 6, rowData.GroupCount.ToString(CultureInfo.InvariantCulture), true); SetCellText(row, 6, rowData.GroupCount.ToString(CultureInfo.InvariantCulture), true);
SetCellText(row, 7, rowData.PassedCount > 0 ? rowData.PassedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true);
SetCellText(row, 8, rowData.FailedCount > 0 ? rowData.FailedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true); if (isClosedDocument)
SetCellText(row, 9, rowData.Notes, false); {
SetCellText(row, 7, rowData.PassedCount > 0 ? rowData.PassedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true);
SetCellText(row, 8, rowData.FailedCount > 0 ? rowData.FailedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true);
SetCellText(row, 9, rowData.VerificationDocumentText, false);
SetCellText(row, 10, rowData.StickerNumberText, false);
SetCellText(row, 11, rowData.VerifierNameText, false);
SetCellText(row, 12, rowData.Notes, false);
}
else
{
SetCellText(row, 7, rowData.Notes, false);
}
} }
finally finally
{ {
ReleaseComObject(row); ReleaseComObject(row);
} }
} }
EnsureTableSpillsToNextPage(document, table);
} }
finally finally
{ {
@@ -225,6 +245,99 @@ namespace XLAB
} }
} }
private static void EnsureTableSpillsToNextPage(dynamic document, dynamic table)
{
if (document == null || table == null)
{
return;
}
var rowCount = Convert.ToInt32(table.Rows.Count, CultureInfo.InvariantCulture);
if (rowCount <= 1)
{
return;
}
RepaginateDocument(document);
var totalPages = InvokeComIntMethod(document, "ComputeStatistics", WordStatisticPages);
if (totalPages <= 1)
{
return;
}
dynamic lastRow = null;
dynamic firstCell = null;
dynamic lastRowRange = null;
dynamic firstCellRange = null;
dynamic paragraphFormat = null;
try
{
lastRow = table.Rows.Item(rowCount);
lastRowRange = lastRow.Range;
var lastRowPage = GetRangePageNumber(lastRowRange);
if (lastRowPage >= totalPages)
{
return;
}
firstCell = lastRow.Cells.Item(1);
firstCellRange = firstCell.Range;
paragraphFormat = firstCellRange.ParagraphFormat;
paragraphFormat.PageBreakBefore = WordParagraphTrue;
RepaginateDocument(document);
}
finally
{
ReleaseComObject(paragraphFormat);
ReleaseComObject(firstCellRange);
ReleaseComObject(lastRowRange);
ReleaseComObject(firstCell);
ReleaseComObject(lastRow);
}
}
private static void EnsurePsvTableLayout(dynamic table, int expectedColumnCount)
{
if (table == null)
{
throw new InvalidOperationException("В шаблоне ПСВ не найдена таблица для печати.");
}
int actualColumnCount;
try
{
actualColumnCount = Convert.ToInt32(table.Columns.Count, CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
throw new InvalidOperationException("Не удалось определить структуру таблицы шаблона ПСВ.", ex);
}
if (actualColumnCount != expectedColumnCount)
{
throw new InvalidOperationException(
string.Format(
CultureInfo.InvariantCulture,
"Шаблон ПСВ имеет неверную структуру таблицы: ожидается {0} колонок, найдено {1}.",
expectedColumnCount,
actualColumnCount));
}
}
private static int GetRangePageNumber(object range)
{
return InvokeComIndexedIntProperty(range, "Information", WordActiveEndAdjustedPageNumber);
}
private static void RepaginateDocument(object document)
{
InvokeComMethod(document, "Repaginate");
}
private static void SetCellText(dynamic row, int columnIndex, string value, bool centerAlign) private static void SetCellText(dynamic row, int columnIndex, string value, bool centerAlign)
{ {
dynamic cell = null; dynamic cell = null;
@@ -274,7 +387,56 @@ namespace XLAB
} }
} }
private static IReadOnlyList<PrintedGroupRow> BuildPrintedGroups(IEnumerable<PsvDocumentLine> lines, bool includeClosedNotes) private static int InvokeComIndexedIntProperty(object target, string propertyName, object argument)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
return Convert.ToInt32(
target.GetType().InvokeMember(
propertyName,
BindingFlags.GetProperty,
null,
target,
new[] { argument }),
CultureInfo.InvariantCulture);
}
private static int InvokeComIntMethod(object target, string methodName, object argument)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
return Convert.ToInt32(
target.GetType().InvokeMember(
methodName,
BindingFlags.InvokeMethod,
null,
target,
new[] { argument }),
CultureInfo.InvariantCulture);
}
private static void InvokeComMethod(object target, string methodName)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
target.GetType().InvokeMember(
methodName,
BindingFlags.InvokeMethod,
null,
target,
Array.Empty<object>());
}
private static IReadOnlyList<PrintedGroupRow> BuildPrintedGroups(IEnumerable<PsvDocumentLine> lines, bool includeClosedDetails)
{ {
return (lines ?? Enumerable.Empty<PsvDocumentLine>()) return (lines ?? Enumerable.Empty<PsvDocumentLine>())
.GroupBy(delegate(PsvDocumentLine line) .GroupBy(delegate(PsvDocumentLine line)
@@ -302,7 +464,10 @@ namespace XLAB
GroupCount = materializedLines.Count, GroupCount = materializedLines.Count,
PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }), PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }),
FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }), FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }),
Notes = includeClosedNotes ? BuildClosedNotesText(materializedLines) : string.Empty VerificationDocumentText = includeClosedDetails ? BuildVerificationDocumentText(materializedLines) : string.Empty,
StickerNumberText = includeClosedDetails ? BuildStickerNumberText(materializedLines) : string.Empty,
VerifierNameText = includeClosedDetails ? BuildVerifierNameText(materializedLines) : string.Empty,
Notes = BuildNotesText(materializedLines)
}; };
}) })
.ToList(); .ToList();
@@ -332,7 +497,7 @@ namespace XLAB
return string.Empty; return string.Empty;
} }
if (serialNumbers.Count > 3) if (serialNumbers.Count > 10)
{ {
return string.Format(CultureInfo.InvariantCulture, "{0} зав. номеров", serialNumbers.Count); return string.Format(CultureInfo.InvariantCulture, "{0} зав. номеров", serialNumbers.Count);
} }
@@ -371,6 +536,46 @@ namespace XLAB
return string.Join(". ", parts.ToArray()); return string.Join(". ", parts.ToArray());
} }
private static string BuildNotesText(IReadOnlyList<PsvDocumentLine> lines)
{
return string.Join("; ", lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line == null ? null : line.Notes); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
private static string BuildVerificationDocumentText(IReadOnlyList<PsvDocumentLine> lines)
{
return string.Join("; ", lines
.Select(FormatVerificationDocument)
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
private static string BuildStickerNumberText(IReadOnlyList<PsvDocumentLine> lines)
{
return string.Join(", ", lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line == null ? null : line.StickerNumber); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
private static string BuildVerifierNameText(IReadOnlyList<PsvDocumentLine> lines)
{
return string.Join("; ", lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line == null ? null : line.VerifierName); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
private static string FormatVerificationDocument(PsvDocumentLine line) private static string FormatVerificationDocument(PsvDocumentLine line)
{ {
if (line == null) if (line == null)
@@ -445,6 +650,7 @@ namespace XLAB
var candidates = new[] var candidates = new[]
{ {
Path.Combine(baseDirectory, fileName), Path.Combine(baseDirectory, fileName),
Path.GetFullPath(Path.Combine(baseDirectory, "..", fileName)),
Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", fileName)), Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", fileName)),
Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", fileName)) Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", fileName))
}; };
@@ -551,6 +757,12 @@ namespace XLAB
public string RangeText { get; set; } public string RangeText { get; set; }
public string SerialNumberText { get; set; } public string SerialNumberText { get; set; }
public string StickerNumberText { get; set; }
public string VerificationDocumentText { get; set; }
public string VerifierNameText { get; set; }
} }
private sealed class PrintGroupKey : IEquatable<PrintGroupKey> private sealed class PrintGroupKey : IEquatable<PrintGroupKey>

View File

@@ -27,6 +27,11 @@ namespace XLAB
return new SqlConnection(connectionString.ConnectionString); return new SqlConnection(connectionString.ConnectionString);
} }
public static int GetCommandTimeoutSeconds()
{
return 60;
}
public static IReadOnlyList<DirectoryLookupItem> LoadLookupItems(string sql) public static IReadOnlyList<DirectoryLookupItem> LoadLookupItems(string sql)
{ {
var items = new List<DirectoryLookupItem>(); var items = new List<DirectoryLookupItem>();
@@ -35,7 +40,7 @@ namespace XLAB
using (var command = new SqlCommand(sql, connection)) using (var command = new SqlCommand(sql, connection))
{ {
connection.Open(); connection.Open();
command.CommandTimeout = 60; command.CommandTimeout = GetCommandTimeoutSeconds();
using (var reader = command.ExecuteReader()) using (var reader = command.ExecuteReader())
{ {
@@ -54,9 +59,14 @@ namespace XLAB
} }
public static List<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, string parentTableName, int id, IEnumerable<string> excludedChildTables = null) public static List<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, string parentTableName, int id, IEnumerable<string> excludedChildTables = null)
{
return LoadDeleteBlockersFromForeignKeys(connection, null, parentTableName, id, excludedChildTables);
}
public static List<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, SqlTransaction transaction, string parentTableName, int id, IEnumerable<string> excludedChildTables = null)
{ {
var excluded = new HashSet<string>(excludedChildTables ?? Enumerable.Empty<string>(), StringComparer.OrdinalIgnoreCase); var excluded = new HashSet<string>(excludedChildTables ?? Enumerable.Empty<string>(), StringComparer.OrdinalIgnoreCase);
var metadata = LoadForeignKeyMetadata(connection, parentTableName) var metadata = LoadForeignKeyMetadata(connection, transaction, parentTableName)
.Where(delegate(ForeignKeyMetadata item) { return !excluded.Contains(item.TableName); }) .Where(delegate(ForeignKeyMetadata item) { return !excluded.Contains(item.TableName); })
.GroupBy(delegate(ForeignKeyMetadata item) { return item.SchemaName + "." + item.TableName; }) .GroupBy(delegate(ForeignKeyMetadata item) { return item.SchemaName + "." + item.TableName; })
.Select(delegate(IGrouping<string, ForeignKeyMetadata> group) .Select(delegate(IGrouping<string, ForeignKeyMetadata> group)
@@ -86,7 +96,8 @@ namespace XLAB
using (var command = new SqlCommand(sql, connection)) using (var command = new SqlCommand(sql, connection))
{ {
command.CommandTimeout = 60; command.Transaction = transaction;
command.CommandTimeout = GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = id; command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
var rowCount = Convert.ToInt32(command.ExecuteScalar()); var rowCount = Convert.ToInt32(command.ExecuteScalar());
if (rowCount > 0) if (rowCount > 0)
@@ -183,7 +194,7 @@ namespace XLAB
command.Parameters.Add(name, SqlDbType.VarChar, -1).Value = (object)value ?? DBNull.Value; command.Parameters.Add(name, SqlDbType.VarChar, -1).Value = (object)value ?? DBNull.Value;
} }
private static IReadOnlyList<ForeignKeyMetadata> LoadForeignKeyMetadata(SqlConnection connection, string parentTableName) private static IReadOnlyList<ForeignKeyMetadata> LoadForeignKeyMetadata(SqlConnection connection, SqlTransaction transaction, string parentTableName)
{ {
const string sql = @" const string sql = @"
SELECT SELECT
@@ -200,7 +211,8 @@ ORDER BY TableName, ColumnName;";
using (var command = new SqlCommand(sql, connection)) using (var command = new SqlCommand(sql, connection))
{ {
command.CommandTimeout = 60; command.Transaction = transaction;
command.CommandTimeout = GetCommandTimeoutSeconds();
command.Parameters.Add("@ParentTableName", SqlDbType.VarChar, 128).Value = parentTableName; command.Parameters.Add("@ParentTableName", SqlDbType.VarChar, 128).Value = parentTableName;
using (var reader = command.ExecuteReader()) using (var reader = command.ExecuteReader())

View File

@@ -61,14 +61,17 @@
</DataGrid> </DataGrid>
<StackPanel Grid.Row="3" <StackPanel Grid.Row="3"
Margin="0,10,0,0" Margin="0,10,0,0">
Orientation="Horizontal"> <TextBlock Text="Заводские номера" />
<TextBlock Width="150" <TextBlock Margin="0,4,0,0"
Margin="0,0,8,0" Foreground="DimGray"
VerticalAlignment="Center" Text="Перечислите номера через запятую, точку с запятой или с новой строки." />
Text="Заводской номер" /> <TextBox Margin="0,6,0,0"
<TextBox Width="320" Height="96"
Text="{Binding SerialNumber, UpdateSourceTrigger=PropertyChanged}" /> AcceptsReturn="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
Text="{Binding SerialNumbersText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel> </StackPanel>
<TextBlock Grid.Row="4" <TextBlock Grid.Row="4"

View File

@@ -12,7 +12,7 @@ namespace XLAB
{ {
private string _searchText; private string _searchText;
private AvailableInstrumentItem _selectedType; private AvailableInstrumentItem _selectedType;
private string _serialNumber; private string _serialNumbersText;
private string _statusText; private string _statusText;
public SelectInstrumentTypeWindowViewModel(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes) public SelectInstrumentTypeWindowViewModel(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes)
@@ -65,12 +65,12 @@ namespace XLAB
} }
} }
public string SerialNumber public string SerialNumbersText
{ {
get { return _serialNumber; } get { return _serialNumbersText; }
set set
{ {
if (SetProperty(ref _serialNumber, value)) if (SetProperty(ref _serialNumbersText, value))
{ {
((RelayCommand)ConfirmCommand).RaiseCanExecuteChanged(); ((RelayCommand)ConfirmCommand).RaiseCanExecuteChanged();
UpdateStatus(); UpdateStatus();
@@ -86,12 +86,13 @@ namespace XLAB
public InstrumentTypeSelectionResult GetResult() public InstrumentTypeSelectionResult GetResult()
{ {
var serialNumbers = ParseSerialNumbers(SerialNumbersText);
return SelectedType == null return SelectedType == null
? null ? null
: new InstrumentTypeSelectionResult : new InstrumentTypeSelectionResult
{ {
TypeItem = SelectedType, TypeItem = SelectedType,
SerialNumber = string.IsNullOrWhiteSpace(SerialNumber) ? string.Empty : SerialNumber.Trim() SerialNumbers = serialNumbers
}; };
} }
@@ -103,7 +104,7 @@ namespace XLAB
private bool CanConfirm(object parameter) private bool CanConfirm(object parameter)
{ {
return SelectedType != null return SelectedType != null
&& !string.IsNullOrWhiteSpace(SerialNumber); && ParseSerialNumbers(SerialNumbersText).Count > 0;
} }
private void Confirm(object parameter) private void Confirm(object parameter)
@@ -138,6 +139,30 @@ namespace XLAB
&& source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0; && source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
} }
private static List<string> ParseSerialNumbers(string value)
{
var serialNumbers = new List<string>();
var unique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(value))
{
return serialNumbers;
}
var separators = new[] { '\r', '\n', '\t', ',', ';' };
foreach (var token in value.Split(separators, StringSplitOptions.RemoveEmptyEntries))
{
var serialNumber = token.Trim();
if (serialNumber.Length == 0 || !unique.Add(serialNumber))
{
continue;
}
serialNumbers.Add(serialNumber);
}
return serialNumbers;
}
private void RaiseCloseRequested(bool? dialogResult) private void RaiseCloseRequested(bool? dialogResult)
{ {
var handler = CloseRequested; var handler = CloseRequested;
@@ -150,12 +175,13 @@ namespace XLAB
private void UpdateStatus() private void UpdateStatus()
{ {
var visibleCount = InstrumentTypesView.Cast<object>().Count(); var visibleCount = InstrumentTypesView.Cast<object>().Count();
var serialCount = ParseSerialNumbers(SerialNumbersText).Count;
StatusText = string.Format( StatusText = string.Format(
"Всего типов: {0}. По фильтру: {1}. Выбран тип: {2}. Заводской номер: {3}.", "Всего типов: {0}. По фильтру: {1}. Выбран тип: {2}. Уникальных зав. №: {3}.",
InstrumentTypes.Count, InstrumentTypes.Count,
visibleCount, visibleCount,
SelectedType == null ? "нет" : "да", SelectedType == null ? "нет" : "да",
string.IsNullOrWhiteSpace(SerialNumber) ? "не указан" : "указан"); serialCount);
} }
} }
} }

View File

@@ -30,7 +30,8 @@
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" /> Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel> </StackPanel>
<DataGrid Grid.Row="2" <DataGrid x:Name="InstrumentsGrid"
Grid.Row="2"
ItemsSource="{Binding InstrumentsView}" ItemsSource="{Binding InstrumentsView}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"

View File

@@ -1,4 +1,5 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
namespace XLAB namespace XLAB
{ {
@@ -13,6 +14,12 @@ namespace XLAB
private void ViewModelOnCloseRequested(object sender, bool? dialogResult) private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{ {
if (dialogResult.GetValueOrDefault())
{
InstrumentsGrid.CommitEdit(DataGridEditingUnit.Cell, true);
InstrumentsGrid.CommitEdit(DataGridEditingUnit.Row, true);
}
DialogResult = dialogResult; DialogResult = dialogResult;
Close(); Close();
} }

View File

@@ -44,6 +44,22 @@
CanUserAddRows="False" CanUserAddRows="False"
IsReadOnly="True" IsReadOnly="True"
HeadersVisibility="Column"> HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddCommand}" />
<MenuItem Header="Изменить"
Command="{Binding EditCommand}" />
<MenuItem Header="Удалить"
Command="{Binding DeleteCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="ID" <DataGridTextColumn Header="ID"
Width="90" Width="90"
@@ -66,18 +82,6 @@
Margin="0,12,0,0" Margin="0,12,0,0"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Right"> HorizontalAlignment="Right">
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding AddCommand}"
Content="Добавить" />
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding EditCommand}"
Content="Изменить" />
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding DeleteCommand}"
Content="Удалить" />
<Button Width="90" <Button Width="90"
IsCancel="True" IsCancel="True"
Content="Закрыть" /> Content="Закрыть" />

View File

@@ -1,4 +1,6 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB namespace XLAB
{ {
@@ -13,6 +15,16 @@ namespace XLAB
DataContext = _viewModel; DataContext = _viewModel;
} }
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row != null)
{
row.IsSelected = true;
row.Focus();
}
}
private async void Window_Loaded(object sender, RoutedEventArgs e) private async void Window_Loaded(object sender, RoutedEventArgs e)
{ {
await _viewModel.InitializeAsync(); await _viewModel.InitializeAsync();

View File

@@ -44,6 +44,22 @@
CanUserAddRows="False" CanUserAddRows="False"
IsReadOnly="True" IsReadOnly="True"
HeadersVisibility="Column"> HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddCommand}" />
<MenuItem Header="Изменить"
Command="{Binding EditCommand}" />
<MenuItem Header="Удалить"
Command="{Binding DeleteCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="ID" <DataGridTextColumn Header="ID"
Width="90" Width="90"
@@ -66,18 +82,6 @@
Margin="0,12,0,0" Margin="0,12,0,0"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Right"> HorizontalAlignment="Right">
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding AddCommand}"
Content="Добавить" />
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding EditCommand}"
Content="Изменить" />
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding DeleteCommand}"
Content="Удалить" />
<Button Width="90" <Button Width="90"
IsCancel="True" IsCancel="True"
Content="Закрыть" /> Content="Закрыть" />

View File

@@ -1,4 +1,6 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB namespace XLAB
{ {
@@ -13,6 +15,16 @@ namespace XLAB
DataContext = _viewModel; DataContext = _viewModel;
} }
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row != null)
{
row.IsSelected = true;
row.Focus();
}
}
private async void Window_Loaded(object sender, RoutedEventArgs e) private async void Window_Loaded(object sender, RoutedEventArgs e)
{ {
await _viewModel.InitializeAsync(); await _viewModel.InitializeAsync();

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
namespace XLAB
{
internal sealed class VerificationReportFilter
{
public int? CustomerId { get; set; }
public int? MeasurementAreaId { get; set; }
public DateTime? DateFrom { get; set; }
public DateTime? DateTo { get; set; }
}
internal sealed class VerificationReportSummary
{
public int TotalCount { get; set; }
public int GoodCount { get; set; }
public int RejectedCount { get; set; }
public int WithoutResultCount { get; set; }
public int IssuedCount { get; set; }
}
internal sealed class VerificationReportCustomerRow
{
public int? CustomerId { get; set; }
public string CustomerName { get; set; }
public int TotalCount { get; set; }
public int GoodCount { get; set; }
public int RejectedCount { get; set; }
public int WithoutResultCount { get; set; }
public int IssuedCount { get; set; }
}
internal sealed class VerificationReportMeasurementAreaRow
{
public int? MeasurementAreaId { get; set; }
public string MeasurementAreaName { get; set; }
public int TotalCount { get; set; }
public int GoodCount { get; set; }
public int RejectedCount { get; set; }
public int WithoutResultCount { get; set; }
public int IssuedCount { get; set; }
}
internal sealed class VerificationReportData
{
public VerificationReportData()
{
Summary = new VerificationReportSummary();
CustomerRows = Array.Empty<VerificationReportCustomerRow>();
MeasurementAreaRows = Array.Empty<VerificationReportMeasurementAreaRow>();
}
public VerificationReportSummary Summary { get; set; }
public IReadOnlyList<VerificationReportCustomerRow> CustomerRows { get; set; }
public IReadOnlyList<VerificationReportMeasurementAreaRow> MeasurementAreaRows { get; set; }
}
}

View File

@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
namespace XLAB
{
internal sealed class VerificationReportsService
{
private const string ReportSourceSql = @"
FROM dbo.EKZMK m
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
LEFT JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
LEFT JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
LEFT JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
LEFT JOIN dbo.FRPD customers ON customers.IDFRPD = z.IDFRPDV
WHERE m.DTMKFK IS NOT NULL
AND (@DateFrom IS NULL OR m.DTMKFK >= @DateFrom)
AND (@DateToExclusive IS NULL OR m.DTMKFK < @DateToExclusive)
AND (@CustomerId IS NULL OR z.IDFRPDV = @CustomerId)
AND (@MeasurementAreaId IS NULL OR tips.IDSPOI = @MeasurementAreaId)";
public VerificationReportData LoadReport(VerificationReportFilter filter)
{
var normalizedFilter = NormalizeFilter(filter);
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
return new VerificationReportData
{
Summary = LoadSummary(connection, normalizedFilter),
CustomerRows = LoadCustomerRows(connection, normalizedFilter),
MeasurementAreaRows = LoadMeasurementAreaRows(connection, normalizedFilter)
};
}
}
public IReadOnlyList<DirectoryLookupItem> LoadCustomerItems()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT DISTINCT
customers.IDFRPD AS Id,
customers.NMFRPD AS Name
FROM dbo.EKZMK m
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
JOIN dbo.FRPD customers ON customers.IDFRPD = z.IDFRPDV
WHERE m.DTMKFK IS NOT NULL
AND NULLIF(LTRIM(RTRIM(customers.NMFRPD)), N'') IS NOT NULL
ORDER BY customers.NMFRPD, customers.IDFRPD;");
}
public IReadOnlyList<DirectoryLookupItem> LoadMeasurementAreaItems()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT DISTINCT
areas.IDSPOI AS Id,
areas.NMOI AS Name
FROM dbo.EKZMK m
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
LEFT JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
LEFT JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
WHERE m.DTMKFK IS NOT NULL
AND NULLIF(LTRIM(RTRIM(areas.NMOI)), N'') IS NOT NULL
ORDER BY areas.NMOI, areas.IDSPOI;");
}
private static void AddFilterParameters(SqlCommand command, VerificationReportFilter filter)
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@CustomerId", filter.CustomerId);
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@MeasurementAreaId", filter.MeasurementAreaId);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@DateFrom", filter.DateFrom);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@DateToExclusive", GetDateToExclusive(filter.DateTo));
}
private static DateTime? GetDateToExclusive(DateTime? dateTo)
{
return dateTo.HasValue ? dateTo.Value.Date.AddDays(1) : (DateTime?)null;
}
private static VerificationReportSummary LoadSummary(SqlConnection connection, VerificationReportFilter filter)
{
var sql = @"
SELECT
COUNT(*) AS TotalCount,
COALESCE(SUM(CASE WHEN m.GDN = 1 THEN 1 ELSE 0 END), 0) AS GoodCount,
COALESCE(SUM(CASE WHEN m.GDN = 0 THEN 1 ELSE 0 END), 0) AS RejectedCount,
COALESCE(SUM(CASE WHEN m.GDN IS NULL THEN 1 ELSE 0 END), 0) AS WithoutResultCount,
COALESCE(SUM(CASE WHEN m.DTVDM IS NOT NULL THEN 1 ELSE 0 END), 0) AS IssuedCount
" + ReportSourceSql + ";";
using (var command = new SqlCommand(sql, connection))
{
AddFilterParameters(command, filter);
using (var reader = command.ExecuteReader())
{
if (!reader.Read())
{
return new VerificationReportSummary();
}
return new VerificationReportSummary
{
TotalCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "TotalCount"),
GoodCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "GoodCount"),
RejectedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "RejectedCount"),
WithoutResultCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "WithoutResultCount"),
IssuedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "IssuedCount")
};
}
}
}
private static IReadOnlyList<VerificationReportCustomerRow> LoadCustomerRows(SqlConnection connection, VerificationReportFilter filter)
{
var sql = @"
SELECT
z.IDFRPDV AS CustomerId,
COALESCE(NULLIF(LTRIM(RTRIM(customers.NMFRPD)), N''), N'Не указано') AS CustomerName,
COUNT(*) AS TotalCount,
COALESCE(SUM(CASE WHEN m.GDN = 1 THEN 1 ELSE 0 END), 0) AS GoodCount,
COALESCE(SUM(CASE WHEN m.GDN = 0 THEN 1 ELSE 0 END), 0) AS RejectedCount,
COALESCE(SUM(CASE WHEN m.GDN IS NULL THEN 1 ELSE 0 END), 0) AS WithoutResultCount,
COALESCE(SUM(CASE WHEN m.DTVDM IS NOT NULL THEN 1 ELSE 0 END), 0) AS IssuedCount
" + ReportSourceSql + @"
GROUP BY
z.IDFRPDV,
COALESCE(NULLIF(LTRIM(RTRIM(customers.NMFRPD)), N''), N'Не указано')
ORDER BY
CustomerName;";
var rows = new List<VerificationReportCustomerRow>();
using (var command = new SqlCommand(sql, connection))
{
AddFilterParameters(command, filter);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
rows.Add(new VerificationReportCustomerRow
{
CustomerId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "CustomerId"),
CustomerName = ReferenceDirectorySqlHelpers.GetString(reader, "CustomerName"),
TotalCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "TotalCount"),
GoodCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "GoodCount"),
RejectedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "RejectedCount"),
WithoutResultCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "WithoutResultCount"),
IssuedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "IssuedCount")
});
}
}
}
return rows;
}
private static IReadOnlyList<VerificationReportMeasurementAreaRow> LoadMeasurementAreaRows(SqlConnection connection, VerificationReportFilter filter)
{
var sql = @"
SELECT
tips.IDSPOI AS MeasurementAreaId,
COALESCE(NULLIF(LTRIM(RTRIM(areas.NMOI)), N''), N'Не указано') AS MeasurementAreaName,
COUNT(*) AS TotalCount,
COALESCE(SUM(CASE WHEN m.GDN = 1 THEN 1 ELSE 0 END), 0) AS GoodCount,
COALESCE(SUM(CASE WHEN m.GDN = 0 THEN 1 ELSE 0 END), 0) AS RejectedCount,
COALESCE(SUM(CASE WHEN m.GDN IS NULL THEN 1 ELSE 0 END), 0) AS WithoutResultCount,
COALESCE(SUM(CASE WHEN m.DTVDM IS NOT NULL THEN 1 ELSE 0 END), 0) AS IssuedCount
" + ReportSourceSql + @"
GROUP BY
tips.IDSPOI,
COALESCE(NULLIF(LTRIM(RTRIM(areas.NMOI)), N''), N'Не указано')
ORDER BY
MeasurementAreaName;";
var rows = new List<VerificationReportMeasurementAreaRow>();
using (var command = new SqlCommand(sql, connection))
{
AddFilterParameters(command, filter);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
rows.Add(new VerificationReportMeasurementAreaRow
{
MeasurementAreaId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "MeasurementAreaId"),
MeasurementAreaName = ReferenceDirectorySqlHelpers.GetString(reader, "MeasurementAreaName"),
TotalCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "TotalCount"),
GoodCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "GoodCount"),
RejectedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "RejectedCount"),
WithoutResultCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "WithoutResultCount"),
IssuedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "IssuedCount")
});
}
}
}
return rows;
}
private static VerificationReportFilter NormalizeFilter(VerificationReportFilter filter)
{
var normalizedFilter = filter ?? new VerificationReportFilter();
var dateFrom = normalizedFilter.DateFrom.HasValue ? normalizedFilter.DateFrom.Value.Date : (DateTime?)null;
var dateTo = normalizedFilter.DateTo.HasValue ? normalizedFilter.DateTo.Value.Date : (DateTime?)null;
if (dateFrom.HasValue && dateTo.HasValue && dateFrom.Value > dateTo.Value)
{
throw new InvalidOperationException("Дата \"с\" не может быть позже даты \"по\".");
}
return new VerificationReportFilter
{
CustomerId = normalizedFilter.CustomerId,
MeasurementAreaId = normalizedFilter.MeasurementAreaId,
DateFrom = dateFrom,
DateTo = dateTo
};
}
}
}

View File

@@ -0,0 +1,238 @@
<Window x:Class="XLAB.VerificationReportsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Отчеты"
Height="760"
Width="1280"
MinHeight="640"
MinWidth="1100"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox Grid.Row="0"
Header="Параметры отчета">
<Grid Margin="8">
<DockPanel LastChildFill="False">
<Button DockPanel.Dock="Right"
Width="130"
Margin="12,0,0,0"
Command="{Binding RefreshCommand}"
Content="Сформировать" />
<WrapPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Заказчик" />
<ComboBox Width="280"
ItemsSource="{Binding CustomerItems}"
SelectedValue="{Binding SelectedCustomerId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Область измерений" />
<ComboBox Width="280"
ItemsSource="{Binding MeasurementAreaItems}"
SelectedValue="{Binding SelectedMeasurementAreaId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Дата поверки с" />
<DatePicker Width="145"
SelectedDate="{Binding DateFrom, Mode=TwoWay}"
SelectedDateFormat="Short" />
</StackPanel>
<StackPanel Margin="0,0,0,8">
<TextBlock Margin="0,0,0,4"
Text="Дата поверки по" />
<DatePicker Width="145"
SelectedDate="{Binding DateTo, Mode=TwoWay}"
SelectedDateFormat="Short" />
</StackPanel>
</WrapPanel>
</DockPanel>
</Grid>
</GroupBox>
<GroupBox Grid.Row="1"
Margin="0,12,0,0"
Header="Итоги">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Margin="0,0,0,8"
Foreground="DimGray"
Text="{Binding PeriodText}" />
<UniformGrid Grid.Row="1"
Columns="5">
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Поверено" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.TotalCount}" />
</StackPanel>
</Border>
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Годен" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.GoodCount}" />
</StackPanel>
</Border>
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Забракован" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.RejectedCount}" />
</StackPanel>
</Border>
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Без результата" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.WithoutResultCount}" />
</StackPanel>
</Border>
<Border Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="С выдачей" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.IssuedCount}" />
</StackPanel>
</Border>
</UniformGrid>
</Grid>
</GroupBox>
<GroupBox Grid.Row="2"
Margin="0,12,0,0"
Header="Сводные таблицы">
<TabControl Margin="8">
<TabItem Header="По заказчикам">
<DataGrid ItemsSource="{Binding CustomerRows}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="Заказчик"
Width="360"
Binding="{Binding CustomerName}" />
<DataGridTextColumn Header="Поверено"
Width="110"
Binding="{Binding TotalCount}" />
<DataGridTextColumn Header="Годен"
Width="110"
Binding="{Binding GoodCount}" />
<DataGridTextColumn Header="Забракован"
Width="120"
Binding="{Binding RejectedCount}" />
<DataGridTextColumn Header="Без результата"
Width="125"
Binding="{Binding WithoutResultCount}" />
<DataGridTextColumn Header="С выдачей"
Width="110"
Binding="{Binding IssuedCount}" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="По видам измерений">
<DataGrid ItemsSource="{Binding MeasurementAreaRows}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="Область измерений"
Width="360"
Binding="{Binding MeasurementAreaName}" />
<DataGridTextColumn Header="Поверено"
Width="110"
Binding="{Binding TotalCount}" />
<DataGridTextColumn Header="Годен"
Width="110"
Binding="{Binding GoodCount}" />
<DataGridTextColumn Header="Забракован"
Width="120"
Binding="{Binding RejectedCount}" />
<DataGridTextColumn Header="Без результата"
Width="125"
Binding="{Binding WithoutResultCount}" />
<DataGridTextColumn Header="С выдачей"
Width="110"
Binding="{Binding IssuedCount}" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
</GroupBox>
<TextBlock Grid.Row="3"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<StackPanel Grid.Row="4"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
IsCancel="True"
Content="Закрыть" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,21 @@
using System.Windows;
namespace XLAB
{
public partial class VerificationReportsWindow : Window
{
private readonly VerificationReportsWindowViewModel _viewModel;
public VerificationReportsWindow()
{
InitializeComponent();
_viewModel = new VerificationReportsWindowViewModel(new VerificationReportsService(), new DialogService(this));
DataContext = _viewModel;
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await _viewModel.InitializeAsync();
}
}
}

View File

@@ -0,0 +1,249 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace XLAB
{
internal sealed class VerificationReportsWindowViewModel : ObservableObject
{
private readonly IDialogService _dialogService;
private readonly VerificationReportsService _service;
private DateTime? _dateFrom;
private DateTime? _dateTo;
private bool _isBusy;
private int _selectedCustomerId;
private int _selectedMeasurementAreaId;
private VerificationReportSummary _summary;
private string _statusText;
public VerificationReportsWindowViewModel(VerificationReportsService service, IDialogService dialogService)
{
_service = service;
_dialogService = dialogService;
CustomerItems = new ObservableCollection<DirectoryLookupItem>();
MeasurementAreaItems = new ObservableCollection<DirectoryLookupItem>();
CustomerRows = new ObservableCollection<VerificationReportCustomerRow>();
MeasurementAreaRows = new ObservableCollection<VerificationReportMeasurementAreaRow>();
Summary = new VerificationReportSummary();
CustomerItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все заказчики" });
MeasurementAreaItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все области измерений" });
RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });
UpdateStatus();
}
public ObservableCollection<DirectoryLookupItem> CustomerItems { get; private set; }
public ObservableCollection<VerificationReportCustomerRow> CustomerRows { get; private set; }
public DateTime? DateFrom
{
get { return _dateFrom; }
set
{
if (SetProperty(ref _dateFrom, value))
{
OnPropertyChanged("PeriodText");
}
}
}
public DateTime? DateTo
{
get { return _dateTo; }
set
{
if (SetProperty(ref _dateTo, value))
{
OnPropertyChanged("PeriodText");
}
}
}
public bool IsBusy
{
get { return _isBusy; }
private set
{
if (SetProperty(ref _isBusy, value))
{
((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
}
}
}
public ObservableCollection<DirectoryLookupItem> MeasurementAreaItems { get; private set; }
public ObservableCollection<VerificationReportMeasurementAreaRow> MeasurementAreaRows { get; private set; }
public string PeriodText
{
get
{
if (!DateFrom.HasValue && !DateTo.HasValue)
{
return "Период: все время.";
}
if (DateFrom.HasValue && DateTo.HasValue)
{
return string.Format("Период: с {0:d} по {1:d}.", DateFrom.Value, DateTo.Value);
}
if (DateFrom.HasValue)
{
return string.Format("Период: с {0:d}.", DateFrom.Value);
}
return string.Format("Период: по {0:d}.", DateTo.Value);
}
}
public ICommand RefreshCommand { get; private set; }
public int SelectedCustomerId
{
get { return _selectedCustomerId; }
set { SetProperty(ref _selectedCustomerId, value); }
}
public int SelectedMeasurementAreaId
{
get { return _selectedMeasurementAreaId; }
set { SetProperty(ref _selectedMeasurementAreaId, value); }
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public VerificationReportSummary Summary
{
get { return _summary; }
private set { SetProperty(ref _summary, value); }
}
public async Task InitializeAsync()
{
await ExecuteBusyOperationAsync(async delegate
{
await LoadFiltersAsync();
await RefreshCoreAsync();
});
}
private VerificationReportFilter BuildFilter()
{
if (DateFrom.HasValue && DateTo.HasValue && DateFrom.Value.Date > DateTo.Value.Date)
{
throw new InvalidOperationException("Дата \"с\" не может быть позже даты \"по\".");
}
return new VerificationReportFilter
{
CustomerId = SelectedCustomerId > 0 ? SelectedCustomerId : (int?)null,
MeasurementAreaId = SelectedMeasurementAreaId > 0 ? SelectedMeasurementAreaId : (int?)null,
DateFrom = DateFrom,
DateTo = DateTo
};
}
private async Task ExecuteBusyOperationAsync(Func<Task> operation)
{
try
{
IsBusy = true;
await operation();
}
catch (InvalidOperationException ex)
{
_dialogService.ShowWarning(ex.Message);
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
}
finally
{
IsBusy = false;
}
}
private async Task LoadFiltersAsync()
{
var customersTask = Task.Run(delegate { return _service.LoadCustomerItems(); });
var measurementAreasTask = Task.Run(delegate { return _service.LoadMeasurementAreaItems(); });
await Task.WhenAll(customersTask, measurementAreasTask);
ApplyLookupItems(CustomerItems, customersTask.Result, "Все заказчики");
ApplyLookupItems(MeasurementAreaItems, measurementAreasTask.Result, "Все области измерений");
if (!CustomerItems.Any(delegate(DirectoryLookupItem item) { return item.Id == SelectedCustomerId; }))
{
SelectedCustomerId = 0;
}
if (!MeasurementAreaItems.Any(delegate(DirectoryLookupItem item) { return item.Id == SelectedMeasurementAreaId; }))
{
SelectedMeasurementAreaId = 0;
}
}
private static void ApplyLookupItems(ObservableCollection<DirectoryLookupItem> target, System.Collections.Generic.IReadOnlyList<DirectoryLookupItem> source, string allItemText)
{
target.Clear();
target.Add(new DirectoryLookupItem { Id = 0, Name = allItemText });
foreach (var item in source)
{
target.Add(item);
}
}
private async Task RefreshCoreAsync()
{
var filter = BuildFilter();
var report = await Task.Run(delegate { return _service.LoadReport(filter); });
Summary = report.Summary ?? new VerificationReportSummary();
CustomerRows.Clear();
foreach (var row in report.CustomerRows ?? Array.Empty<VerificationReportCustomerRow>())
{
CustomerRows.Add(row);
}
MeasurementAreaRows.Clear();
foreach (var row in report.MeasurementAreaRows ?? Array.Empty<VerificationReportMeasurementAreaRow>())
{
MeasurementAreaRows.Add(row);
}
UpdateStatus();
}
private async void RefreshAsync()
{
await ExecuteBusyOperationAsync(RefreshCoreAsync);
}
private void UpdateStatus()
{
StatusText = string.Format(
"Заказчиков в своде: {0}. Видов измерений в своде: {1}. Поверено: {2}. Годен: {3}. Забракован: {4}.",
CustomerRows.Count,
MeasurementAreaRows.Count,
Summary == null ? 0 : Summary.TotalCount,
Summary == null ? 0 : Summary.GoodCount,
Summary == null ? 0 : Summary.RejectedCount);
}
}
}

View File

@@ -78,9 +78,30 @@
</Compile> </Compile>
<Compile Include="CloneVerificationWindowViewModel.cs" /> <Compile Include="CloneVerificationWindowViewModel.cs" />
<Compile Include="DialogService.cs" /> <Compile Include="DialogService.cs" />
<Compile Include="EkzDirectoryDialogService.cs" />
<Compile Include="EkzDirectoryModels.cs" />
<Compile Include="EkzDirectoryService.cs" />
<Compile Include="FrpdDirectoryDialogService.cs" /> <Compile Include="FrpdDirectoryDialogService.cs" />
<Compile Include="FrpdDirectoryModels.cs" /> <Compile Include="FrpdDirectoryModels.cs" />
<Compile Include="FrpdDirectoryService.cs" /> <Compile Include="FrpdDirectoryService.cs" />
<Page Include="EkzDirectoryWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="EkzDirectoryWindow.xaml.cs">
<DependentUpon>EkzDirectoryWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="EkzDirectoryWindowViewModel.cs" />
<Page Include="EkzEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="EkzEditWindow.xaml.cs">
<DependentUpon>EkzEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="EkzEditWindowViewModel.cs" />
<Page Include="MainWindow.xaml"> <Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
@@ -91,6 +112,9 @@
</Compile> </Compile>
<Compile Include="MainWindowViewModel.cs" /> <Compile Include="MainWindowViewModel.cs" />
<Compile Include="MvvmInfrastructure.cs" /> <Compile Include="MvvmInfrastructure.cs" />
<Compile Include="PlanningDialogService.cs" />
<Compile Include="PlanningModels.cs" />
<Compile Include="PlanningService.cs" />
<Compile Include="PsvDataService.cs" /> <Compile Include="PsvDataService.cs" />
<Compile Include="PsvPrintService.cs" /> <Compile Include="PsvPrintService.cs" />
<Compile Include="PsvModels.cs" /> <Compile Include="PsvModels.cs" />
@@ -263,6 +287,24 @@
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="PrdspvEditWindowViewModel.cs" /> <Compile Include="PrdspvEditWindowViewModel.cs" />
<Page Include="PlanningEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="PlanningEditWindow.xaml.cs">
<DependentUpon>PlanningEditWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="PlanningEditWindowViewModel.cs" />
<Page Include="PlanningWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="PlanningWindow.xaml.cs">
<DependentUpon>PlanningWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="PlanningWindowViewModel.cs" />
<Page Include="TprmcpEditWindow.xaml"> <Page Include="TprmcpEditWindow.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
@@ -299,6 +341,17 @@
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="VerificationEditWindowViewModel.cs" /> <Compile Include="VerificationEditWindowViewModel.cs" />
<Compile Include="VerificationReportsModels.cs" />
<Compile Include="VerificationReportsService.cs" />
<Page Include="VerificationReportsWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="VerificationReportsWindow.xaml.cs">
<DependentUpon>VerificationReportsWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="VerificationReportsWindowViewModel.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs"> <Compile Include="Properties\AssemblyInfo.cs">

View File

@@ -0,0 +1,118 @@
<Window x:Class="XLAB2.AccountingBookDirectoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Книги учета"
Height="680"
Width="980"
MinHeight="520"
MinWidth="820"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0"
Margin="0,0,0,8">
<StackPanel DockPanel.Dock="Left"
Orientation="Horizontal">
<TextBlock Width="210"
VerticalAlignment="Center"
Text="Поиск по ключу или книге учета" />
<TextBox Width="320"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel DockPanel.Dock="Right"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="110"
Margin="8,0,0,0"
Command="{Binding RefreshCommand}"
Content="Обновить" />
</StackPanel>
</DockPanel>
<DataGrid Grid.Row="1"
ItemsSource="{Binding ItemsView}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить"
Command="{Binding EditCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить"
Command="{Binding DeleteCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Ключ"
Width="240"
Binding="{Binding Key}" />
<DataGridTextColumn Header="Номер книги учета"
Width="*"
Binding="{Binding Title}" />
</DataGrid.Columns>
</DataGrid>
<TextBlock Grid.Row="2"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<StackPanel Grid.Row="3"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
IsCancel="True"
Content="Закрыть" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,35 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB2
{
public partial class AccountingBookDirectoryWindow : Window
{
private readonly AccountingBookDirectoryWindowViewModel _viewModel;
internal AccountingBookDirectoryWindow(DocumentNumberDirectoryService documentNumberDirectoryService)
{
InitializeComponent();
_viewModel = new AccountingBookDirectoryWindowViewModel(
documentNumberDirectoryService,
new DialogService(this, documentNumberDirectoryService));
DataContext = _viewModel;
}
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row != null)
{
row.IsSelected = true;
row.Focus();
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_viewModel.Initialize();
}
}
}

View File

@@ -0,0 +1,266 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class AccountingBookDirectoryWindowViewModel : ObservableObject
{
private readonly IDialogService _dialogService;
private readonly DocumentNumberDirectoryService _service;
private string _searchText;
private GroupOption _selectedItem;
private string _statusText;
public AccountingBookDirectoryWindowViewModel(DocumentNumberDirectoryService service, IDialogService dialogService)
{
_service = service ?? throw new ArgumentNullException("service");
_dialogService = dialogService ?? throw new ArgumentNullException("dialogService");
Items = new ObservableCollection<GroupOption>();
ItemsView = CollectionViewSource.GetDefaultView(Items);
ItemsView.Filter = FilterItems;
AddCommand = new RelayCommand(delegate { AddItem(); });
DeleteCommand = new RelayCommand(delegate { DeleteSelectedItem(); }, delegate { return SelectedItem != null; });
EditCommand = new RelayCommand(delegate { EditSelectedItem(); }, delegate { return SelectedItem != null; });
RefreshCommand = new RelayCommand(delegate { RefreshItems(null); });
UpdateStatus();
}
public ICommand AddCommand { get; private set; }
public ICommand DeleteCommand { get; private set; }
public ICommand EditCommand { get; private set; }
public ObservableCollection<GroupOption> Items { get; private set; }
public ICollectionView ItemsView { get; private set; }
public ICommand RefreshCommand { get; private set; }
public string SearchText
{
get { return _searchText; }
set
{
if (SetProperty(ref _searchText, value))
{
ItemsView.Refresh();
UpdateStatus();
}
}
}
public GroupOption SelectedItem
{
get { return _selectedItem; }
set
{
if (SetProperty(ref _selectedItem, value))
{
RaiseCommandStates();
}
}
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public void Initialize()
{
RunOperation(delegate { RefreshItems(null); }, false);
}
private void AddItem()
{
var result = _dialogService.ShowAccountingBookEditDialog(new GroupOption(), true, Items.ToList());
if (result == null)
{
return;
}
RunOperation(delegate
{
var items = CloneItems(Items);
items.Add(CloneItem(result));
_service.SaveAccountingBooks(items);
RefreshItems(result.Key);
_dialogService.ShowInfo("Запись справочника добавлена.");
}, true);
}
private bool Contains(string source, string searchText)
{
return !string.IsNullOrWhiteSpace(source)
&& source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
}
private void DeleteSelectedItem()
{
if (SelectedItem == null)
{
return;
}
var selectedItem = SelectedItem;
if (!_dialogService.Confirm(string.Format("Удалить книгу учета \"{0}\"?", selectedItem.Title)))
{
return;
}
RunOperation(delegate
{
var items = CloneItems(Items);
items.RemoveAll(delegate(GroupOption item)
{
return string.Equals(item.Key, selectedItem.Key, StringComparison.OrdinalIgnoreCase);
});
_service.SaveAccountingBooks(items);
RefreshItems(null);
_dialogService.ShowInfo("Запись справочника удалена.");
}, true);
}
private void EditSelectedItem()
{
if (SelectedItem == null)
{
return;
}
var seed = CloneItem(SelectedItem);
var result = _dialogService.ShowAccountingBookEditDialog(seed, false, Items.ToList());
if (result == null)
{
return;
}
RunOperation(delegate
{
var items = CloneItems(Items);
var index = items.FindIndex(delegate(GroupOption item)
{
return string.Equals(item.Key, seed.Key, StringComparison.OrdinalIgnoreCase);
});
if (index < 0)
{
throw new InvalidOperationException("Не удалось найти книгу учета для обновления.");
}
items[index] = CloneItem(result);
_service.SaveAccountingBooks(items);
RefreshItems(result.Key);
_dialogService.ShowInfo("Запись справочника обновлена.");
}, true);
}
private bool FilterItems(object item)
{
var directoryItem = item as GroupOption;
if (directoryItem == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(SearchText))
{
return true;
}
return Contains(directoryItem.Key, SearchText)
|| Contains(directoryItem.Title, SearchText);
}
private void RaiseCommandStates()
{
((RelayCommand)AddCommand).RaiseCanExecuteChanged();
((RelayCommand)DeleteCommand).RaiseCanExecuteChanged();
((RelayCommand)EditCommand).RaiseCanExecuteChanged();
((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
}
private void RefreshItems(string keyToSelect)
{
var items = _service.LoadAccountingBooks();
var currentKey = string.IsNullOrWhiteSpace(keyToSelect)
? (SelectedItem == null ? null : SelectedItem.Key)
: keyToSelect;
Items.Clear();
foreach (var item in items)
{
Items.Add(CloneItem(item));
}
ItemsView.Refresh();
SelectedItem = string.IsNullOrWhiteSpace(currentKey)
? Items.FirstOrDefault()
: Items.FirstOrDefault(delegate(GroupOption item)
{
return string.Equals(item.Key, currentKey, StringComparison.OrdinalIgnoreCase);
}) ?? Items.FirstOrDefault();
UpdateStatus();
}
private void RunOperation(Action action, bool showWarningForInvalidOperation)
{
try
{
action();
}
catch (InvalidOperationException ex)
{
if (showWarningForInvalidOperation)
{
_dialogService.ShowWarning(ex.Message);
return;
}
_dialogService.ShowError(ex.Message);
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
}
}
private static GroupOption CloneItem(GroupOption item)
{
return new GroupOption
{
Key = item == null ? string.Empty : item.Key,
Title = item == null ? string.Empty : item.Title
};
}
private static List<GroupOption> CloneItems(IEnumerable<GroupOption> items)
{
var result = new List<GroupOption>();
foreach (var item in items ?? Array.Empty<GroupOption>())
{
result.Add(CloneItem(item));
}
return result;
}
private void UpdateStatus()
{
var visibleCount = ItemsView.Cast<object>().Count();
StatusText = string.Format("Всего записей: {0}. По фильтру: {1}.", Items.Count, visibleCount);
}
}
}

View File

@@ -0,0 +1,64 @@
<Window x:Class="XLAB2.AccountingBookEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="250"
Width="560"
MinHeight="240"
MinWidth="520"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Номер книги учета" />
<TextBox Grid.Row="0"
Grid.Column="1"
Margin="0,0,0,8"
Text="{Binding BookNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Ключ" />
<TextBox Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,8"
Text="{Binding Key, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="2"
Grid.ColumnSpan="2"
Margin="0,8,0,0"
Foreground="Firebrick"
Text="{Binding ValidationMessage}" />
<StackPanel Grid.Row="3"
Grid.ColumnSpan="2"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="100"
Margin="0,0,8,0"
IsDefault="True"
Command="{Binding ConfirmCommand}"
Content="Сохранить" />
<Button Width="90"
Command="{Binding CancelCommand}"
Content="Отмена" />
</StackPanel>
</Grid>
</Window>

View File

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

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class AccountingBookEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<GroupOption> _existingItems;
private readonly string _originalKey;
private string _bookNumber;
private string _key;
private string _validationMessage;
public AccountingBookEditWindowViewModel(GroupOption seed, bool isNew, IReadOnlyList<GroupOption> existingItems)
{
var source = seed ?? new GroupOption();
_existingItems = existingItems ?? Array.Empty<GroupOption>();
_originalKey = Normalize(source.Key);
IsNew = isNew;
BookNumber = source.Title ?? string.Empty;
Key = source.Key ?? string.Empty;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public string BookNumber
{
get { return _bookNumber; }
set { SetProperty(ref _bookNumber, value); }
}
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public bool IsNew { get; private set; }
public string Key
{
get { return _key; }
set { SetProperty(ref _key, value); }
}
public string Title
{
get { return IsNew ? "Новая книга учета" : "Редактирование книги учета"; }
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public GroupOption ToResult()
{
return new GroupOption
{
Key = Normalize(Key) ?? string.Empty,
Title = Normalize(BookNumber) ?? string.Empty
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
var normalizedKey = Normalize(Key);
if (normalizedKey == null)
{
ValidationMessage = "Укажите ключ книги учета.";
return;
}
var normalizedBookNumber = Normalize(BookNumber);
if (normalizedBookNumber == null)
{
ValidationMessage = "Укажите номер книги учета.";
return;
}
var duplicateKey = _existingItems.FirstOrDefault(delegate(GroupOption item)
{
return item != null
&& !IsCurrentItem(item)
&& string.Equals(Normalize(item.Key), normalizedKey, StringComparison.OrdinalIgnoreCase);
});
if (duplicateKey != null)
{
ValidationMessage = string.Format("Ключ книги учета \"{0}\" уже существует.", normalizedKey);
return;
}
var duplicateBookNumber = _existingItems.FirstOrDefault(delegate(GroupOption item)
{
return item != null
&& !IsCurrentItem(item)
&& string.Equals(Normalize(item.Title), normalizedBookNumber, StringComparison.OrdinalIgnoreCase);
});
if (duplicateBookNumber != null)
{
ValidationMessage = string.Format("Книга учета \"{0}\" уже существует.", normalizedBookNumber);
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private bool IsCurrentItem(GroupOption item)
{
return !IsNew
&& string.Equals(Normalize(item == null ? null : item.Key), _originalKey, StringComparison.OrdinalIgnoreCase);
}
private static string Normalize(string value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
}
}

View File

@@ -16,6 +16,15 @@
<SolidColorBrush x:Key="AppMutedTextBrush" Color="#FF6B7B88" /> <SolidColorBrush x:Key="AppMutedTextBrush" Color="#FF6B7B88" />
<SolidColorBrush x:Key="AppButtonBrush" Color="#FFF7FAFD" /> <SolidColorBrush x:Key="AppButtonBrush" Color="#FFF7FAFD" />
<SolidColorBrush x:Key="AppButtonHoverBrush" Color="#FFE7F0F9" /> <SolidColorBrush x:Key="AppButtonHoverBrush" Color="#FFE7F0F9" />
<SolidColorBrush x:Key="AppPrimaryButtonBrush" Color="#FF5B8DB8" />
<SolidColorBrush x:Key="AppPrimaryButtonHoverBrush" Color="#FF6A9BC4" />
<SolidColorBrush x:Key="AppPrimaryButtonPressedBrush" Color="#FF4C799E" />
<SolidColorBrush x:Key="AppPrimaryButtonDisabledBrush" Color="#FF9EB3C4" />
<SolidColorBrush x:Key="AppPrimaryButtonBorderBrush" Color="#FF426E91" />
<SolidColorBrush x:Key="AppMenuIconAccentBrush" Color="#FF4F7FA7" />
<SolidColorBrush x:Key="AppMenuIconSuccessBrush" Color="#FF47A772" />
<SolidColorBrush x:Key="AppMenuIconDangerBrush" Color="#FFC76868" />
<SolidColorBrush x:Key="AppMenuIconWarningBrush" Color="#FFCC9B52" />
<SolidColorBrush x:Key="AppSelectionBrush" Color="#FFD8E6F4" /> <SolidColorBrush x:Key="AppSelectionBrush" Color="#FFD8E6F4" />
<SolidColorBrush x:Key="AppSelectionTextBrush" Color="#FF17324A" /> <SolidColorBrush x:Key="AppSelectionTextBrush" Color="#FF17324A" />
<SolidColorBrush x:Key="OpenDocumentTenDaysBrush" Color="#FFF2F8EA" /> <SolidColorBrush x:Key="OpenDocumentTenDaysBrush" Color="#FFF2F8EA" />
@@ -28,6 +37,7 @@
<Style TargetType="{x:Type Window}"> <Style TargetType="{x:Type Window}">
<Setter Property="Background" Value="{StaticResource AppWindowBackgroundBrush}" /> <Setter Property="Background" Value="{StaticResource AppWindowBackgroundBrush}" />
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" /> <Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
<Setter Property="Icon" Value="pack://application:,,,/Assets/XlabApp.ico" />
</Style> </Style>
<Style TargetType="{x:Type TextBlock}"> <Style TargetType="{x:Type TextBlock}">
@@ -76,6 +86,53 @@
<Setter Property="Padding" Value="10,4" /> <Setter Property="Padding" Value="10,4" />
</Style> </Style>
<Style x:Key="PrimaryActionButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{StaticResource AppPrimaryButtonBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppPrimaryButtonBorderBrush}" />
<Setter Property="Foreground" Value="#FFFAFCFE" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Padding" Value="12,5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="RootBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
CornerRadius="4"
SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="RootBorder" Property="Background" Value="{StaticResource AppPrimaryButtonHoverBrush}" />
<Setter TargetName="RootBorder" Property="BorderBrush" Value="#FF4F7FA7" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="RootBorder" Property="Background" Value="{StaticResource AppPrimaryButtonPressedBrush}" />
<Setter TargetName="RootBorder" Property="BorderBrush" Value="#FF3E6687" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="RootBorder" Property="Background" Value="{StaticResource AppPrimaryButtonDisabledBrush}" />
<Setter TargetName="RootBorder" Property="BorderBrush" Value="#FF8CA0B1" />
<Setter Property="Foreground" Value="#FFF5F8FB" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{StaticResource AppPrimaryButtonDisabledBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppPrimaryButtonDisabledBrush}" />
<Setter Property="Foreground" Value="#FFF4F7FA" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type TextBox}"> <Style TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" /> <Setter Property="Background" Value="{StaticResource AppSurfaceBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" /> <Setter Property="BorderBrush" Value="{StaticResource AppBorderBrush}" />
@@ -117,6 +174,166 @@
<Style TargetType="{x:Type MenuItem}"> <Style TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="{StaticResource AppTextBrush}" /> <Setter Property="Foreground" Value="{StaticResource AppTextBrush}" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="Header" Value="Добавить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Изменить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Удалить">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Распечатать">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="1.5" Width="8" Height="4" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="5" Width="12" Height="5" RadiusX="1.5" RadiusY="1.5" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="4" Canvas.Top="8.5" Width="8" Height="5" Fill="#FFF9FCFE" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Добавить по заводским номерам">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="2" Canvas.Top="3" Width="8" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="7" Width="8" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="11" Width="6" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Ellipse Canvas.Left="10.5" Canvas.Top="6" Width="4" Height="4" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="12" Canvas.Top="4.5" Width="1" Height="7" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="9.5" Canvas.Top="7" Width="6" Height="1" Fill="{StaticResource AppMenuIconSuccessBrush}" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Добавить по типу">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="2" Canvas.Top="3" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="9" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="8" Canvas.Top="6" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="12" Canvas.Top="1.5" Width="1.5" Height="5" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="10.25" Canvas.Top="3.25" Width="5" Height="1.5" Fill="{StaticResource AppMenuIconSuccessBrush}" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Клонировать поверку в выбранные строки">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="3" Canvas.Top="4" Width="7" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="2" Width="7" Height="8" RadiusX="1" RadiusY="1" Fill="#FFEAF3FB" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Распечатать документ о поверке">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="1.5" Width="8" Height="4" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="5" Width="12" Height="5" RadiusX="1.5" RadiusY="1.5" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="4" Canvas.Top="8.5" Width="8" Height="5" Fill="#FFF9FCFE" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
<Ellipse Canvas.Left="10.5" Canvas.Top="9.5" Width="4" Height="4" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Path Fill="White" Data="M12.1,10.4 L12.9,11.2 L14.2,9.6 L14.8,10.1 L12.9,12.4 L11.5,11 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Годен">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Path Fill="White" Data="M5.1,8.2 L7.2,10.3 L11.6,5.6 L12.8,6.6 L7.3,12.3 L3.9,8.9 Z" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Забракован">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Path Stroke="White" StrokeThickness="1.8" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Data="M5.1,5.1 L10.9,10.9 M10.9,5.1 L5.1,10.9" />
</Grid>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Отменить проверку">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconWarningBrush}" Data="M7.8,2.2 C10.8,2.2 13.2,4.6 13.2,7.6 C13.2,10.6 10.8,13 7.8,13 C5.5,13 3.6,11.6 2.8,9.5 L4.5,9.5 C5.2,10.8 6.4,11.5 7.8,11.5 C10,11.5 11.7,9.8 11.7,7.6 C11.7,5.4 10,3.7 7.8,3.7 C6.5,3.7 5.3,4.3 4.6,5.4 L6.7,5.4 L3.8,8.2 L1.1,5.4 L3.1,5.4 C4,3.4 5.8,2.2 7.8,2.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Header" Value="Виды клейм...">
<Setter Property="Icon">
<Setter.Value>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Ellipse Canvas.Left="2.5" Canvas.Top="2.5" Width="11" Height="11" Fill="{StaticResource AppMenuIconWarningBrush}" />
<Path Fill="White" Data="M8,4 L8.9,6.2 L11.3,6.3 L9.4,7.8 L10.1,10.1 L8,8.8 L5.9,10.1 L6.6,7.8 L4.7,6.3 L7.1,6.2 Z" />
</Canvas>
</Viewbox>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style> </Style>
<Style TargetType="{x:Type ListBox}"> <Style TargetType="{x:Type ListBox}">

View File

@@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Markup; using System.Windows.Markup;
using System.Windows.Threading;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using XLAB2.Infrastructure; using XLAB2.Infrastructure;
@@ -17,6 +18,7 @@ namespace XLAB2
public App() public App()
{ {
ApplyRussianCulture(); ApplyRussianCulture();
RegisterGlobalExceptionHandlers();
} }
protected override async void OnStartup(StartupEventArgs e) protected override async void OnStartup(StartupEventArgs e)
@@ -40,19 +42,29 @@ namespace XLAB2
protected override async void OnExit(ExitEventArgs e) protected override async void OnExit(ExitEventArgs e)
{ {
if (_host != null) try
{ {
try if (_host != null)
{ {
await _host.StopAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(true); try
} {
finally await _host.StopAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(true);
{ }
_host.Dispose(); finally
{
_host.Dispose();
}
} }
} }
catch (Exception ex)
base.OnExit(e); {
ShowUnhandledException(ex, true);
}
finally
{
UnregisterGlobalExceptionHandlers();
base.OnExit(e);
}
} }
private static void ApplyRussianCulture() private static void ApplyRussianCulture()
@@ -68,5 +80,77 @@ namespace XLAB2
typeof(FrameworkElement), typeof(FrameworkElement),
new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(culture.IetfLanguageTag))); new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(culture.IetfLanguageTag)));
} }
private void RegisterGlobalExceptionHandlers()
{
DispatcherUnhandledException += OnDispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += OnCurrentDomainUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
}
private void UnregisterGlobalExceptionHandlers()
{
DispatcherUnhandledException -= OnDispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException -= OnCurrentDomainUnhandledException;
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
}
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
ShowUnhandledException(e.Exception, false);
e.Handled = true;
}
private void OnCurrentDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = e.ExceptionObject as Exception;
if (exception != null)
{
ShowUnhandledException(exception, e.IsTerminating);
return;
}
MessageBox.Show(
e.ExceptionObject == null ? "Произошла необработанная ошибка." : e.ExceptionObject.ToString(),
e.IsTerminating ? "XLAB2 - критическая ошибка" : "XLAB2",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
ShowUnhandledException(e.Exception, false);
e.SetObserved();
}
private static void ShowUnhandledException(Exception exception, bool isCritical)
{
var actualException = UnwrapException(exception);
var message = string.IsNullOrWhiteSpace(actualException.Message)
? actualException.ToString()
: actualException.Message;
MessageBox.Show(
message,
isCritical ? "XLAB2 - критическая ошибка" : "XLAB2",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
private static Exception UnwrapException(Exception exception)
{
if (exception is AggregateException aggregateException)
{
var flattened = aggregateException.Flatten();
if (flattened.InnerExceptions.Count == 1)
{
return UnwrapException(flattened.InnerExceptions[0]);
}
return flattened;
}
return exception;
}
} }
} }

View File

@@ -22,8 +22,11 @@ namespace XLAB2
.ConfigureServices((_, services) => .ConfigureServices((_, services) =>
{ {
services.AddSingleton<IDatabaseConnectionFactory>(_ => SqlServerConnectionFactory.Current); services.AddSingleton<IDatabaseConnectionFactory>(_ => SqlServerConnectionFactory.Current);
services.AddSingleton(new DocumentNumberDirectoryService(System.IO.Path.Combine(AppContext.BaseDirectory, "Assets", "document-number-directory.json")));
services.AddTransient<PsvDataService>(); services.AddTransient<PsvDataService>();
services.AddTransient<MainWindow>(provider => new MainWindow(provider.GetRequiredService<PsvDataService>())); services.AddTransient<MainWindow>(provider => new MainWindow(
provider.GetRequiredService<PsvDataService>(),
provider.GetRequiredService<DocumentNumberDirectoryService>()));
}) })
.UseDefaultServiceProvider((_, options) => .UseDefaultServiceProvider((_, options) =>
{ {

BIN
XLAB2/Assets/XlabApp.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

View File

@@ -0,0 +1,18 @@
{
"documentTypes": [
{
"key": "PSV",
"title": "ПСВ"
},
{
"key": "ACT_REFERENCE",
"title": "Акт-справка"
}
],
"accountingBooks": [
{
"key": "219/С5/15",
"title": "219/С5/15"
}
]
}

BIN
XLAB2/ClosePsv.docx Normal file

Binary file not shown.

View File

@@ -1,9 +1,9 @@
<Window x:Class="XLAB2.CreateDocumentWindow" <Window x:Class="XLAB2.CreateDocumentWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Новый ПСВ" Title="Новый документ"
Height="240" Height="360"
Width="430" Width="520"
ResizeMode="NoResize" ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"> WindowStartupLocation="CenterOwner">
<Grid Margin="16"> <Grid Margin="16">
@@ -13,9 +13,12 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="150" /> <ColumnDefinition Width="170" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -23,36 +26,72 @@
Grid.Column="0" Grid.Column="0"
Margin="0,0,12,8" Margin="0,0,12,8"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Номер ПСВ" /> Text="Тип документа" />
<TextBox Grid.Row="0" <ComboBox Grid.Row="0"
Grid.Column="1" Grid.Column="1"
MinWidth="180" Margin="0,0,0,8"
Margin="0,0,0,8" DisplayMemberPath="Title"
Text="{Binding DocumentNumber, UpdateSourceTrigger=PropertyChanged}" /> ItemsSource="{Binding DocumentTypes}"
SelectedItem="{Binding SelectedDocumentType, Mode=TwoWay}" />
<TextBlock Grid.Row="1" <TextBlock Grid.Row="1"
Grid.Column="0" Grid.Column="0"
Margin="0,0,12,8" Margin="0,0,12,8"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Дата приемки" /> Text="Книга учета" />
<DatePicker Grid.Row="1" <ComboBox Grid.Row="1"
Grid.Column="1" Grid.Column="1"
SelectedDate="{Binding AcceptedOn, Mode=TwoWay}" Margin="0,0,0,8"
SelectedDateFormat="Short" DisplayMemberPath="Title"
Margin="0,0,0,8" /> ItemsSource="{Binding AccountingBooks}"
SelectedItem="{Binding SelectedAccountingBook, Mode=TwoWay}" />
<TextBlock Grid.Row="2" <TextBlock Grid.Row="2"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Номер документа" />
<TextBox Grid.Row="2"
Grid.Column="1"
MinWidth="180"
Margin="0,0,0,8"
Text="{Binding DocumentSequenceNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="3"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Итоговый номер" />
<TextBox Grid.Row="3"
Grid.Column="1"
Margin="0,0,0,8"
IsReadOnly="True"
Text="{Binding DocumentNumber, Mode=OneWay}" />
<TextBlock Grid.Row="4"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Дата приемки" />
<DatePicker Grid.Row="4"
Grid.Column="1"
Margin="0,0,0,8"
SelectedDate="{Binding AcceptedOn, Mode=TwoWay}"
SelectedDateFormat="Short" />
<TextBlock Grid.Row="5"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Margin="0,8,0,4" Margin="0,8,0,4"
TextWrapping="Wrap"
Foreground="DimGray" Foreground="DimGray"
Text="ПСВ без строк EKZMK не хранится в базе. Команда «Добавить» создаёт только черновик текущего сеанса." /> TextWrapping="Wrap"
<TextBlock Grid.Row="3" Text="Документ без строк EKZMK не хранится в базе. Команда «Создать» создаёт только черновик текущего сеанса." />
<TextBlock Grid.Row="6"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Foreground="Firebrick" Foreground="Firebrick"
Text="{Binding ValidationMessage}" /> Text="{Binding ValidationMessage}" />
<StackPanel Grid.Row="4" <StackPanel Grid.Row="7"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Right"> HorizontalAlignment="Right">

View File

@@ -1,4 +1,6 @@
using System; using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input; using System.Windows.Input;
namespace XLAB2 namespace XLAB2
@@ -6,13 +8,25 @@ namespace XLAB2
internal sealed class CreateDocumentWindowViewModel : ObservableObject internal sealed class CreateDocumentWindowViewModel : ObservableObject
{ {
private DateTime? _acceptedOn; private DateTime? _acceptedOn;
private string _documentNumber; private string _documentSequenceNumber;
private GroupOption _selectedAccountingBook;
private GroupOption _selectedDocumentType;
private string _validationMessage; private string _validationMessage;
public CreateDocumentWindowViewModel(DocumentEditorResult seed) public CreateDocumentWindowViewModel(DocumentEditorResult seed, DocumentNumberDirectory directory)
{ {
if (directory == null)
{
throw new ArgumentNullException("directory");
}
AccountingBooks = new ObservableCollection<GroupOption>(directory.AccountingBooks ?? Array.Empty<GroupOption>());
DocumentTypes = new ObservableCollection<GroupOption>(directory.DocumentTypes ?? Array.Empty<GroupOption>());
_acceptedOn = seed != null ? seed.AcceptedOn : DateTime.Today; _acceptedOn = seed != null ? seed.AcceptedOn : DateTime.Today;
_documentNumber = seed != null ? seed.DocumentNumber : string.Empty; _documentSequenceNumber = seed == null ? string.Empty : seed.DocumentSequenceNumber ?? string.Empty;
_selectedAccountingBook = ResolveSelectedOption(AccountingBooks, seed == null ? null : seed.AccountingBookKey);
_selectedDocumentType = ResolveSelectedOption(DocumentTypes, seed == null ? null : seed.DocumentTypeKey);
ConfirmCommand = new RelayCommand(Confirm); ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel); CancelCommand = new RelayCommand(Cancel);
@@ -23,17 +37,71 @@ namespace XLAB2
public DateTime? AcceptedOn public DateTime? AcceptedOn
{ {
get { return _acceptedOn; } get { return _acceptedOn; }
set { SetProperty(ref _acceptedOn, value); } set
{
if (SetProperty(ref _acceptedOn, value))
{
ClearValidationMessage();
}
}
} }
public ObservableCollection<GroupOption> AccountingBooks { get; private set; }
public ICommand CancelCommand { get; private set; } public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; } public ICommand ConfirmCommand { get; private set; }
public string DocumentNumber public string DocumentNumber
{ {
get { return _documentNumber; } get
set { SetProperty(ref _documentNumber, value); } {
return DocumentNumberFormatter.Build(
SelectedDocumentType == null ? null : SelectedDocumentType.Title,
SelectedAccountingBook == null ? null : SelectedAccountingBook.Title,
DocumentSequenceNumber);
}
}
public string DocumentSequenceNumber
{
get { return _documentSequenceNumber; }
set
{
if (SetProperty(ref _documentSequenceNumber, value))
{
ClearValidationMessage();
OnPropertyChanged("DocumentNumber");
}
}
}
public ObservableCollection<GroupOption> DocumentTypes { get; private set; }
public GroupOption SelectedAccountingBook
{
get { return _selectedAccountingBook; }
set
{
if (SetProperty(ref _selectedAccountingBook, value))
{
ClearValidationMessage();
OnPropertyChanged("DocumentNumber");
}
}
}
public GroupOption SelectedDocumentType
{
get { return _selectedDocumentType; }
set
{
if (SetProperty(ref _selectedDocumentType, value))
{
ClearValidationMessage();
OnPropertyChanged("DocumentNumber");
}
}
} }
public string ValidationMessage public string ValidationMessage
@@ -46,7 +114,12 @@ namespace XLAB2
{ {
return new DocumentEditorResult return new DocumentEditorResult
{ {
DocumentNumber = DocumentNumber == null ? string.Empty : DocumentNumber.Trim(), AccountingBookKey = SelectedAccountingBook == null ? string.Empty : SelectedAccountingBook.Key,
AccountingBookTitle = SelectedAccountingBook == null ? string.Empty : SelectedAccountingBook.Title,
DocumentNumber = DocumentNumber,
DocumentSequenceNumber = DocumentSequenceNumber == null ? string.Empty : DocumentSequenceNumber.Trim(),
DocumentTypeKey = SelectedDocumentType == null ? string.Empty : SelectedDocumentType.Key,
DocumentTypeTitle = SelectedDocumentType == null ? string.Empty : SelectedDocumentType.Title,
AcceptedOn = AcceptedOn.HasValue ? AcceptedOn.Value : DateTime.Today, AcceptedOn = AcceptedOn.HasValue ? AcceptedOn.Value : DateTime.Today,
IssuedOn = null IssuedOn = null
}; };
@@ -57,11 +130,31 @@ namespace XLAB2
RaiseCloseRequested(false); RaiseCloseRequested(false);
} }
private void ClearValidationMessage()
{
if (!string.IsNullOrEmpty(ValidationMessage))
{
ValidationMessage = string.Empty;
}
}
private void Confirm(object parameter) private void Confirm(object parameter)
{ {
if (string.IsNullOrWhiteSpace(DocumentNumber)) if (SelectedDocumentType == null)
{ {
ValidationMessage = "Введите номер ПСВ."; ValidationMessage = "Выберите тип документа.";
return;
}
if (SelectedAccountingBook == null)
{
ValidationMessage = "Выберите книгу учета.";
return;
}
if (string.IsNullOrWhiteSpace(DocumentSequenceNumber))
{
ValidationMessage = "Введите номер документа.";
return; return;
} }
@@ -71,10 +164,42 @@ namespace XLAB2
return; return;
} }
if (DocumentNumber.Length > DocumentNumberFormatter.MaxLength)
{
ValidationMessage = string.Format(
"Итоговый номер документа не должен превышать {0} символов.",
DocumentNumberFormatter.MaxLength);
return;
}
ValidationMessage = string.Empty; ValidationMessage = string.Empty;
RaiseCloseRequested(true); RaiseCloseRequested(true);
} }
private static GroupOption ResolveSelectedOption(ObservableCollection<GroupOption> items, string preferredKey)
{
if (items == null || items.Count == 0)
{
return null;
}
if (!string.IsNullOrWhiteSpace(preferredKey))
{
var matchingItem = items.FirstOrDefault(delegate(GroupOption item)
{
return item != null
&& string.Equals(item.Key, preferredKey.Trim(), StringComparison.OrdinalIgnoreCase);
});
if (matchingItem != null)
{
return matchingItem;
}
}
return items[0];
}
private void RaiseCloseRequested(bool? dialogResult) private void RaiseCloseRequested(bool? dialogResult)
{ {
var handler = CloseRequested; var handler = CloseRequested;

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Windows; using System.Windows;
@@ -13,6 +14,8 @@ namespace XLAB2
IReadOnlyList<string> ShowCloneVerificationDialog(CloneVerificationSeed seed); IReadOnlyList<string> ShowCloneVerificationDialog(CloneVerificationSeed seed);
GroupOption ShowAccountingBookEditDialog(GroupOption seed, bool isNew, IReadOnlyList<GroupOption> existingItems);
SpoiDirectoryItem ShowSpoiEditDialog(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems); SpoiDirectoryItem ShowSpoiEditDialog(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems);
SpnmtpDirectoryItem ShowSpnmtpEditDialog(SpnmtpDirectoryItem seed, bool isNew, IReadOnlyList<SpnmtpDirectoryItem> existingItems); SpnmtpDirectoryItem ShowSpnmtpEditDialog(SpnmtpDirectoryItem seed, bool isNew, IReadOnlyList<SpnmtpDirectoryItem> existingItems);
@@ -33,6 +36,8 @@ namespace XLAB2
internal sealed class DialogService : IDialogService internal sealed class DialogService : IDialogService
{ {
private readonly DocumentNumberDirectoryService _documentNumberDirectoryService;
public DialogService() public DialogService()
{ {
} }
@@ -42,11 +47,18 @@ namespace XLAB2
Owner = owner; Owner = owner;
} }
public DialogService(Window owner, DocumentNumberDirectoryService documentNumberDirectoryService)
{
Owner = owner;
_documentNumberDirectoryService = documentNumberDirectoryService;
}
public Window Owner { get; set; } public Window Owner { get; set; }
public DocumentEditorResult ShowCreateDocumentDialog(DocumentEditorResult seed) public DocumentEditorResult ShowCreateDocumentDialog(DocumentEditorResult seed)
{ {
var viewModel = new CreateDocumentWindowViewModel(seed); var directory = (_documentNumberDirectoryService ?? CreateDefaultDocumentNumberDirectoryService()).LoadDirectory();
var viewModel = new CreateDocumentWindowViewModel(seed, directory);
var window = new CreateDocumentWindow(viewModel); var window = new CreateDocumentWindow(viewModel);
AttachOwner(window); AttachOwner(window);
@@ -84,6 +96,16 @@ namespace XLAB2
return result.HasValue && result.Value ? viewModel.GetSerialNumbers() : null; return result.HasValue && result.Value ? viewModel.GetSerialNumbers() : null;
} }
public GroupOption ShowAccountingBookEditDialog(GroupOption seed, bool isNew, IReadOnlyList<GroupOption> existingItems)
{
var viewModel = new AccountingBookEditWindowViewModel(seed, isNew, existingItems);
var window = new AccountingBookEditWindow(viewModel);
AttachOwner(window);
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.ToResult() : null;
}
public SpoiDirectoryItem ShowSpoiEditDialog(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems) public SpoiDirectoryItem ShowSpoiEditDialog(SpoiDirectoryItem seed, bool isNew, IReadOnlyList<SpoiDirectoryItem> existingItems)
{ {
var viewModel = new SpoiEditWindowViewModel(seed, isNew, existingItems); var viewModel = new SpoiEditWindowViewModel(seed, isNew, existingItems);
@@ -157,5 +179,10 @@ namespace XLAB2
MessageBox.Show(Owner, message, "ПСВ", MessageBoxButton.OK, image); MessageBox.Show(Owner, message, "ПСВ", MessageBoxButton.OK, image);
} }
private static DocumentNumberDirectoryService CreateDefaultDocumentNumberDirectoryService()
{
return new DocumentNumberDirectoryService(System.IO.Path.Combine(AppContext.BaseDirectory, "Assets", "document-number-directory.json"));
}
} }
} }

View File

@@ -0,0 +1,260 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace XLAB2
{
internal sealed class DocumentNumberDirectory
{
public DocumentNumberDirectory(IReadOnlyList<GroupOption> accountingBooks, IReadOnlyList<GroupOption> documentTypes)
{
AccountingBooks = accountingBooks ?? Array.Empty<GroupOption>();
DocumentTypes = documentTypes ?? Array.Empty<GroupOption>();
}
public IReadOnlyList<GroupOption> AccountingBooks { get; private set; }
public IReadOnlyList<GroupOption> DocumentTypes { get; private set; }
}
internal sealed class DocumentNumberDirectoryService
{
private readonly string _filePath;
public DocumentNumberDirectoryService(string filePath)
{
_filePath = filePath;
}
public DocumentNumberDirectory LoadDirectory()
{
var configuration = LoadConfiguration();
return new DocumentNumberDirectory(
NormalizeEntries(configuration.AccountingBooks, "книг учета"),
NormalizeEntries(configuration.DocumentTypes, "типов документов"));
}
public IReadOnlyList<GroupOption> LoadAccountingBooks()
{
return LoadDirectory().AccountingBooks;
}
public void SaveAccountingBooks(IReadOnlyList<GroupOption> accountingBooks)
{
var configuration = LoadConfiguration();
var normalizedAccountingBooks = NormalizeWritableEntries(accountingBooks, "книг учета");
var normalizedDocumentTypes = NormalizeEntries(configuration.DocumentTypes, "типов документов");
configuration.AccountingBooks = ToConfigurationEntries(normalizedAccountingBooks);
configuration.DocumentTypes = ToConfigurationEntries(normalizedDocumentTypes);
SaveConfiguration(configuration);
}
private static IReadOnlyList<GroupOption> NormalizeEntries(IEnumerable<DocumentNumberDirectoryEntry> entries, string sectionName)
{
var items = new List<GroupOption>();
var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var entry in entries ?? Array.Empty<DocumentNumberDirectoryEntry>())
{
if (entry == null)
{
continue;
}
var key = DocumentNumberFormatter.NormalizePart(entry.Key);
var title = DocumentNumberFormatter.NormalizePart(entry.Title);
if (key == null || title == null || !seenKeys.Add(key))
{
continue;
}
items.Add(new GroupOption
{
Key = key,
Title = title
});
}
if (items.Count == 0)
{
throw new InvalidOperationException(string.Format("Справочник номеров документов не содержит {0}.", sectionName));
}
return items;
}
private static IReadOnlyList<GroupOption> NormalizeWritableEntries(IEnumerable<GroupOption> entries, string sectionName)
{
var items = new List<GroupOption>();
var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var seenTitles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var entry in entries ?? Array.Empty<GroupOption>())
{
if (entry == null)
{
continue;
}
var key = DocumentNumberFormatter.NormalizePart(entry.Key);
if (key == null)
{
throw new InvalidOperationException("Не задан ключ книги учета.");
}
var title = DocumentNumberFormatter.NormalizePart(entry.Title);
if (title == null)
{
throw new InvalidOperationException("Не задан номер книги учета.");
}
if (!seenKeys.Add(key))
{
throw new InvalidOperationException(string.Format("Ключ книги учета \"{0}\" повторяется.", key));
}
if (!seenTitles.Add(title))
{
throw new InvalidOperationException(string.Format("Номер книги учета \"{0}\" повторяется.", title));
}
items.Add(new GroupOption
{
Key = key,
Title = title
});
}
if (items.Count == 0)
{
throw new InvalidOperationException(string.Format("Справочник номеров документов не содержит {0}.", sectionName));
}
return items;
}
private DocumentNumberDirectoryConfiguration LoadConfiguration()
{
if (string.IsNullOrWhiteSpace(_filePath))
{
throw new InvalidOperationException("Не задан путь к справочнику номеров документов.");
}
if (!File.Exists(_filePath))
{
throw new InvalidOperationException(string.Format("Не найден справочник номеров документов: {0}.", _filePath));
}
using (var stream = File.OpenRead(_filePath))
{
var configuration = JsonSerializer.Deserialize<DocumentNumberDirectoryConfiguration>(stream, CreateReadSerializerOptions());
if (configuration == null)
{
throw new InvalidOperationException("Справочник номеров документов пуст или поврежден.");
}
return configuration;
}
}
private void SaveConfiguration(DocumentNumberDirectoryConfiguration configuration)
{
var directoryPath = Path.GetDirectoryName(_filePath);
if (!string.IsNullOrWhiteSpace(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
var json = JsonSerializer.Serialize(configuration, CreateWriteSerializerOptions());
File.WriteAllText(_filePath, json);
}
private static List<DocumentNumberDirectoryEntry> ToConfigurationEntries(IEnumerable<GroupOption> entries)
{
var items = new List<DocumentNumberDirectoryEntry>();
foreach (var entry in entries ?? Array.Empty<GroupOption>())
{
if (entry == null)
{
continue;
}
items.Add(new DocumentNumberDirectoryEntry
{
Key = entry.Key,
Title = entry.Title
});
}
return items;
}
private static JsonSerializerOptions CreateReadSerializerOptions()
{
return new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
}
private static JsonSerializerOptions CreateWriteSerializerOptions()
{
return new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
}
}
internal static class DocumentNumberFormatter
{
public const int MaxLength = 60;
public static string Build(string documentTypeTitle, string accountingBookTitle, string documentSequenceNumber)
{
var normalizedDocumentTypeTitle = NormalizePart(documentTypeTitle);
var normalizedAccountingBookTitle = NormalizePart(accountingBookTitle);
var normalizedDocumentSequenceNumber = NormalizePart(documentSequenceNumber);
if (normalizedDocumentTypeTitle == null
|| normalizedAccountingBookTitle == null
|| normalizedDocumentSequenceNumber == null)
{
return string.Empty;
}
return string.Format("{0} № {1}-{2}", normalizedDocumentTypeTitle, normalizedAccountingBookTitle, normalizedDocumentSequenceNumber);
}
public static string NormalizePart(string value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
}
internal sealed class DocumentNumberDirectoryConfiguration
{
public List<DocumentNumberDirectoryEntry> AccountingBooks { get; set; }
public List<DocumentNumberDirectoryEntry> DocumentTypes { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData { get; set; }
}
internal sealed class DocumentNumberDirectoryEntry
{
public string Key { get; set; }
public string Title { get; set; }
}
}

View File

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

138
XLAB2/EkzDirectoryModels.cs Normal file
View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
namespace XLAB2
{
public sealed class EkzDirectoryItem
{
public int Id { get; set; }
public int TypeSizeId { get; set; }
public string MeasurementAreaName { get; set; }
public string InstrumentName { get; set; }
public string TypeName { get; set; }
public string RangeText { get; set; }
public string AccuracyText { get; set; }
public string RegistryNumber { get; set; }
public int OwnerOrganizationId { get; set; }
public string OwnerOrganizationName { get; set; }
public string SerialNumber { get; set; }
public string InventoryNumber { get; set; }
public string StickerNumbers { get; set; }
public string Notes { get; set; }
}
public sealed class EkzMkDirectoryItem
{
public int CardId { get; set; }
public int InstrumentId { get; set; }
public string VerificationTypeName { get; set; }
public string VerificationOrganizationName { get; set; }
public string DocumentNumber { get; set; }
public string VerificationDocumentNumber { get; set; }
public DateTime? VerificationDocumentDate { get; set; }
public string StickerNumber { get; set; }
public string VerifierName { get; set; }
public int PeriodMonths { get; set; }
public DateTime? AcceptedOn { get; set; }
public DateTime? PlannedOn { get; set; }
public DateTime? PerformedOn { get; set; }
public DateTime? IssuedOn { get; set; }
public bool? IsPassed { get; set; }
public string Notes { get; set; }
public string ResultText
{
get
{
if (!IsPassed.HasValue)
{
return string.Empty;
}
return IsPassed.Value ? "Годен" : "Не годен";
}
}
public string VerificationDocumentDisplay
{
get
{
if (string.IsNullOrWhiteSpace(VerificationDocumentNumber))
{
return VerificationDocumentDate.HasValue ? VerificationDocumentDate.Value.ToString("d") : string.Empty;
}
if (!VerificationDocumentDate.HasValue)
{
return VerificationDocumentNumber;
}
return string.Format("{0} от {1:d}", VerificationDocumentNumber, VerificationDocumentDate.Value);
}
}
}
internal sealed class EkzDeleteImpactItem
{
public string TableName { get; set; }
public int RowCount { get; set; }
}
internal sealed class EkzDeletePreview
{
public bool CanDelete { get; set; }
public IReadOnlyList<EkzDeleteImpactItem> ImpactItems { get; set; }
public string ConfirmationMessage { get; set; }
public string WarningMessage { get; set; }
}
internal sealed class EkzDeleteResult
{
public bool IsDeleted { get; set; }
public IReadOnlyList<EkzDeleteImpactItem> ImpactItems { get; set; }
public string WarningMessage { get; set; }
}
internal static class EkzDirectoryRules
{
public const int SerialNumberMaxLength = 30;
public const int InventoryNumberMaxLength = 30;
public const int NotesMaxLength = 8000;
}
}

View File

@@ -0,0 +1,858 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Microsoft.Data.SqlClient;
namespace XLAB2
{
internal sealed class EkzDirectoryService
{
private static readonly string[] CascadingEkzChildTables = { "EKZMK", "EKZMCP" };
private static readonly string[] CascadingEkzMkChildTables = { "EKZMKFCTVL", "EKZMKDH", "EKZMKEKZK", "EKZMKND", "KSPELEKZMK" };
public int AddEkzItem(EkzDirectoryItem item)
{
var normalizedItem = NormalizeEkzItem(item);
const string sql = @"
INSERT INTO dbo.EKZ
(
IDTPRZ,
IDFRPDV,
KLSIPR,
NNZV,
NNIN,
DSEKZ,
GUIDEKZ,
IsDeleted
)
VALUES
(
@TypeSizeId,
@OwnerOrganizationId,
1,
@SerialNumber,
@InventoryNumber,
@Notes,
@Guid,
0
);
SELECT CAST(SCOPE_IDENTITY() AS int);";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureEkzIsUnique(connection, normalizedItem.TypeSizeId, normalizedItem.OwnerOrganizationId, normalizedItem.SerialNumber, null);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = normalizedItem.OwnerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = normalizedItem.SerialNumber;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@InventoryNumber", SqlDbType.VarChar, EkzDirectoryRules.InventoryNumberMaxLength, normalizedItem.InventoryNumber);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Notes", SqlDbType.VarChar, EkzDirectoryRules.NotesMaxLength, normalizedItem.Notes);
command.Parameters.Add("@Guid", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
return Convert.ToInt32(command.ExecuteScalar());
}
}
public EkzDeletePreview GetEkzDeletePreview(int id)
{
if (id <= 0)
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Не выбрана запись EKZ для удаления."
};
}
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
return BuildEkzDeletePreview(connection, null, id);
}
}
public EkzDeleteResult DeleteEkzItem(int id)
{
if (id <= 0)
{
throw new InvalidOperationException("Не выбрана запись EKZ для удаления.");
}
try
{
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
var preview = BuildEkzDeletePreview(connection, transaction, id);
if (!preview.CanDelete)
{
transaction.Rollback();
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = preview.ImpactItems ?? Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = preview.WarningMessage
};
}
var impactItems = new List<EkzDeleteImpactItem>();
AddImpactItem(impactItems, "EKZMKFCTVL", DeleteEkzMkFctvl(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKDH", DeleteEkzMkDh(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKEKZK", DeleteEkzMkEkzk(connection, transaction, id));
AddImpactItem(impactItems, "EKZMKND", DeleteEkzMkNd(connection, transaction, id));
AddImpactItem(impactItems, "KSPELEKZMK", DeleteKspelEkzMk(connection, transaction, id));
AddImpactItem(impactItems, "DMS", DeleteEkzDms(connection, transaction, id));
AddImpactItem(impactItems, "EKZMK", DeleteEkzMk(connection, transaction, id));
AddImpactItem(impactItems, "EKZMCP", DeleteEkzMcp(connection, transaction, id));
var deletedEkzCount = DeleteEkz(connection, transaction, id);
if (deletedEkzCount == 0)
{
transaction.Rollback();
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Запись EKZ для удаления не найдена."
};
}
AddImpactItem(impactItems, "EKZ", deletedEkzCount);
transaction.Commit();
return new EkzDeleteResult
{
IsDeleted = true,
ImpactItems = OrderImpactItems(impactItems)
};
}
}
}
catch (SqlException ex) when (ex.Number == 547)
{
return new EkzDeleteResult
{
IsDeleted = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = CreateEkzDeleteFailedMessage(ex)
};
}
}
public IReadOnlyList<EkzDirectoryItem> LoadEkzItems()
{
const string sql = @"
SELECT
z.IDEKZ AS Id,
z.IDTPRZ AS TypeSizeId,
areas.NMOI AS MeasurementAreaName,
names.NMTP AS InstrumentName,
tips.TP AS TypeName,
tprz.DPZN AS RangeText,
tprz.HRTC AS AccuracyText,
tprz.NNGSRS AS RegistryNumber,
z.IDFRPDV AS OwnerOrganizationId,
ownerOrg.NMFRPD AS OwnerOrganizationName,
z.NNZV AS SerialNumber,
z.NNIN AS InventoryNumber,
stickers.StickerNumbers AS StickerNumbers,
CAST(z.DSEKZ AS nvarchar(max)) AS Notes
FROM dbo.EKZ z
JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
JOIN dbo.FRPD ownerOrg ON ownerOrg.IDFRPD = z.IDFRPDV
OUTER APPLY
(
SELECT STUFF(
(
SELECT N' ' + sticker.StickerNumber
FROM
(
SELECT DISTINCT LTRIM(RTRIM(m.NNNKL)) AS StickerNumber
FROM dbo.EKZMK m
WHERE m.IDEKZ = z.IDEKZ
AND NULLIF(LTRIM(RTRIM(m.NNNKL)), N'') IS NOT NULL
) sticker
ORDER BY sticker.StickerNumber
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'),
1,
1,
N'') AS StickerNumbers
) stickers
WHERE ISNULL(z.IsDeleted, 0) = 0
ORDER BY ownerOrg.NMFRPD, areas.NMOI, names.NMTP, tips.TP, tprz.DPZN, z.NNZV, z.IDEKZ;";
var items = new List<EkzDirectoryItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new EkzDirectoryItem
{
Id = ReferenceDirectorySqlHelpers.GetInt32(reader, "Id"),
TypeSizeId = ReferenceDirectorySqlHelpers.GetInt32(reader, "TypeSizeId"),
MeasurementAreaName = ReferenceDirectorySqlHelpers.GetString(reader, "MeasurementAreaName"),
InstrumentName = ReferenceDirectorySqlHelpers.GetString(reader, "InstrumentName"),
TypeName = ReferenceDirectorySqlHelpers.GetString(reader, "TypeName"),
RangeText = ReferenceDirectorySqlHelpers.GetString(reader, "RangeText"),
AccuracyText = ReferenceDirectorySqlHelpers.GetString(reader, "AccuracyText"),
RegistryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "RegistryNumber"),
OwnerOrganizationId = ReferenceDirectorySqlHelpers.GetInt32(reader, "OwnerOrganizationId"),
OwnerOrganizationName = ReferenceDirectorySqlHelpers.GetString(reader, "OwnerOrganizationName"),
SerialNumber = ReferenceDirectorySqlHelpers.GetString(reader, "SerialNumber"),
InventoryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "InventoryNumber"),
StickerNumbers = ReferenceDirectorySqlHelpers.GetString(reader, "StickerNumbers"),
Notes = ReferenceDirectorySqlHelpers.GetString(reader, "Notes")
});
}
}
}
return items;
}
public IReadOnlyList<EkzMkDirectoryItem> LoadEkzMkItems(int instrumentId)
{
const string sql = @"
SELECT
m.IDEKZMK AS CardId,
m.IDEKZ AS InstrumentId,
verificationType.NMVDMK AS VerificationTypeName,
organization.NMFRPD AS VerificationOrganizationName,
m.NNZVPV AS DocumentNumber,
verificationDocument.NNDMS AS VerificationDocumentNumber,
verificationDocument.DTDMS AS VerificationDocumentDate,
m.NNNKL AS StickerNumber,
verifier.PRFIO AS VerifierName,
m.PRMK AS PeriodMonths,
m.DTPRM AS AcceptedOn,
m.DTMKPL AS PlannedOn,
m.DTMKFK AS PerformedOn,
m.DTVDM AS IssuedOn,
m.GDN AS IsPassed,
CAST(m.DSEKZMK AS nvarchar(max)) AS Notes
FROM dbo.EKZMK m
LEFT JOIN dbo.SPVDMK verificationType ON verificationType.IDSPVDMK = m.IDSPVDMK
LEFT JOIN dbo.FRPD organization ON organization.IDFRPD = m.IDFRPD
LEFT JOIN dbo.PRSN verifier ON verifier.IDPRSN = m.IDPRSN
OUTER APPLY
(
SELECT TOP (1)
d.NND AS NNDMS,
d.DTD AS DTDMS
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.FRDMS frdms ON frdms.IDFRDMS = d.IDFRDMS
JOIN dbo.SPVDD spvdd ON spvdd.IDSPVDD = frdms.IDSPVDD
WHERE d.IDOD = m.IDEKZMK
AND vdd.IDSPVDOD = 2
AND spvdd.IDSPVDD IN (2, 6, 8)
ORDER BY d.DTD DESC
) verificationDocument
WHERE m.IDEKZ = @InstrumentId
ORDER BY ISNULL(m.DTPRM, CONVERT(datetime, '19000101', 112)) DESC, m.IDEKZMK DESC;";
var items = new List<EkzMkDirectoryItem>();
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = instrumentId;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
items.Add(new EkzMkDirectoryItem
{
CardId = ReferenceDirectorySqlHelpers.GetInt32(reader, "CardId"),
InstrumentId = ReferenceDirectorySqlHelpers.GetInt32(reader, "InstrumentId"),
VerificationTypeName = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationTypeName"),
VerificationOrganizationName = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationOrganizationName"),
DocumentNumber = ReferenceDirectorySqlHelpers.GetString(reader, "DocumentNumber"),
VerificationDocumentNumber = ReferenceDirectorySqlHelpers.GetString(reader, "VerificationDocumentNumber"),
VerificationDocumentDate = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "VerificationDocumentDate"),
StickerNumber = ReferenceDirectorySqlHelpers.GetString(reader, "StickerNumber"),
VerifierName = ReferenceDirectorySqlHelpers.GetString(reader, "VerifierName"),
PeriodMonths = ReferenceDirectorySqlHelpers.GetInt32(reader, "PeriodMonths"),
AcceptedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "AcceptedOn"),
PlannedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "PlannedOn"),
PerformedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "PerformedOn"),
IssuedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "IssuedOn"),
IsPassed = ReferenceDirectorySqlHelpers.GetNullableBoolean(reader, "IsPassed"),
Notes = ReferenceDirectorySqlHelpers.GetString(reader, "Notes")
});
}
}
}
return items;
}
public IReadOnlyList<DirectoryLookupItem> LoadFrpdReferences()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
fr.IDFRPD AS Id,
fr.NMFRPD AS Name
FROM dbo.FRPD fr
WHERE NULLIF(LTRIM(RTRIM(fr.NMFRPD)), '') IS NOT NULL
ORDER BY fr.NMFRPD, fr.IDFRPD;");
}
public IReadOnlyList<DirectoryLookupItem> LoadTypeSizeReferences()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT
tprz.IDTPRZ AS Id,
LTRIM(RTRIM(
COALESCE(NULLIF(areas.NMOI, N'') + N' / ', N'')
+ COALESCE(NULLIF(names.NMTP, N'') + N' / ', N'')
+ COALESCE(NULLIF(tips.TP, N''), N'')
+ CASE WHEN NULLIF(LTRIM(RTRIM(tprz.DPZN)), N'') IS NULL THEN N'' ELSE N' / ' + tprz.DPZN END
+ CASE
WHEN NULLIF(LTRIM(RTRIM(CONVERT(nvarchar(50), tprz.NNGSRS))), N'') IS NULL THEN N''
ELSE N' / № ГР ' + CONVERT(nvarchar(50), tprz.NNGSRS)
END
)) AS Name
FROM dbo.TPRZ tprz
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
ORDER BY areas.NMOI, names.NMTP, tips.TP, tprz.DPZN, tprz.IDTPRZ;");
}
public void UpdateEkzItem(EkzDirectoryItem item)
{
var normalizedItem = NormalizeEkzItem(item);
if (normalizedItem.Id <= 0)
{
throw new InvalidOperationException("Не выбрана запись EKZ для изменения.");
}
const string sql = @"
UPDATE dbo.EKZ
SET IDTPRZ = @TypeSizeId,
IDFRPDV = @OwnerOrganizationId,
NNZV = @SerialNumber,
NNIN = @InventoryNumber,
DSEKZ = @Notes
WHERE IDEKZ = @Id;
SELECT @@ROWCOUNT;";
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
EnsureEkzIsUnique(connection, normalizedItem.TypeSizeId, normalizedItem.OwnerOrganizationId, normalizedItem.SerialNumber, normalizedItem.Id);
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = normalizedItem.OwnerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = normalizedItem.SerialNumber;
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@InventoryNumber", SqlDbType.VarChar, EkzDirectoryRules.InventoryNumberMaxLength, normalizedItem.InventoryNumber);
ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Notes", SqlDbType.VarChar, EkzDirectoryRules.NotesMaxLength, normalizedItem.Notes);
if (Convert.ToInt32(command.ExecuteScalar()) == 0)
{
throw new InvalidOperationException("Запись EKZ для изменения не найдена.");
}
}
}
private static EkzDeletePreview BuildEkzDeletePreview(SqlConnection connection, SqlTransaction transaction, int id)
{
if (!EkzExists(connection, transaction, id))
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = Array.Empty<EkzDeleteImpactItem>(),
WarningMessage = "Запись EKZ для удаления не найдена."
};
}
var cardIds = LoadEkzMkCardIds(connection, transaction, id);
var blockers = new List<DeleteBlockerInfo>();
blockers.AddRange(ReferenceDirectorySqlHelpers.LoadDeleteBlockersFromForeignKeys(connection, transaction, "EKZ", id, CascadingEkzChildTables));
blockers.AddRange(LoadUnhandledDeleteBlockers(connection, transaction, "EKZMK", cardIds, CascadingEkzMkChildTables));
var impactItems = BuildImpactItems(connection, transaction, id, cardIds);
var mergedBlockers = MergeBlockers(blockers);
if (mergedBlockers.Count > 0)
{
return new EkzDeletePreview
{
CanDelete = false,
ImpactItems = impactItems,
WarningMessage = CreateEkzCascadeBlockedMessage(mergedBlockers)
};
}
return new EkzDeletePreview
{
CanDelete = true,
ImpactItems = impactItems,
ConfirmationMessage = CreateEkzDeleteConfirmationMessage(impactItems)
};
}
private static IReadOnlyList<EkzDeleteImpactItem> BuildImpactItems(SqlConnection connection, SqlTransaction transaction, int instrumentId, IReadOnlyCollection<int> cardIds)
{
var impactItems = new List<EkzDeleteImpactItem>();
AddImpactItem(impactItems, "EKZ", 1);
AddImpactItem(impactItems, "EKZMK", cardIds == null ? 0 : cardIds.Count);
AddImpactItem(impactItems, "DMS", CountEkzDms(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKFCTVL", CountEkzMkFctvl(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKDH", CountEkzMkDh(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKEKZK", CountEkzMkEkzk(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMKND", CountEkzMkNd(connection, transaction, instrumentId));
AddImpactItem(impactItems, "KSPELEKZMK", CountKspelEkzMk(connection, transaction, instrumentId));
AddImpactItem(impactItems, "EKZMCP", CountEkzMcp(connection, transaction, instrumentId));
return OrderImpactItems(impactItems);
}
private static IReadOnlyList<EkzDeleteImpactItem> OrderImpactItems(IEnumerable<EkzDeleteImpactItem> impactItems)
{
var order = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
{ "EKZ", 1 },
{ "EKZMK", 2 },
{ "DMS", 3 },
{ "EKZMKFCTVL", 4 },
{ "EKZMKDH", 5 },
{ "EKZMKEKZK", 6 },
{ "EKZMKND", 7 },
{ "KSPELEKZMK", 8 },
{ "EKZMCP", 9 }
};
return (impactItems ?? Enumerable.Empty<EkzDeleteImpactItem>())
.Where(delegate(EkzDeleteImpactItem item) { return item != null && item.RowCount > 0; })
.OrderBy(delegate(EkzDeleteImpactItem item)
{
int value;
return order.TryGetValue(item.TableName ?? string.Empty, out value) ? value : int.MaxValue;
})
.ThenBy(delegate(EkzDeleteImpactItem item) { return item.TableName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static void AddImpactItem(ICollection<EkzDeleteImpactItem> impactItems, string tableName, int rowCount)
{
if (impactItems == null || string.IsNullOrWhiteSpace(tableName) || rowCount <= 0)
{
return;
}
impactItems.Add(new EkzDeleteImpactItem
{
TableName = tableName,
RowCount = rowCount
});
}
private static string CreateEkzDeleteConfirmationMessage(IEnumerable<EkzDeleteImpactItem> impactItems)
{
var items = OrderImpactItems(impactItems).ToList();
var lines = new List<string>();
if (items.Count == 0)
{
lines.Add("Будет физически удалена только запись EKZ.");
}
else
{
lines.Add("Будут физически удалены записи:");
foreach (var item in items)
{
lines.Add(string.Format("{0}: {1}", item.TableName, item.RowCount));
}
}
lines.Add(string.Empty);
lines.Add("Продолжить?");
return string.Join(Environment.NewLine, lines.ToArray());
}
private static string CreateEkzCascadeBlockedMessage(IEnumerable<DeleteBlockerInfo> blockers)
{
return string.Format(
"Экземпляр не может быть удалён автоматически. Есть связанные записи в таблицах, которые не входят в каскад удаления: {0}.",
FormatBlockerDetails(blockers));
}
private static string CreateEkzDeleteFailedMessage(SqlException ex)
{
var suffix = ex == null || string.IsNullOrWhiteSpace(ex.Message)
? string.Empty
: " " + ex.Message.Trim();
return "Экземпляр не может быть удалён из-за ограничений ссылочной целостности БД." + suffix;
}
private static string FormatBlockerDetails(IEnumerable<DeleteBlockerInfo> blockers)
{
var details = string.Join(", ", (blockers ?? Enumerable.Empty<DeleteBlockerInfo>())
.Where(delegate(DeleteBlockerInfo blocker) { return blocker != null && blocker.RowCount > 0; })
.OrderBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.Select(delegate(DeleteBlockerInfo blocker) { return string.Format("{0}: {1}", blocker.TableName, blocker.RowCount); }));
return string.IsNullOrWhiteSpace(details) ? "связанные данные" : details;
}
private static List<DeleteBlockerInfo> MergeBlockers(IEnumerable<DeleteBlockerInfo> blockers)
{
return (blockers ?? Enumerable.Empty<DeleteBlockerInfo>())
.Where(delegate(DeleteBlockerInfo blocker) { return blocker != null && blocker.RowCount > 0 && !string.IsNullOrWhiteSpace(blocker.TableName); })
.GroupBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, DeleteBlockerInfo> group)
{
return new DeleteBlockerInfo
{
TableName = group.Key,
RowCount = group.Sum(delegate(DeleteBlockerInfo blocker) { return blocker.RowCount; })
};
})
.OrderBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static List<DeleteBlockerInfo> LoadUnhandledDeleteBlockers(SqlConnection connection, SqlTransaction transaction, string parentTableName, IEnumerable<int> ids, IEnumerable<string> excludedChildTables)
{
var blockers = new List<DeleteBlockerInfo>();
foreach (var id in (ids ?? Enumerable.Empty<int>()).Where(delegate(int value) { return value > 0; }).Distinct())
{
blockers.AddRange(ReferenceDirectorySqlHelpers.LoadDeleteBlockersFromForeignKeys(connection, transaction, parentTableName, id, excludedChildTables));
}
return MergeBlockers(blockers);
}
private static bool EkzExists(SqlConnection connection, SqlTransaction transaction, int id)
{
const string sql = @"
SELECT COUNT(1)
FROM dbo.EKZ
WHERE IDEKZ = @Id;";
return ExecuteScalarForId(connection, transaction, sql, id) > 0;
}
private static List<int> LoadEkzMkCardIds(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
const string sql = @"
SELECT IDEKZMK
FROM dbo.EKZMK
WHERE IDEKZ = @InstrumentId
ORDER BY IDEKZMK;";
return ExecuteIdList(connection, transaction, sql, "@InstrumentId", instrumentId);
}
private static int CountEkzMcp(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMCP
WHERE IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkFctvl(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKFCTVL child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkDh(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKDH child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkEkzk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKEKZK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzMkNd(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.EKZMKND child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountKspelEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.KSPELEKZMK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;", instrumentId);
}
private static int CountEkzDms(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
SELECT COUNT(*)
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.EKZMK m ON m.IDEKZMK = d.IDOD
WHERE m.IDEKZ = @InstrumentId
AND vdd.IDSPVDOD = 2;", instrumentId);
}
private static int DeleteEkzMcp(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE FROM dbo.EKZMCP
WHERE IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkFctvl(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKFCTVL child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkDh(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKDH child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkEkzk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKEKZK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMkNd(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.EKZMKND child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteKspelEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE child
FROM dbo.KSPELEKZMK child
JOIN dbo.EKZMK parent ON parent.IDEKZMK = child.IDEKZMK
WHERE parent.IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzDms(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE d
FROM dbo.DMS d
JOIN dbo.VDODVDD vdd ON vdd.IDVDODVDD = d.IDVDODVDD
JOIN dbo.EKZMK m ON m.IDEKZMK = d.IDOD
WHERE m.IDEKZ = @InstrumentId
AND vdd.IDSPVDOD = 2;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkzMk(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{
return ExecuteScalarForInstrument(connection, transaction, @"
DELETE FROM dbo.EKZMK
WHERE IDEKZ = @InstrumentId;
SELECT @@ROWCOUNT;", instrumentId);
}
private static int DeleteEkz(SqlConnection connection, SqlTransaction transaction, int id)
{
return ExecuteScalarForId(connection, transaction, @"
DELETE FROM dbo.EKZ
WHERE IDEKZ = @Id;
SELECT @@ROWCOUNT;", id);
}
private static int ExecuteScalarForInstrument(SqlConnection connection, SqlTransaction transaction, string sql, int instrumentId)
{
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = instrumentId;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static int ExecuteScalarForId(SqlConnection connection, SqlTransaction transaction, string sql, int id)
{
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static List<int> ExecuteIdList(SqlConnection connection, SqlTransaction transaction, string sql, string parameterName, int parameterValue)
{
var result = new List<int>();
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add(parameterName, SqlDbType.Int).Value = parameterValue;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
result.Add(reader.GetInt32(0));
}
}
}
return result;
}
private static string NormalizeOptional(string value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
private static EkzDirectoryItem NormalizeEkzItem(EkzDirectoryItem item)
{
if (item == null)
{
throw new InvalidOperationException("Не переданы данные записи EKZ.");
}
var normalizedItem = new EkzDirectoryItem
{
Id = item.Id,
TypeSizeId = item.TypeSizeId,
OwnerOrganizationId = item.OwnerOrganizationId,
SerialNumber = NormalizeOptional(item.SerialNumber),
InventoryNumber = NormalizeOptional(item.InventoryNumber),
Notes = NormalizeOptional(item.Notes)
};
if (normalizedItem.TypeSizeId <= 0)
{
throw new InvalidOperationException("Не указан типоразмер СИ.");
}
if (normalizedItem.OwnerOrganizationId <= 0)
{
throw new InvalidOperationException("Не указана организация-владелец.");
}
if (string.IsNullOrWhiteSpace(normalizedItem.SerialNumber))
{
throw new InvalidOperationException("Не указан заводской номер.");
}
if (normalizedItem.SerialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength));
}
if (!string.IsNullOrWhiteSpace(normalizedItem.InventoryNumber) && normalizedItem.InventoryNumber.Length > EkzDirectoryRules.InventoryNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Инвентарный номер не должен превышать {0} символов.", EkzDirectoryRules.InventoryNumberMaxLength));
}
if (!string.IsNullOrWhiteSpace(normalizedItem.Notes) && normalizedItem.Notes.Length > EkzDirectoryRules.NotesMaxLength)
{
throw new InvalidOperationException(string.Format("Примечание не должно превышать {0} символов.", EkzDirectoryRules.NotesMaxLength));
}
return normalizedItem;
}
private static void EnsureEkzIsUnique(SqlConnection connection, int typeSizeId, int ownerOrganizationId, string serialNumber, int? excludeId)
{
const string sql = @"
SELECT TOP (1) z.IDEKZ
FROM dbo.EKZ z
WHERE z.IDTPRZ = @TypeSizeId
AND z.IDFRPDV = @OwnerOrganizationId
AND z.NNZV = @SerialNumber
AND ISNULL(z.IsDeleted, 0) = 0
AND (@ExcludeId IS NULL OR z.IDEKZ <> @ExcludeId)
ORDER BY z.IDEKZ DESC;";
using (var command = new SqlCommand(sql, connection))
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;
command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = ownerOrganizationId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = serialNumber;
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@ExcludeId", excludeId);
if (command.ExecuteScalar() != null)
{
throw new InvalidOperationException("Экземпляр с таким типоразмером, владельцем и заводским номером уже существует.");
}
}
}
}
}

View File

@@ -0,0 +1,198 @@
<Window x:Class="XLAB2.EkzDirectoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Экземпляры"
Height="900"
Width="1540"
MinHeight="760"
MinWidth="1260"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="2.2*" />
<RowDefinition Height="1.6*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0"
Margin="0,0,0,12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<Button DockPanel.Dock="Right"
Width="110"
Margin="12,0,0,0"
Command="{Binding RefreshCommand}"
Content="Обновить" />
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,0,8,0"
VerticalAlignment="Center"
Text="Поиск" />
<TextBox Width="360"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DockPanel>
<StackPanel Grid.Row="1"
Margin="0,8,0,0"
Orientation="Horizontal">
<TextBlock Margin="0,0,8,0"
VerticalAlignment="Center"
Text="Организация-владелец" />
<ComboBox Width="420"
ItemsSource="{Binding OwnerFilterItems}"
SelectedValue="{Binding SelectedOwnerFilterId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
</Grid>
<GroupBox Grid.Row="1"
Header="Экземпляры (EKZ)">
<DataGrid ItemsSource="{Binding EkzItems}"
SelectedItem="{Binding SelectedEkz, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddEkzCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить"
Command="{Binding EditEkzCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить"
Command="{Binding DeleteEkzCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Организация-владелец"
Width="220"
Binding="{Binding OwnerOrganizationName}" />
<DataGridTextColumn Header="Область измерений"
Width="160"
Binding="{Binding MeasurementAreaName}" />
<DataGridTextColumn Header="Наименование"
Width="220"
Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип"
Width="180"
Binding="{Binding TypeName}" />
<DataGridTextColumn Header="Диапазон"
Width="220"
Binding="{Binding RangeText}" />
<DataGridTextColumn Header="Х-ка точности"
Width="150"
Binding="{Binding AccuracyText}" />
<DataGridTextColumn Header="№ Госреестра"
Width="120"
Binding="{Binding RegistryNumber}" />
<DataGridTextColumn Header="Заводской номер"
Width="140"
Binding="{Binding SerialNumber}" />
<DataGridTextColumn Header="Инвентарный номер"
Width="140"
Binding="{Binding InventoryNumber}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<GroupBox Grid.Row="2"
Margin="0,12,0,0"
Header="МК выбранного экземпляра (EKZMK)">
<DataGrid ItemsSource="{Binding EkzMkItems}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="ПСВ/Акт-справка"
Width="220"
Binding="{Binding DocumentNumber}" />
<DataGridTextColumn Header="Документ по поверке"
Width="180"
Binding="{Binding VerificationDocumentDisplay}" />
<DataGridTextColumn Header="Номер наклейки"
Width="140"
Binding="{Binding StickerNumber}" />
<DataGridTextColumn Header="Поверитель"
Width="180"
Binding="{Binding VerifierName}" />
<DataGridTextColumn Header="Период, мес."
Width="95"
Binding="{Binding PeriodMonths}" />
<DataGridTextColumn Header="Принят"
Width="95"
Binding="{Binding AcceptedOn, StringFormat=d}" />
<DataGridTextColumn Header="Поверен"
Width="95"
Binding="{Binding PerformedOn, StringFormat=d}" />
<DataGridTextColumn Header="Выдан"
Width="95"
Binding="{Binding IssuedOn, StringFormat=d}" />
<DataGridTextColumn Header="Результат"
Width="95"
Binding="{Binding ResultText}" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<TextBlock Grid.Row="3"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<StackPanel Grid.Row="4"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
IsCancel="True"
Content="Закрыть" />
</StackPanel>
</Grid>
</Window>

View File

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

View File

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

109
XLAB2/EkzEditWindow.xaml Normal file
View File

@@ -0,0 +1,109 @@
<Window x:Class="XLAB2.EkzEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Height="340"
Width="860"
MinHeight="340"
MinWidth="760"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Типоразмер СИ" />
<ComboBox Grid.Row="0"
Grid.Column="1"
Margin="0,0,0,8"
ItemsSource="{Binding TypeSizeItems}"
SelectedValue="{Binding TypeSizeId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Организация-владелец" />
<ComboBox Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,8"
ItemsSource="{Binding OwnerItems}"
SelectedValue="{Binding OwnerOrganizationId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
<TextBlock Grid.Row="2"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Заводской номер" />
<TextBox Grid.Row="2"
Grid.Column="1"
Margin="0,0,0,8"
Text="{Binding SerialNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="3"
Grid.Column="0"
Margin="0,0,12,8"
VerticalAlignment="Center"
Text="Инвентарный номер" />
<TextBox Grid.Row="3"
Grid.Column="1"
Margin="0,0,0,8"
Text="{Binding InventoryNumber, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="4"
Grid.Column="0"
Margin="0,0,12,0"
VerticalAlignment="Top"
Text="Примечание" />
<TextBox Grid.Row="4"
Grid.Column="1"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
Text="{Binding Notes, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<DockPanel Grid.Row="1"
Margin="0,12,0,0">
<TextBlock DockPanel.Dock="Left"
VerticalAlignment="Center"
Foreground="Firebrick"
Text="{Binding ValidationMessage}" />
<StackPanel DockPanel.Dock="Right"
Orientation="Horizontal">
<Button Width="100"
Margin="0,0,8,0"
IsDefault="True"
Command="{Binding ConfirmCommand}"
Content="Сохранить" />
<Button Width="90"
Command="{Binding CancelCommand}"
Content="Отмена" />
</StackPanel>
</DockPanel>
</Grid>
</Window>

View File

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

View File

@@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class EkzEditWindowViewModel : ObservableObject
{
private readonly IReadOnlyList<EkzDirectoryItem> _existingItems;
private string _inventoryNumber;
private string _notes;
private int _ownerOrganizationId;
private string _serialNumber;
private int _typeSizeId;
private string _validationMessage;
public EkzEditWindowViewModel(EkzDirectoryItem seed, bool isNew, IReadOnlyList<EkzDirectoryItem> existingItems, EkzDirectoryService service)
{
var source = seed ?? new EkzDirectoryItem();
_existingItems = existingItems ?? Array.Empty<EkzDirectoryItem>();
Id = source.Id;
IsNew = isNew;
TypeSizeItems = service.LoadTypeSizeReferences();
OwnerItems = service.LoadFrpdReferences();
TypeSizeId = source.TypeSizeId;
OwnerOrganizationId = source.OwnerOrganizationId;
SerialNumber = source.SerialNumber ?? string.Empty;
InventoryNumber = source.InventoryNumber ?? string.Empty;
Notes = source.Notes ?? string.Empty;
ConfirmCommand = new RelayCommand(Confirm);
CancelCommand = new RelayCommand(Cancel);
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public int Id { get; private set; }
public bool IsNew { get; private set; }
public string InventoryNumber
{
get { return _inventoryNumber; }
set { SetProperty(ref _inventoryNumber, value); }
}
public string Notes
{
get { return _notes; }
set { SetProperty(ref _notes, value); }
}
public IReadOnlyList<DirectoryLookupItem> OwnerItems { get; private set; }
public int OwnerOrganizationId
{
get { return _ownerOrganizationId; }
set { SetProperty(ref _ownerOrganizationId, value); }
}
public string SerialNumber
{
get { return _serialNumber; }
set { SetProperty(ref _serialNumber, value); }
}
public string Title
{
get { return IsNew ? "Новый экземпляр" : "Редактирование экземпляра"; }
}
public IReadOnlyList<DirectoryLookupItem> TypeSizeItems { get; private set; }
public int TypeSizeId
{
get { return _typeSizeId; }
set { SetProperty(ref _typeSizeId, value); }
}
public string ValidationMessage
{
get { return _validationMessage; }
private set { SetProperty(ref _validationMessage, value); }
}
public EkzDirectoryItem ToResult()
{
return new EkzDirectoryItem
{
Id = Id,
TypeSizeId = TypeSizeId,
OwnerOrganizationId = OwnerOrganizationId,
SerialNumber = Normalize(SerialNumber),
InventoryNumber = Normalize(InventoryNumber),
Notes = Normalize(Notes)
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private void Confirm(object parameter)
{
var serialNumber = Normalize(SerialNumber);
var inventoryNumber = Normalize(InventoryNumber);
var notes = Normalize(Notes);
if (TypeSizeId <= 0)
{
ValidationMessage = "Укажите типоразмер СИ.";
return;
}
if (OwnerOrganizationId <= 0)
{
ValidationMessage = "Укажите организацию-владельца.";
return;
}
if (string.IsNullOrWhiteSpace(serialNumber))
{
ValidationMessage = "Укажите заводской номер.";
return;
}
if (serialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
ValidationMessage = string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength);
return;
}
if (!string.IsNullOrWhiteSpace(inventoryNumber) && inventoryNumber.Length > EkzDirectoryRules.InventoryNumberMaxLength)
{
ValidationMessage = string.Format("Инвентарный номер не должен превышать {0} символов.", EkzDirectoryRules.InventoryNumberMaxLength);
return;
}
if (!string.IsNullOrWhiteSpace(notes) && notes.Length > EkzDirectoryRules.NotesMaxLength)
{
ValidationMessage = string.Format("Примечание не должно превышать {0} символов.", EkzDirectoryRules.NotesMaxLength);
return;
}
var duplicate = _existingItems.FirstOrDefault(delegate(EkzDirectoryItem item)
{
return item != null
&& item.Id != Id
&& item.TypeSizeId == TypeSizeId
&& item.OwnerOrganizationId == OwnerOrganizationId
&& string.Equals(item.SerialNumber ?? string.Empty, serialNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
});
if (duplicate != null)
{
ValidationMessage = "Экземпляр с таким типоразмером, владельцем и заводским номером уже существует.";
return;
}
ValidationMessage = string.Empty;
RaiseCloseRequested(true);
}
private static string Normalize(string value)
{
return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
}
}

View File

@@ -44,11 +44,42 @@
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" <MenuItem Header="Добавить"
Command="{Binding AddFrpdCommand}" /> Command="{Binding AddFrpdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" <MenuItem Header="Изменить"
Command="{Binding EditFrpdCommand}" /> Command="{Binding EditFrpdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeleteFrpdCommand}" /> Command="{Binding DeleteFrpdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>
@@ -76,11 +107,42 @@
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" <MenuItem Header="Добавить"
Command="{Binding AddFrpdvdCommand}" /> Command="{Binding AddFrpdvdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" <MenuItem Header="Изменить"
Command="{Binding EditFrpdvdCommand}" /> Command="{Binding EditFrpdvdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeleteFrpdvdCommand}" /> Command="{Binding DeleteFrpdvdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>

View File

@@ -37,6 +37,8 @@
Click="SpoiDirectoryMenuItem_Click" /> Click="SpoiDirectoryMenuItem_Click" />
<MenuItem Header="Наименования типов СИ" <MenuItem Header="Наименования типов СИ"
Click="SpnmtpDirectoryMenuItem_Click" /> Click="SpnmtpDirectoryMenuItem_Click" />
<MenuItem Header="Книги учета"
Click="AccountingBookDirectoryMenuItem_Click" />
</MenuItem> </MenuItem>
<MenuItem Header="Подразделения" <MenuItem Header="Подразделения"
Click="FrpdDirectoryMenuItem_Click" /> Click="FrpdDirectoryMenuItem_Click" />
@@ -44,6 +46,10 @@
Click="PrsnDirectoryMenuItem_Click" /> Click="PrsnDirectoryMenuItem_Click" />
<MenuItem Header="Типоразмеры" <MenuItem Header="Типоразмеры"
Click="TypeSizeDirectoryMenuItem_Click" /> Click="TypeSizeDirectoryMenuItem_Click" />
<MenuItem Header="Экземпляры"
Click="EkzDirectoryMenuItem_Click" />
<MenuItem Header="Отчеты"
Click="VerificationReportsMenuItem_Click" />
</Menu> </Menu>
<Grid Grid.Row="1"> <Grid Grid.Row="1">
@@ -93,12 +99,44 @@
<ListBox.ContextMenu> <ListBox.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" <MenuItem Header="Добавить"
Command="{Binding AddDocumentCommand}" /> Command="{Binding AddDocumentCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Распечатать" <MenuItem Header="Распечатать"
Command="{Binding PrintDocumentCommand}" /> Command="{Binding PrintDocumentCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="1.5" Width="8" Height="4" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="5" Width="12" Height="5" RadiusX="1.5" RadiusY="1.5" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="4" Canvas.Top="8.5" Width="8" Height="5" Fill="#FFF9FCFE" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator/> <Separator/>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeleteDocumentCommand}" /> Command="{Binding DeleteDocumentCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</ListBox.ContextMenu> </ListBox.ContextMenu>
<ListBox.ItemContainerStyle> <ListBox.ItemContainerStyle>
@@ -262,6 +300,7 @@
HorizontalAlignment="Right"> HorizontalAlignment="Right">
<Button Width="120" <Button Width="120"
Margin="0,0,0,8" Margin="0,0,0,8"
Style="{StaticResource PrimaryActionButtonStyle}"
IsEnabled="{Binding IsDocumentHeaderEditable}" IsEnabled="{Binding IsDocumentHeaderEditable}"
Command="{Binding SaveDocumentHeaderCommand}" Command="{Binding SaveDocumentHeaderCommand}"
Content="Сохранить" /> Content="Сохранить" />
@@ -304,7 +343,28 @@
<GroupBox Grid.Row="1" Header="Группы приборов выбранного документа"> <GroupBox Grid.Row="1" Header="Группы приборов выбранного документа">
<Grid Margin="8"> <Grid Margin="8">
<DataGrid ItemsSource="{Binding DocumentGroupSummaries}" <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="290" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding GroupFilterText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Column="1"
Margin="12,0,0,0"
VerticalAlignment="Center"
Foreground="DimGray"
Text="Поиск по наименованию, типу, диапазону, характеристикам, госреестру или зав. №" />
</Grid>
<DataGrid Grid.Row="1"
ItemsSource="{Binding DocumentGroupsView}"
SelectedItem="{Binding SelectedDocumentGroup, Mode=TwoWay}" SelectedItem="{Binding SelectedDocumentGroup, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"
@@ -313,12 +373,49 @@
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить по заводским номерам" <MenuItem Header="Добавить по заводским номерам"
Command="{Binding OpenInstrumentPickerCommand}" /> Command="{Binding OpenInstrumentPickerCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="2" Canvas.Top="3" Width="8" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="7" Width="8" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="11" Width="6" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Ellipse Canvas.Left="10.5" Canvas.Top="6" Width="4" Height="4" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="12" Canvas.Top="4.5" Width="1" Height="7" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="9.5" Canvas.Top="7" Width="6" Height="1" Fill="{StaticResource AppMenuIconSuccessBrush}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Добавить по типу" <MenuItem Header="Добавить по типу"
Command="{Binding OpenInstrumentTypePickerCommand}" /> Command="{Binding OpenInstrumentTypePickerCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="2" Canvas.Top="3" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="9" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="8" Canvas.Top="6" Width="5" Height="5" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="12" Canvas.Top="1.5" Width="1.5" Height="5" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Rectangle Canvas.Left="10.25" Canvas.Top="3.25" Width="5" Height="1.5" Fill="{StaticResource AppMenuIconSuccessBrush}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator/> <Separator/>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeleteSelectedGroupsCommand}" /> Command="{Binding DeleteSelectedGroupsCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>
@@ -338,12 +435,19 @@
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTextColumn Header="Наименование"
Width="220"
Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип" <DataGridTextColumn Header="Тип"
Width="180" Width="160"
Binding="{Binding InstrumentType}" /> Binding="{Binding InstrumentType}" />
<DataGridTextColumn Header="Диапазон" <DataGridTextColumn Header="Диапазон"
Width="170" Width="160"
Binding="{Binding RangeText}" /> Binding="{Binding RangeText}" />
<DataGridTextColumn Header="Характеристики"
Width="160"
Binding="{Binding AccuracyText}" />
<DataGridTextColumn Header="Госреестр" <DataGridTextColumn Header="Госреестр"
Width="120" Width="120"
Binding="{Binding RegistryNumber}" /> Binding="{Binding RegistryNumber}" />
@@ -405,24 +509,83 @@
SelectedItem="{Binding SelectedDocumentLine, Mode=TwoWay}" SelectedItem="{Binding SelectedDocumentLine, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"
IsReadOnly="True" IsReadOnly="{Binding IsDocumentLinesReadOnly}"
HeadersVisibility="Column"> HeadersVisibility="Column">
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Клонировать поверку в выбранные строки" <MenuItem Header="Клонировать поверку в выбранные строки"
Command="{Binding CloneLineVerificationCommand}" /> Command="{Binding CloneLineVerificationCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="3" Canvas.Top="4" Width="7" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="2" Width="7" Height="8" RadiusX="1" RadiusY="1" Fill="#FFEAF3FB" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Распечатать документ о поверке" <MenuItem Header="Распечатать документ о поверке"
Command="{Binding PrintVerificationDocumentCommand}" /> Command="{Binding PrintVerificationDocumentCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="1.5" Width="8" Height="4" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="2" Canvas.Top="5" Width="12" Height="5" RadiusX="1.5" RadiusY="1.5" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Canvas.Left="4" Canvas.Top="8.5" Width="8" Height="5" Fill="#FFF9FCFE" Stroke="{StaticResource AppMenuIconAccentBrush}" StrokeThickness="1" />
<Ellipse Canvas.Left="10.5" Canvas.Top="9.5" Width="4" Height="4" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Path Fill="White" Data="M12.1,10.4 L12.9,11.2 L14.2,9.6 L14.8,10.1 L12.9,12.4 L11.5,11 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator/> <Separator/>
<MenuItem Header="Годен" <MenuItem Header="Годен"
Command="{Binding MarkLinePassedCommand}" /> Command="{Binding MarkLinePassedCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconSuccessBrush}" />
<Path Fill="White" Data="M5.1,8.2 L7.2,10.3 L11.6,5.6 L12.8,6.6 L7.3,12.3 L3.9,8.9 Z" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Забракован" <MenuItem Header="Забракован"
Command="{Binding MarkLineRejectedCommand}" /> Command="{Binding MarkLineRejectedCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Path Stroke="White" StrokeThickness="1.8" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Data="M5.1,5.1 L10.9,10.9 M10.9,5.1 L5.1,10.9" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator/> <Separator/>
<MenuItem Header="Отменить проверку" <MenuItem Header="Отменить проверку"
Command="{Binding ResetLineVerificationCommand}" /> Command="{Binding ResetLineVerificationCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconWarningBrush}" Data="M7.8,2.2 C10.8,2.2 13.2,4.6 13.2,7.6 C13.2,10.6 10.8,13 7.8,13 C5.5,13 3.6,11.6 2.8,9.5 L4.5,9.5 C5.2,10.8 6.4,11.5 7.8,11.5 C10,11.5 11.7,9.8 11.7,7.6 C11.7,5.4 10,3.7 7.8,3.7 C6.5,3.7 5.3,4.3 4.6,5.4 L6.7,5.4 L3.8,8.2 L1.1,5.4 L3.1,5.4 C4,3.4 5.8,2.2 7.8,2.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeleteSelectedLinesCommand}" /> Command="{Binding DeleteSelectedLinesCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>
@@ -446,22 +609,31 @@
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTextColumn Header="Зав. №" <DataGridTextColumn Header="Зав. №"
Width="120" Width="120"
IsReadOnly="True"
Binding="{Binding SerialNumber}" /> Binding="{Binding SerialNumber}" />
<DataGridTextColumn Header="Дата поверки" <DataGridTextColumn Header="Дата поверки"
Width="110" Width="110"
IsReadOnly="True"
Binding="{Binding VerificationDateDisplay}" /> Binding="{Binding VerificationDateDisplay}" />
<DataGridTextColumn Header="Поверитель" <DataGridTextColumn Header="Поверитель"
Width="180" Width="180"
IsReadOnly="True"
Binding="{Binding VerifierName}" /> Binding="{Binding VerifierName}" />
<DataGridTextColumn Header="Номер наклейки" <DataGridTextColumn Header="Номер наклейки"
Width="150" Width="150"
IsReadOnly="True"
Binding="{Binding StickerNumber}" /> Binding="{Binding StickerNumber}" />
<DataGridTextColumn Header="Результат поверки" <DataGridTextColumn Header="Результат поверки"
Width="140" Width="140"
IsReadOnly="True"
Binding="{Binding ResultText}" /> Binding="{Binding ResultText}" />
<DataGridTextColumn Header="Номер документа по поверке" <DataGridTextColumn Header="Номер документа по поверке"
Width="240" Width="240"
IsReadOnly="True"
Binding="{Binding VerificationDocumentDisplay}" /> Binding="{Binding VerificationDocumentDisplay}" />
<DataGridTextColumn Header="Комплектность"
Width="240"
Binding="{Binding Completeness, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</Grid> </Grid>

View File

@@ -6,12 +6,14 @@ namespace XLAB2
{ {
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
private readonly DocumentNumberDirectoryService _documentNumberDirectoryService;
private readonly MainWindowViewModel _viewModel; private readonly MainWindowViewModel _viewModel;
internal MainWindow(PsvDataService service) internal MainWindow(PsvDataService service, DocumentNumberDirectoryService documentNumberDirectoryService)
{ {
InitializeComponent(); InitializeComponent();
_viewModel = new MainWindowViewModel(service, new DialogService(this)); _documentNumberDirectoryService = documentNumberDirectoryService;
_viewModel = new MainWindowViewModel(service, new DialogService(this, documentNumberDirectoryService));
DataContext = _viewModel; DataContext = _viewModel;
} }
@@ -79,6 +81,13 @@ namespace XLAB2
window.ShowDialog(); window.ShowDialog();
} }
private void AccountingBookDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new AccountingBookDirectoryWindow(_documentNumberDirectoryService);
window.Owner = this;
window.ShowDialog();
}
private void TypeSizeDirectoryMenuItem_Click(object sender, RoutedEventArgs e) private void TypeSizeDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{ {
var window = new TypeSizeDirectoryWindow(); var window = new TypeSizeDirectoryWindow();
@@ -86,6 +95,20 @@ namespace XLAB2
window.ShowDialog(); window.ShowDialog();
} }
private void EkzDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new EkzDirectoryWindow();
window.Owner = this;
window.ShowDialog();
}
private void VerificationReportsMenuItem_Click(object sender, RoutedEventArgs e)
{
var window = new VerificationReportsWindow();
window.Owner = this;
window.ShowDialog();
}
private void SpoiDirectoryMenuItem_Click(object sender, RoutedEventArgs e) private void SpoiDirectoryMenuItem_Click(object sender, RoutedEventArgs e)
{ {
var window = new SpoiDirectoryWindow(); var window = new SpoiDirectoryWindow();

View File

@@ -20,6 +20,7 @@ namespace XLAB2
private string _documentNumberEditor; private string _documentNumberEditor;
private string _documentStatusText; private string _documentStatusText;
private string _detailTableCountText; private string _detailTableCountText;
private string _groupFilterText;
private string _groupDetailFilterText; private string _groupDetailFilterText;
private string _headerDepartmentName; private string _headerDepartmentName;
private int _headerInstrumentCount; private int _headerInstrumentCount;
@@ -50,6 +51,9 @@ namespace XLAB2
DocumentsView = CollectionViewSource.GetDefaultView(Documents); DocumentsView = CollectionViewSource.GetDefaultView(Documents);
DocumentsView.Filter = FilterDocuments; DocumentsView.Filter = FilterDocuments;
DocumentGroupsView = CollectionViewSource.GetDefaultView(DocumentGroupSummaries);
DocumentGroupsView.Filter = FilterDocumentGroups;
DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines); DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines);
DocumentLinesView.Filter = FilterDocumentLines; DocumentLinesView.Filter = FilterDocumentLines;
@@ -132,6 +136,8 @@ namespace XLAB2
public ObservableCollection<PsvDocumentGroupSummary> DocumentGroupSummaries { get; private set; } public ObservableCollection<PsvDocumentGroupSummary> DocumentGroupSummaries { get; private set; }
public ICollectionView DocumentGroupsView { get; private set; }
public string DocumentStatusText public string DocumentStatusText
{ {
get { return _documentStatusText; } get { return _documentStatusText; }
@@ -154,6 +160,18 @@ namespace XLAB2
public ICommand DeleteSelectedGroupsCommand { get; private set; } public ICommand DeleteSelectedGroupsCommand { get; private set; }
public string GroupFilterText
{
get { return _groupFilterText; }
set
{
if (SetProperty(ref _groupFilterText, value))
{
RefreshDocumentGroupsView();
}
}
}
public string GroupDetailFilterText public string GroupDetailFilterText
{ {
get { return _groupDetailFilterText; } get { return _groupDetailFilterText; }
@@ -188,6 +206,11 @@ namespace XLAB2
} }
} }
public bool IsDocumentLinesReadOnly
{
get { return !CanModifySelectedDocument(); }
}
public DateTime? HeaderIssuedOn public DateTime? HeaderIssuedOn
{ {
get { return _headerIssuedOn; } get { return _headerIssuedOn; }
@@ -210,6 +233,7 @@ namespace XLAB2
RaiseCommandStates(); RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsCustomerEditable");
OnPropertyChanged("IsDocumentHeaderEditable"); OnPropertyChanged("IsDocumentHeaderEditable");
OnPropertyChanged("IsDocumentLinesReadOnly");
} }
} }
} }
@@ -275,6 +299,7 @@ namespace XLAB2
RaiseCommandStates(); RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsCustomerEditable");
OnPropertyChanged("IsDocumentHeaderEditable"); OnPropertyChanged("IsDocumentHeaderEditable");
OnPropertyChanged("IsDocumentLinesReadOnly");
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
} }
} }
@@ -466,6 +491,40 @@ namespace XLAB2
return document != null && document.IssuedOn.HasValue; return document != null && document.IssuedOn.HasValue;
} }
private static string BuildSerialNumbersText(IEnumerable<PsvDocumentLine> lines)
{
var serialNumbers = (lines ?? Enumerable.Empty<PsvDocumentLine>())
.Select(delegate(PsvDocumentLine line)
{
return line == null || string.IsNullOrWhiteSpace(line.SerialNumber)
? null
: line.SerialNumber.Trim();
})
.Where(delegate(string serialNumber) { return !string.IsNullOrWhiteSpace(serialNumber); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string serialNumber) { return serialNumber; }, StringComparer.OrdinalIgnoreCase)
.ToList();
return serialNumbers.Count == 0 ? string.Empty : string.Join(", ", serialNumbers.ToArray());
}
private static string BuildInstrumentNamesText(IEnumerable<PsvDocumentLine> lines)
{
var instrumentNames = (lines ?? Enumerable.Empty<PsvDocumentLine>())
.Select(delegate(PsvDocumentLine line)
{
return line == null || string.IsNullOrWhiteSpace(line.InstrumentName)
? null
: line.InstrumentName.Trim();
})
.Where(delegate(string instrumentName) { return !string.IsNullOrWhiteSpace(instrumentName); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string instrumentName) { return instrumentName; }, StringComparer.OrdinalIgnoreCase)
.ToList();
return instrumentNames.Count == 0 ? string.Empty : string.Join("; ", instrumentNames.ToArray());
}
private static bool HasVerificationData(PsvDocumentLine line) private static bool HasVerificationData(PsvDocumentLine line)
{ {
return line != null return line != null
@@ -901,14 +960,14 @@ namespace XLAB2
foreach (var pendingLinesByDocument in _pendingLinesByDocumentKey) foreach (var pendingLinesByDocument in _pendingLinesByDocumentKey)
{ {
if (string.Equals(pendingLinesByDocument.Key, currentDocument.DocumentKey, StringComparison.OrdinalIgnoreCase)) if (MatchesPendingLinesStorageKey(currentDocument, pendingLinesByDocument.Key))
{ {
continue; continue;
} }
var otherDocument = Documents.FirstOrDefault(delegate(PsvDocumentSummary document) var otherDocument = Documents.FirstOrDefault(delegate(PsvDocumentSummary document)
{ {
return string.Equals(document.DocumentKey, pendingLinesByDocument.Key, StringComparison.OrdinalIgnoreCase); return MatchesPendingLinesStorageKey(document, pendingLinesByDocument.Key);
}); });
if (otherDocument == null if (otherDocument == null
@@ -1336,7 +1395,7 @@ namespace XLAB2
RunBusyOperation(async delegate RunBusyOperation(async delegate
{ {
var result = await _service.DeleteDocumentAsync(selectedDocument.DocumentNumber); var result = await _service.DeleteDocumentAsync(selectedDocument.DocumentNumber);
_pendingLinesByDocumentKey.Remove(selectedDocument.DocumentKey); _pendingLinesByDocumentKey.Remove(GetPendingLinesStorageKey(selectedDocument));
await RefreshDocumentsCoreAsync(null, null); await RefreshDocumentsCoreAsync(null, null);
_dialogService.ShowInfo( _dialogService.ShowInfo(
string.Format( string.Format(
@@ -1350,7 +1409,7 @@ namespace XLAB2
private void DeleteDraftDocument(PsvDocumentSummary draft) private void DeleteDraftDocument(PsvDocumentSummary draft)
{ {
_draftDocuments.RemoveAll(delegate(PsvDocumentSummary item) { return item.DocumentKey == draft.DocumentKey; }); _draftDocuments.RemoveAll(delegate(PsvDocumentSummary item) { return item.DocumentKey == draft.DocumentKey; });
_pendingLinesByDocumentKey.Remove(draft.DocumentKey); _pendingLinesByDocumentKey.Remove(GetPendingLinesStorageKey(draft));
Documents.Remove(draft); Documents.Remove(draft);
DocumentsView.Refresh(); DocumentsView.Refresh();
SelectedDocument = Documents.Count > 0 ? Documents[0] : null; SelectedDocument = Documents.Count > 0 ? Documents[0] : null;
@@ -1662,6 +1721,7 @@ namespace XLAB2
document.PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }); document.PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; });
document.FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }); document.FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; });
document.IssuedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IssuedOn.HasValue; }); document.IssuedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IssuedOn.HasValue; });
document.SerialNumbersText = BuildSerialNumbersText(materializedLines);
} }
private async Task ExecuteBusyOperationAsync(Func<Task> operation) private async Task ExecuteBusyOperationAsync(Func<Task> operation)
@@ -1701,7 +1761,8 @@ namespace XLAB2
if (!string.IsNullOrWhiteSpace(DocumentFilterText) if (!string.IsNullOrWhiteSpace(DocumentFilterText)
&& !Contains(document.DocumentNumber, DocumentFilterText) && !Contains(document.DocumentNumber, DocumentFilterText)
&& !Contains(document.CustomerName, DocumentFilterText)) && !Contains(document.CustomerName, DocumentFilterText)
&& !Contains(document.SerialNumbersText, DocumentFilterText))
{ {
return false; return false;
} }
@@ -1709,6 +1770,27 @@ namespace XLAB2
return true; return true;
} }
private bool FilterDocumentGroups(object item)
{
var group = item as PsvDocumentGroupSummary;
if (group == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(GroupFilterText))
{
return true;
}
return Contains(group.InstrumentName, GroupFilterText)
|| Contains(group.InstrumentType, GroupFilterText)
|| Contains(group.RangeText, GroupFilterText)
|| Contains(group.AccuracyText, GroupFilterText)
|| Contains(group.RegistryNumber, GroupFilterText)
|| Contains(group.SerialNumbersText, GroupFilterText);
}
private string BuildDocumentStatusText(int count) private string BuildDocumentStatusText(int count)
{ {
if (ShowClosedDocuments) if (ShowClosedDocuments)
@@ -1753,12 +1835,33 @@ namespace XLAB2
}); });
} }
private PsvDocumentGroupSummary FindMatchingVisibleGroup(PsvDocumentGroupSummary group)
{
if (group == null)
{
return null;
}
return GetVisibleDocumentGroups().FirstOrDefault(delegate(PsvDocumentGroupSummary current)
{
return AreSameGroup(current, group);
});
}
private List<PsvDocumentGroupSummary> GetVisibleDocumentGroups()
{
return DocumentGroupsView == null
? new List<PsvDocumentGroupSummary>()
: DocumentGroupsView.Cast<object>().OfType<PsvDocumentGroupSummary>().ToList();
}
private static bool AreSameGroup(PsvDocumentGroupSummary left, PsvDocumentGroupSummary right) private static bool AreSameGroup(PsvDocumentGroupSummary left, PsvDocumentGroupSummary right)
{ {
return left != null return left != null
&& right != null && right != null
&& string.Equals(left.InstrumentType ?? string.Empty, right.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(left.InstrumentType ?? string.Empty, right.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(left.RangeText ?? string.Empty, right.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(left.RangeText ?? string.Empty, right.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(left.AccuracyText ?? string.Empty, right.AccuracyText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(left.RegistryNumber ?? string.Empty, right.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase); && string.Equals(left.RegistryNumber ?? string.Empty, right.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
} }
@@ -1784,12 +1887,41 @@ namespace XLAB2
return new List<PsvDocumentLine>(); return new List<PsvDocumentLine>();
} }
var pendingKey = GetPendingLinesStorageKey(document);
if (string.IsNullOrWhiteSpace(pendingKey))
{
return new List<PsvDocumentLine>();
}
List<PsvDocumentLine> lines; List<PsvDocumentLine> lines;
return _pendingLinesByDocumentKey.TryGetValue(document.DocumentKey, out lines) return _pendingLinesByDocumentKey.TryGetValue(pendingKey, out lines)
? lines ? lines
: new List<PsvDocumentLine>(); : new List<PsvDocumentLine>();
} }
private static string GetPendingLinesStorageKey(PsvDocumentSummary document)
{
if (document == null)
{
return string.Empty;
}
if (!document.IsDraft && !string.IsNullOrWhiteSpace(document.DocumentNumber))
{
return document.DocumentNumber.Trim();
}
return string.IsNullOrWhiteSpace(document.DocumentKey)
? string.Empty
: document.DocumentKey.Trim();
}
private static bool MatchesPendingLinesStorageKey(PsvDocumentSummary document, string pendingKey)
{
return !string.IsNullOrWhiteSpace(pendingKey)
&& string.Equals(GetPendingLinesStorageKey(document), pendingKey, StringComparison.OrdinalIgnoreCase);
}
private List<PsvDocumentLine> MergeDocumentLinesForPrint(PsvDocumentSummary document, IEnumerable<PsvDocumentLine> persistedLines) private List<PsvDocumentLine> MergeDocumentLinesForPrint(PsvDocumentSummary document, IEnumerable<PsvDocumentLine> persistedLines)
{ {
var mergedLines = new List<PsvDocumentLine>(); var mergedLines = new List<PsvDocumentLine>();
@@ -2002,11 +2134,17 @@ namespace XLAB2
return; return;
} }
var pendingKey = GetPendingLinesStorageKey(SelectedDocument);
if (string.IsNullOrWhiteSpace(pendingKey))
{
return;
}
List<PsvDocumentLine> pendingLines; List<PsvDocumentLine> pendingLines;
if (!_pendingLinesByDocumentKey.TryGetValue(SelectedDocument.DocumentKey, out pendingLines)) if (!_pendingLinesByDocumentKey.TryGetValue(pendingKey, out pendingLines))
{ {
pendingLines = new List<PsvDocumentLine>(); pendingLines = new List<PsvDocumentLine>();
_pendingLinesByDocumentKey[SelectedDocument.DocumentKey] = pendingLines; _pendingLinesByDocumentKey[pendingKey] = pendingLines;
} }
var candidateLines = selectedItems var candidateLines = selectedItems
@@ -2048,12 +2186,6 @@ namespace XLAB2
continue; continue;
} }
if (!item.HasTemplate)
{
skippedWithoutTemplateCount++;
continue;
}
var duplicateKey = PsvDocumentLine.BuildDuplicateKey(item.InstrumentType, item.RangeText, item.RegistryNumber, item.SerialNumber); var duplicateKey = PsvDocumentLine.BuildDuplicateKey(item.InstrumentType, item.RangeText, item.RegistryNumber, item.SerialNumber);
if (duplicateKeys.Contains(duplicateKey)) if (duplicateKeys.Contains(duplicateKey))
{ {
@@ -2068,7 +2200,7 @@ namespace XLAB2
if (SelectedDocument.IsDraft) if (SelectedDocument.IsDraft)
{ {
SelectedDocument.ItemCount = pendingLines.Count; UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines);
} }
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
@@ -2119,31 +2251,66 @@ namespace XLAB2
return; return;
} }
List<PsvDocumentLine> pendingLines; var pendingKey = GetPendingLinesStorageKey(SelectedDocument);
if (!_pendingLinesByDocumentKey.TryGetValue(SelectedDocument.DocumentKey, out pendingLines)) if (string.IsNullOrWhiteSpace(pendingKey))
{ {
pendingLines = new List<PsvDocumentLine>();
_pendingLinesByDocumentKey[SelectedDocument.DocumentKey] = pendingLines;
}
var serialNumber = string.IsNullOrWhiteSpace(result.SerialNumber) ? string.Empty : result.SerialNumber.Trim();
if (string.IsNullOrWhiteSpace(serialNumber))
{
_dialogService.ShowWarning("Введите заводской номер.");
return; return;
} }
if (!result.TypeItem.HasTemplate) List<PsvDocumentLine> pendingLines;
if (!_pendingLinesByDocumentKey.TryGetValue(pendingKey, out pendingLines))
{
pendingLines = new List<PsvDocumentLine>();
_pendingLinesByDocumentKey[pendingKey] = pendingLines;
}
var serialNumbers = (result.SerialNumbers ?? Array.Empty<string>())
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Select(delegate(string value) { return value.Trim(); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (serialNumbers.Count == 0)
{
_dialogService.ShowWarning("Введите хотя бы один заводской номер.");
return;
}
var blockOnMissingEkzmkTemplate = false;
if (blockOnMissingEkzmkTemplate && !result.TypeItem.HasTemplate)
{ {
_dialogService.ShowWarning("Выбранный тип нельзя добавить: для него не найден шаблон EKZMK, период из TPRMCP или регистрационный период TIPS."); _dialogService.ShowWarning("Выбранный тип нельзя добавить: для него не найден шаблон EKZMK, период из TPRMCP или регистрационный период TIPS.");
return; return;
} }
var candidateLine = CreatePendingTypeLine(result.TypeItem, serialNumber); var validSerialNumbers = new List<string>();
var skippedInvalidLengthCount = 0;
foreach (var serialNumber in serialNumbers)
{
if (serialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
skippedInvalidLengthCount++;
continue;
}
validSerialNumbers.Add(serialNumber);
}
if (validSerialNumbers.Count == 0)
{
_dialogService.ShowWarning(string.Format("Каждый заводской номер должен содержать не более {0} символов.", EkzDirectoryRules.SerialNumberMaxLength));
return;
}
var candidateLines = validSerialNumbers
.Select(delegate(string serialNumber) { return CreatePendingTypeLine(result.TypeItem, serialNumber); })
.ToList();
List<OpenDocumentConflictInfo> openDocumentConflicts; List<OpenDocumentConflictInfo> openDocumentConflicts;
try try
{ {
openDocumentConflicts = FindOpenDocumentConflicts(new[] { candidateLine }); openDocumentConflicts = FindOpenDocumentConflicts(candidateLines);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -2151,36 +2318,88 @@ namespace XLAB2
return; return;
} }
if (openDocumentConflicts.Count > 0) var openConflictKeys = new HashSet<string>(
openDocumentConflicts.Select(delegate(OpenDocumentConflictInfo conflict) { return conflict.OpenDocumentConflictKey; }),
StringComparer.OrdinalIgnoreCase);
var duplicateKeys = new HashSet<string>(DocumentLines.Select(delegate(PsvDocumentLine line) { return line.DuplicateKey; }), StringComparer.OrdinalIgnoreCase);
var addedCount = 0;
var skippedDuplicateCount = 0;
var skippedOpenDocumentCount = 0;
foreach (var serialNumber in validSerialNumbers)
{ {
_dialogService.ShowWarning(BuildOpenDocumentConflictMessage(openDocumentConflicts)); if (openConflictKeys.Contains(PsvDocumentLine.BuildOpenDocumentConflictKey(result.TypeItem.TypeSizeId, serialNumber)))
return; {
skippedOpenDocumentCount++;
continue;
}
var duplicateKey = PsvDocumentLine.BuildDuplicateKey(
result.TypeItem.InstrumentType,
result.TypeItem.RangeText,
result.TypeItem.RegistryNumber,
serialNumber);
if (duplicateKeys.Contains(duplicateKey))
{
skippedDuplicateCount++;
continue;
}
pendingLines.Add(CreatePendingTypeLine(result.TypeItem, serialNumber));
duplicateKeys.Add(duplicateKey);
addedCount++;
} }
var duplicateKey = PsvDocumentLine.BuildDuplicateKey( if (addedCount == 0 && skippedDuplicateCount > 0 && skippedOpenDocumentCount == 0 && skippedInvalidLengthCount == 0)
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("Такие приборы уже есть в ПСВ.");
}))
{
_dialogService.ShowWarning("Такой прибор уже есть в ПСВ.");
return; return;
} }
pendingLines.Add(candidateLine);
if (SelectedDocument.IsDraft) if (SelectedDocument.IsDraft)
{ {
SelectedDocument.ItemCount = pendingLines.Count; UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines);
} }
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
_dialogService.ShowInfo("Прибор по типу добавлен в ПСВ.");
var messages = new List<string>();
if (addedCount > 0)
{
messages.Add(string.Format("Добавлено приборов по типу: {0}.", addedCount));
}
if (skippedDuplicateCount > 0)
{
messages.Add(string.Format("Исключено дублей: {0}.", skippedDuplicateCount));
}
if (skippedInvalidLengthCount > 0)
{
messages.Add(string.Format("Пропущено из-за длины зав. № более {0} символов: {1}.", EkzDirectoryRules.SerialNumberMaxLength, skippedInvalidLengthCount));
}
if (skippedOpenDocumentCount > 0)
{
messages.Add(string.Format("Пропущено из-за других открытых ПСВ: {0}.", skippedOpenDocumentCount));
messages.Add(BuildOpenDocumentConflictMessage(openDocumentConflicts));
}
if (messages.Count > 0)
{
var message = string.Join(" ", messages.ToArray());
if (addedCount == 0)
{
_dialogService.ShowWarning(message);
}
else
{
_dialogService.ShowInfo(message);
}
}
RaiseCommandStates(); RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable"); OnPropertyChanged("IsCustomerEditable");
} }
@@ -2196,7 +2415,8 @@ namespace XLAB2
} }
RebuildDocumentGroupSummaries(DocumentLines); RebuildDocumentGroupSummaries(DocumentLines);
SelectedDocumentGroup = FindMatchingGroup(previousGroup) ?? DocumentGroupSummaries.FirstOrDefault(); SelectedDocumentGroup = FindMatchingVisibleGroup(previousGroup)
?? GetVisibleDocumentGroups().FirstOrDefault();
SelectedDocumentLine = previousLine == null SelectedDocumentLine = previousLine == null
? null ? null
: DocumentLines.FirstOrDefault(delegate(PsvDocumentLine line) : DocumentLines.FirstOrDefault(delegate(PsvDocumentLine line)
@@ -2211,6 +2431,7 @@ namespace XLAB2
&& string.Equals(line.DuplicateKey, previousLine.DuplicateKey, StringComparison.OrdinalIgnoreCase); && string.Equals(line.DuplicateKey, previousLine.DuplicateKey, StringComparison.OrdinalIgnoreCase);
}); });
HeaderInstrumentCount = DocumentLines.Count; HeaderInstrumentCount = DocumentLines.Count;
RefreshDocumentGroupsView();
RefreshDocumentLinesView(); RefreshDocumentLinesView();
RaiseCommandStates(); RaiseCommandStates();
} }
@@ -2245,20 +2466,26 @@ namespace XLAB2
{ {
InstrumentType = line.InstrumentType ?? string.Empty, InstrumentType = line.InstrumentType ?? string.Empty,
RangeText = line.RangeText ?? string.Empty, RangeText = line.RangeText ?? string.Empty,
AccuracyText = line.AccuracyText ?? string.Empty,
RegistryNumber = line.RegistryNumber ?? string.Empty RegistryNumber = line.RegistryNumber ?? string.Empty
}) })
.OrderBy(group => group.Key.InstrumentType) .OrderBy(group => group.Key.InstrumentType)
.ThenBy(group => group.Key.RegistryNumber) .ThenBy(group => group.Key.RegistryNumber)
.ThenBy(group => group.Key.RangeText) .ThenBy(group => group.Key.RangeText)
.ThenBy(group => group.Key.AccuracyText)
.Select(group => new PsvDocumentGroupSummary .Select(group => new PsvDocumentGroupSummary
{ {
InstrumentName = BuildInstrumentNamesText(group),
InstrumentType = group.Key.InstrumentType, InstrumentType = group.Key.InstrumentType,
RangeText = group.Key.RangeText, RangeText = group.Key.RangeText,
AccuracyText = group.Key.AccuracyText,
RegistryNumber = group.Key.RegistryNumber, RegistryNumber = group.Key.RegistryNumber,
SerialNumbersText = BuildSerialNumbersText(group),
IsBatchSelected = checkedGroups.Any(delegate(PsvDocumentGroupSummary previous) IsBatchSelected = checkedGroups.Any(delegate(PsvDocumentGroupSummary previous)
{ {
return string.Equals(previous.InstrumentType ?? string.Empty, group.Key.InstrumentType, StringComparison.OrdinalIgnoreCase) return string.Equals(previous.InstrumentType ?? string.Empty, group.Key.InstrumentType, StringComparison.OrdinalIgnoreCase)
&& string.Equals(previous.RangeText ?? string.Empty, group.Key.RangeText, StringComparison.OrdinalIgnoreCase) && string.Equals(previous.RangeText ?? string.Empty, group.Key.RangeText, StringComparison.OrdinalIgnoreCase)
&& string.Equals(previous.AccuracyText ?? string.Empty, group.Key.AccuracyText, StringComparison.OrdinalIgnoreCase)
&& string.Equals(previous.RegistryNumber ?? string.Empty, group.Key.RegistryNumber, StringComparison.OrdinalIgnoreCase); && string.Equals(previous.RegistryNumber ?? string.Empty, group.Key.RegistryNumber, StringComparison.OrdinalIgnoreCase);
}), }),
InVerificationCount = group.Count(line => !line.IsPassed.HasValue), InVerificationCount = group.Count(line => !line.IsPassed.HasValue),
@@ -2274,6 +2501,36 @@ namespace XLAB2
} }
} }
private void RefreshDocumentGroupsView()
{
if (DocumentGroupsView != null)
{
DocumentGroupsView.Refresh();
}
var selectedVisibleGroup = FindMatchingVisibleGroup(SelectedDocumentGroup);
if (selectedVisibleGroup != null)
{
if (!ReferenceEquals(SelectedDocumentGroup, selectedVisibleGroup))
{
SelectedDocumentGroup = selectedVisibleGroup;
return;
}
RefreshDocumentLinesView();
return;
}
var firstVisibleGroup = GetVisibleDocumentGroups().FirstOrDefault();
if (!ReferenceEquals(SelectedDocumentGroup, firstVisibleGroup))
{
SelectedDocumentGroup = firstVisibleGroup;
return;
}
RefreshDocumentLinesView();
}
private void RefreshDocumentLinesView() private void RefreshDocumentLinesView()
{ {
DocumentLinesView.Refresh(); DocumentLinesView.Refresh();
@@ -2406,12 +2663,14 @@ namespace XLAB2
var currentDocumentNumber = selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber; var currentDocumentNumber = selectedDocument.IsDraft ? null : selectedDocument.DocumentNumber;
var documentKey = selectedDocument.DocumentKey; var documentKey = selectedDocument.DocumentKey;
var documentPendingKey = GetPendingLinesStorageKey(selectedDocument);
var wasDraft = selectedDocument.IsDraft; var wasDraft = selectedDocument.IsDraft;
var closingNow = !selectedDocument.IssuedOn.HasValue && request.IssuedOn.HasValue; var closingNow = !selectedDocument.IssuedOn.HasValue && request.IssuedOn.HasValue;
var printDocument = closingNow ? CreateSavedDocumentSummaryForPrint(selectedDocument, request) : null; var printDocument = closingNow ? CreateSavedDocumentSummaryForPrint(selectedDocument, request) : null;
var result = await Task.Run(delegate { return _service.SaveDocument(currentDocumentNumber, request, pendingLines); }); var documentLinesSnapshot = DocumentLines.ToList();
var result = await Task.Run(delegate { return _service.SaveDocument(currentDocumentNumber, request, documentLinesSnapshot); });
_pendingLinesByDocumentKey.Remove(documentKey); _pendingLinesByDocumentKey.Remove(documentPendingKey);
if (wasDraft) if (wasDraft)
{ {
_draftDocuments.RemoveAll(delegate(PsvDocumentSummary draft) { return draft.DocumentKey == documentKey; }); _draftDocuments.RemoveAll(delegate(PsvDocumentSummary draft) { return draft.DocumentKey == documentKey; });
@@ -2456,6 +2715,8 @@ namespace XLAB2
private void UpdateLineStatus() private void UpdateLineStatus()
{ {
var visibleGroupCount = GetVisibleDocumentGroups().Count;
if (SelectedDocument == null) if (SelectedDocument == null)
{ {
DetailTableCountText = "Приборов в таблице: 0."; DetailTableCountText = "Приборов в таблице: 0.";
@@ -2472,10 +2733,19 @@ namespace XLAB2
return; return;
} }
if (visibleGroupCount == 0)
{
DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = string.IsNullOrWhiteSpace(GroupFilterText)
? "Выберите группу."
: string.Format("Группы по фильтру \"{0}\" не найдены.", GroupFilterText.Trim());
return;
}
if (SelectedDocumentGroup == null) if (SelectedDocumentGroup == null)
{ {
DetailTableCountText = "Приборов в таблице: 0."; DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = string.Format("Групп: {0}. Выберите группу.", DocumentGroupSummaries.Count); LineStatusText = string.Format("Групп: {0}/{1}. Выберите группу.", visibleGroupCount, DocumentGroupSummaries.Count);
return; return;
} }
@@ -2489,7 +2759,8 @@ namespace XLAB2
DetailTableCountText = string.Format("Приборов в таблице: {0}.", filteredCount); DetailTableCountText = string.Format("Приборов в таблице: {0}.", filteredCount);
LineStatusText = string.Format( LineStatusText = string.Format(
"Групп: {0}. Приборов в выбранной группе: {1}. Отображено по фильтру: {2}. Не сохранено строк: {3}.", "Групп: {0}/{1}. Приборов в выбранной группе: {2}. Отображено по фильтру: {3}. Не сохранено строк: {4}.",
visibleGroupCount,
DocumentGroupSummaries.Count, DocumentGroupSummaries.Count,
groupLineCount, groupLineCount,
filteredCount, filteredCount,

BIN
XLAB2/OpenPsv.docx Normal file

Binary file not shown.

View File

@@ -45,11 +45,42 @@
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" <MenuItem Header="Добавить"
Command="{Binding AddPrsnCommand}" /> Command="{Binding AddPrsnCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" <MenuItem Header="Изменить"
Command="{Binding EditPrsnCommand}" /> Command="{Binding EditPrsnCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeletePrsnCommand}" /> Command="{Binding DeletePrsnCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>
@@ -82,11 +113,42 @@
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" <MenuItem Header="Добавить"
Command="{Binding AddPrfrCommand}" /> Command="{Binding AddPrfrCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" <MenuItem Header="Изменить"
Command="{Binding EditPrfrCommand}" /> Command="{Binding EditPrfrCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeletePrfrCommand}" /> Command="{Binding DeletePrfrCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>
@@ -127,11 +189,42 @@
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" <MenuItem Header="Добавить"
Command="{Binding AddPrfrvdCommand}" /> Command="{Binding AddPrfrvdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" <MenuItem Header="Изменить"
Command="{Binding EditPrfrvdCommand}" /> Command="{Binding EditPrfrvdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeletePrfrvdCommand}" /> Command="{Binding DeletePrfrvdCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>
@@ -158,11 +251,42 @@
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" <MenuItem Header="Добавить"
Command="{Binding AddPrdspvCommand}" /> Command="{Binding AddPrdspvCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" <MenuItem Header="Изменить"
Command="{Binding EditPrdspvCommand}" /> Command="{Binding EditPrdspvCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeletePrdspvCommand}" /> Command="{Binding DeletePrdspvCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>

View File

@@ -11,6 +11,8 @@ namespace XLAB2
{ {
internal sealed class PsvDataService internal sealed class PsvDataService
{ {
private const int EkzMkCompletenessMaxLength = 600;
public bool DocumentNumberExists(string documentNumber, string excludeDocumentNumber) public bool DocumentNumberExists(string documentNumber, string excludeDocumentNumber)
{ {
var normalizedNumber = NormalizeDocumentNumber(documentNumber); var normalizedNumber = NormalizeDocumentNumber(documentNumber);
@@ -86,7 +88,7 @@ ORDER BY fr.NMFRPD;";
return customers; return customers;
} }
public Task<IReadOnlyList<CustomerReference>> LoadCustomersAsync(CancellationToken cancellationToken = default) public async Task<IReadOnlyList<CustomerReference>> LoadCustomersAsync(CancellationToken cancellationToken = default)
{ {
const string sql = @" const string sql = @"
SELECT SELECT
@@ -98,7 +100,7 @@ WHERE z.IDFRPDV IS NOT NULL
GROUP BY z.IDFRPDV, fr.NMFRPD GROUP BY z.IDFRPDV, fr.NMFRPD
ORDER BY fr.NMFRPD;"; ORDER BY fr.NMFRPD;";
return SqlServerConnectionFactory.Current.QueryOpenConnectionAsync( return await SqlServerConnectionFactory.Current.QueryOpenConnectionAsync(
sql, sql,
delegate(SqlDataReader reader) delegate(SqlDataReader reader)
{ {
@@ -109,10 +111,7 @@ ORDER BY fr.NMFRPD;";
}; };
}, },
ConfigureCommandTimeout, ConfigureCommandTimeout,
cancellationToken).ContinueWith<IReadOnlyList<CustomerReference>>(delegate(Task<List<CustomerReference>> task) cancellationToken).ConfigureAwait(false);
{
return task.Result;
}, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
} }
public IReadOnlyList<PersonReference> LoadVerifiers() public IReadOnlyList<PersonReference> LoadVerifiers()
@@ -149,7 +148,7 @@ ORDER BY p.PRFIO;";
return verifiers; return verifiers;
} }
public Task<IReadOnlyList<PersonReference>> LoadVerifiersAsync(CancellationToken cancellationToken = default) public async Task<IReadOnlyList<PersonReference>> LoadVerifiersAsync(CancellationToken cancellationToken = default)
{ {
const string sql = @" const string sql = @"
SELECT SELECT
@@ -159,7 +158,7 @@ FROM dbo.PRSN p
WHERE NULLIF(LTRIM(RTRIM(p.PRFIO)), N'') IS NOT NULL WHERE NULLIF(LTRIM(RTRIM(p.PRFIO)), N'') IS NOT NULL
ORDER BY p.PRFIO;"; ORDER BY p.PRFIO;";
return SqlServerConnectionFactory.Current.QueryOpenConnectionAsync( return await SqlServerConnectionFactory.Current.QueryOpenConnectionAsync(
sql, sql,
delegate(SqlDataReader reader) delegate(SqlDataReader reader)
{ {
@@ -170,10 +169,7 @@ ORDER BY p.PRFIO;";
}; };
}, },
ConfigureCommandTimeout, ConfigureCommandTimeout,
cancellationToken).ContinueWith<IReadOnlyList<PersonReference>>(delegate(Task<List<PersonReference>> task) cancellationToken).ConfigureAwait(false);
{
return task.Result;
}, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
} }
public IReadOnlyList<DocumentFormReference> LoadVerificationDocumentForms(bool isPassed) public IReadOnlyList<DocumentFormReference> LoadVerificationDocumentForms(bool isPassed)
@@ -218,7 +214,7 @@ ORDER BY fr.NMFRD, v.IDVDODVDD;";
return forms; return forms;
} }
public Task<IReadOnlyList<DocumentFormReference>> LoadVerificationDocumentFormsAsync(bool isPassed, CancellationToken cancellationToken = default) public async Task<IReadOnlyList<DocumentFormReference>> LoadVerificationDocumentFormsAsync(bool isPassed, CancellationToken cancellationToken = default)
{ {
const string sql = @" const string sql = @"
SELECT DISTINCT SELECT DISTINCT
@@ -233,7 +229,7 @@ WHERE v.IDSPVDOD = 2
AND fr.IDSPVDD = @DocumentTypeId AND fr.IDSPVDD = @DocumentTypeId
ORDER BY fr.NMFRD, v.IDVDODVDD;"; ORDER BY fr.NMFRD, v.IDVDODVDD;";
return SqlServerConnectionFactory.Current.QueryOpenConnectionAsync( return await SqlServerConnectionFactory.Current.QueryOpenConnectionAsync(
sql, sql,
delegate(SqlDataReader reader) delegate(SqlDataReader reader)
{ {
@@ -250,10 +246,7 @@ ORDER BY fr.NMFRD, v.IDVDODVDD;";
ConfigureCommandTimeout(command); ConfigureCommandTimeout(command);
command.Parameters.Add("@DocumentTypeId", SqlDbType.Int).Value = isPassed ? 6 : 2; command.Parameters.Add("@DocumentTypeId", SqlDbType.Int).Value = isPassed ? 6 : 2;
}, },
cancellationToken).ContinueWith<IReadOnlyList<DocumentFormReference>>(delegate(Task<List<DocumentFormReference>> task) cancellationToken).ConfigureAwait(false);
{
return task.Result;
}, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
} }
public IReadOnlyList<SpoiDirectoryItem> LoadSpoiItems() public IReadOnlyList<SpoiDirectoryItem> LoadSpoiItems()
@@ -291,7 +284,7 @@ ORDER BY s.NMOI, s.KDOI, s.IDSPOI;";
return items; return items;
} }
public Task<IReadOnlyList<SpoiDirectoryItem>> LoadSpoiItemsAsync(CancellationToken cancellationToken = default) public async Task<IReadOnlyList<SpoiDirectoryItem>> LoadSpoiItemsAsync(CancellationToken cancellationToken = default)
{ {
const string sql = @" const string sql = @"
SELECT SELECT
@@ -301,7 +294,7 @@ SELECT
FROM dbo.SPOI s FROM dbo.SPOI s
ORDER BY s.NMOI, s.KDOI, s.IDSPOI;"; ORDER BY s.NMOI, s.KDOI, s.IDSPOI;";
return SqlServerConnectionFactory.Current.QueryOpenConnectionAsync( return await SqlServerConnectionFactory.Current.QueryOpenConnectionAsync(
sql, sql,
delegate(SqlDataReader reader) delegate(SqlDataReader reader)
{ {
@@ -313,10 +306,7 @@ ORDER BY s.NMOI, s.KDOI, s.IDSPOI;";
}; };
}, },
ConfigureCommandTimeout, ConfigureCommandTimeout,
cancellationToken).ContinueWith<IReadOnlyList<SpoiDirectoryItem>>(delegate(Task<List<SpoiDirectoryItem>> task) cancellationToken).ConfigureAwait(false);
{
return task.Result;
}, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
} }
public int AddSpoiItem(SpoiDirectoryItem item) public int AddSpoiItem(SpoiDirectoryItem item)
@@ -497,7 +487,7 @@ ORDER BY s.NMTP, s.IDSPNMTP;";
return items; return items;
} }
public Task<IReadOnlyList<SpnmtpDirectoryItem>> LoadSpnmtpItemsAsync(CancellationToken cancellationToken = default) public async Task<IReadOnlyList<SpnmtpDirectoryItem>> LoadSpnmtpItemsAsync(CancellationToken cancellationToken = default)
{ {
const string sql = @" const string sql = @"
SELECT SELECT
@@ -507,7 +497,7 @@ SELECT
FROM dbo.SPNMTP s FROM dbo.SPNMTP s
ORDER BY s.NMTP, s.IDSPNMTP;"; ORDER BY s.NMTP, s.IDSPNMTP;";
return SqlServerConnectionFactory.Current.QueryOpenConnectionAsync( return await SqlServerConnectionFactory.Current.QueryOpenConnectionAsync(
sql, sql,
delegate(SqlDataReader reader) delegate(SqlDataReader reader)
{ {
@@ -519,10 +509,7 @@ ORDER BY s.NMTP, s.IDSPNMTP;";
}; };
}, },
ConfigureCommandTimeout, ConfigureCommandTimeout,
cancellationToken).ContinueWith<IReadOnlyList<SpnmtpDirectoryItem>>(delegate(Task<List<SpnmtpDirectoryItem>> task) cancellationToken).ConfigureAwait(false);
{
return task.Result;
}, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
} }
public int AddSpnmtpItem(SpnmtpDirectoryItem item) public int AddSpnmtpItem(SpnmtpDirectoryItem item)
@@ -724,12 +711,21 @@ ORDER BY " + (loadClosedDocumentsForCurrentYear
}); });
} }
} }
var serialNumbersByDocument = LoadDocumentSerialNumbers(connection, loadClosedDocumentsForCurrentYear, currentYearStart, nextYearStart);
foreach (var document in documents)
{
if (serialNumbersByDocument.TryGetValue(document.DocumentNumber, out var serialNumbersText))
{
document.SerialNumbersText = serialNumbersText;
}
}
} }
return documents; return documents;
} }
public Task<IReadOnlyList<PsvDocumentSummary>> LoadDocumentsAsync(bool loadClosedDocumentsForCurrentYear, CancellationToken cancellationToken = default) public async Task<IReadOnlyList<PsvDocumentSummary>> LoadDocumentsAsync(bool loadClosedDocumentsForCurrentYear, CancellationToken cancellationToken = default)
{ {
var sql = @" var sql = @"
SELECT SELECT
@@ -760,41 +756,171 @@ ORDER BY " + (loadClosedDocumentsForCurrentYear
var currentYearStart = new DateTime(today.Year, 1, 1); var currentYearStart = new DateTime(today.Year, 1, 1);
var nextYearStart = currentYearStart.AddYears(1); var nextYearStart = currentYearStart.AddYears(1);
return SqlServerConnectionFactory.Current.QueryOpenConnectionAsync( var documents = new List<PsvDocumentSummary>();
sql,
delegate(SqlDataReader reader)
{
var documentNumber = GetString(reader, "DocumentNumber");
return new PsvDocumentSummary
{
DocumentKey = documentNumber,
DocumentNumber = documentNumber,
AcceptedOn = GetNullableDateTime(reader, "AcceptedOn"),
IssuedOn = GetNullableDateTime(reader, "IssuedOn"),
DepartmentName = GetString(reader, "DepartmentName"),
CustomerId = GetNullableInt32(reader, "CustomerId"),
CustomerName = GetString(reader, "CustomerName"),
ItemCount = GetInt32(reader, "ItemCount"),
IssuedCount = GetInt32(reader, "IssuedCount"),
PassedCount = GetInt32(reader, "PassedCount"),
FailedCount = GetInt32(reader, "FailedCount"),
IsDraft = false
};
},
delegate(SqlCommand command)
{
ConfigureCommandTimeout(command);
if (loadClosedDocumentsForCurrentYear) await using (var connection = await SqlServerConnectionFactory.Current.OpenConnectionAsync(cancellationToken).ConfigureAwait(false))
{ using (var command = new SqlCommand(sql, connection))
command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart; {
command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart; ConfigureCommandTimeout(command);
}
}, if (loadClosedDocumentsForCurrentYear)
cancellationToken).ContinueWith<IReadOnlyList<PsvDocumentSummary>>(delegate(Task<List<PsvDocumentSummary>> task)
{ {
return task.Result; command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart;
}, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart;
}
using (var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
{
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
var documentNumber = GetString(reader, "DocumentNumber");
documents.Add(new PsvDocumentSummary
{
DocumentKey = documentNumber,
DocumentNumber = documentNumber,
AcceptedOn = GetNullableDateTime(reader, "AcceptedOn"),
IssuedOn = GetNullableDateTime(reader, "IssuedOn"),
DepartmentName = GetString(reader, "DepartmentName"),
CustomerId = GetNullableInt32(reader, "CustomerId"),
CustomerName = GetString(reader, "CustomerName"),
ItemCount = GetInt32(reader, "ItemCount"),
IssuedCount = GetInt32(reader, "IssuedCount"),
PassedCount = GetInt32(reader, "PassedCount"),
FailedCount = GetInt32(reader, "FailedCount"),
IsDraft = false
});
}
}
var serialNumbersByDocument = await LoadDocumentSerialNumbersAsync(connection, loadClosedDocumentsForCurrentYear, currentYearStart, nextYearStart, cancellationToken).ConfigureAwait(false);
foreach (var document in documents)
{
if (serialNumbersByDocument.TryGetValue(document.DocumentNumber, out var serialNumbersText))
{
document.SerialNumbersText = serialNumbersText;
}
}
}
return documents;
}
private Dictionary<string, string> LoadDocumentSerialNumbers(SqlConnection connection, bool loadClosedDocumentsForCurrentYear, DateTime currentYearStart, DateTime nextYearStart)
{
var sql = @"
WITH filteredDocuments AS
(
SELECT m.NNZVPV
FROM dbo.EKZMK m
WHERE NULLIF(LTRIM(RTRIM(m.NNZVPV)), N'') IS NOT NULL
GROUP BY m.NNZVPV
HAVING " + (loadClosedDocumentsForCurrentYear
? "MAX(m.DTVDM) >= @IssuedFrom AND MAX(m.DTVDM) < @IssuedTo"
: "MAX(m.DTVDM) IS NULL") + @"
)
SELECT
m.NNZVPV AS DocumentNumber,
z.NNZV AS SerialNumber
FROM dbo.EKZMK m
JOIN filteredDocuments documents ON documents.NNZVPV = m.NNZVPV
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
WHERE NULLIF(LTRIM(RTRIM(z.NNZV)), N'') IS NOT NULL
ORDER BY m.NNZVPV, z.NNZV;";
var serialNumbersByDocument = new Dictionary<string, SortedSet<string>>(StringComparer.OrdinalIgnoreCase);
using (var command = new SqlCommand(sql, connection))
{
ConfigureCommandTimeout(command);
if (loadClosedDocumentsForCurrentYear)
{
command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart;
command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart;
}
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var documentNumber = GetString(reader, "DocumentNumber");
var serialNumber = GetString(reader, "SerialNumber");
if (string.IsNullOrWhiteSpace(documentNumber) || string.IsNullOrWhiteSpace(serialNumber))
{
continue;
}
if (!serialNumbersByDocument.TryGetValue(documentNumber, out var serialNumbers))
{
serialNumbers = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
serialNumbersByDocument[documentNumber] = serialNumbers;
}
serialNumbers.Add(serialNumber.Trim());
}
}
}
return serialNumbersByDocument.ToDictionary(pair => pair.Key, pair => string.Join(", ", pair.Value), StringComparer.OrdinalIgnoreCase);
}
private async Task<Dictionary<string, string>> LoadDocumentSerialNumbersAsync(SqlConnection connection, bool loadClosedDocumentsForCurrentYear, DateTime currentYearStart, DateTime nextYearStart, CancellationToken cancellationToken)
{
var sql = @"
WITH filteredDocuments AS
(
SELECT m.NNZVPV
FROM dbo.EKZMK m
WHERE NULLIF(LTRIM(RTRIM(m.NNZVPV)), N'') IS NOT NULL
GROUP BY m.NNZVPV
HAVING " + (loadClosedDocumentsForCurrentYear
? "MAX(m.DTVDM) >= @IssuedFrom AND MAX(m.DTVDM) < @IssuedTo"
: "MAX(m.DTVDM) IS NULL") + @"
)
SELECT
m.NNZVPV AS DocumentNumber,
z.NNZV AS SerialNumber
FROM dbo.EKZMK m
JOIN filteredDocuments documents ON documents.NNZVPV = m.NNZVPV
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
WHERE NULLIF(LTRIM(RTRIM(z.NNZV)), N'') IS NOT NULL
ORDER BY m.NNZVPV, z.NNZV;";
var serialNumbersByDocument = new Dictionary<string, SortedSet<string>>(StringComparer.OrdinalIgnoreCase);
using (var command = new SqlCommand(sql, connection))
{
ConfigureCommandTimeout(command);
if (loadClosedDocumentsForCurrentYear)
{
command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart;
command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart;
}
using (var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
{
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
var documentNumber = GetString(reader, "DocumentNumber");
var serialNumber = GetString(reader, "SerialNumber");
if (string.IsNullOrWhiteSpace(documentNumber) || string.IsNullOrWhiteSpace(serialNumber))
{
continue;
}
if (!serialNumbersByDocument.TryGetValue(documentNumber, out var serialNumbers))
{
serialNumbers = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
serialNumbersByDocument[documentNumber] = serialNumbers;
}
serialNumbers.Add(serialNumber.Trim());
}
}
}
return serialNumbersByDocument.ToDictionary(pair => pair.Key, pair => string.Join(", ", pair.Value), StringComparer.OrdinalIgnoreCase);
} }
public IReadOnlyList<PsvDocumentLine> LoadDocumentLines(string documentNumber) public IReadOnlyList<PsvDocumentLine> LoadDocumentLines(string documentNumber)
@@ -1126,7 +1252,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;";
return instruments; return instruments;
} }
public Task<IReadOnlyList<AvailableInstrumentItem>> LoadCustomerInstrumentsAsync(int customerId, CancellationToken cancellationToken = default) public async Task<IReadOnlyList<AvailableInstrumentItem>> LoadCustomerInstrumentsAsync(int customerId, CancellationToken cancellationToken = default)
{ {
const string sql = @" const string sql = @"
SELECT SELECT
@@ -1202,7 +1328,7 @@ OUTER APPLY
WHERE z.IDFRPDV = @CustomerId WHERE z.IDFRPDV = @CustomerId
ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;"; ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;";
return SqlServerConnectionFactory.Current.QueryOpenConnectionAsync( return await SqlServerConnectionFactory.Current.QueryOpenConnectionAsync(
sql, sql,
delegate(SqlDataReader reader) delegate(SqlDataReader reader)
{ {
@@ -1230,10 +1356,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;";
ConfigureCommandTimeout(command); ConfigureCommandTimeout(command);
command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId; command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId;
}, },
cancellationToken).ContinueWith<IReadOnlyList<AvailableInstrumentItem>>(delegate(Task<List<AvailableInstrumentItem>> task) cancellationToken).ConfigureAwait(false);
{
return task.Result;
}, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
} }
public IReadOnlyList<AvailableInstrumentItem> LoadInstrumentTypes() public IReadOnlyList<AvailableInstrumentItem> LoadInstrumentTypes()
@@ -1327,7 +1450,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
return instrumentTypes; return instrumentTypes;
} }
public Task<IReadOnlyList<AvailableInstrumentItem>> LoadInstrumentTypesAsync(CancellationToken cancellationToken = default) public async Task<IReadOnlyList<AvailableInstrumentItem>> LoadInstrumentTypesAsync(CancellationToken cancellationToken = default)
{ {
const string sql = @" const string sql = @"
SELECT SELECT
@@ -1381,7 +1504,7 @@ OUTER APPLY
) periodByType ) periodByType
ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;"; ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
return SqlServerConnectionFactory.Current.QueryOpenConnectionAsync( return await SqlServerConnectionFactory.Current.QueryOpenConnectionAsync(
sql, sql,
delegate(SqlDataReader reader) delegate(SqlDataReader reader)
{ {
@@ -1405,10 +1528,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
}; };
}, },
ConfigureCommandTimeout, ConfigureCommandTimeout,
cancellationToken).ContinueWith<IReadOnlyList<AvailableInstrumentItem>>(delegate(Task<List<AvailableInstrumentItem>> task) cancellationToken).ConfigureAwait(false);
{
return task.Result;
}, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
} }
public IReadOnlyList<OpenDocumentConflictInfo> FindOpenDocumentConflicts(int customerId, string currentDocumentNumber, IEnumerable<PsvDocumentLine> candidateLines) public IReadOnlyList<OpenDocumentConflictInfo> FindOpenDocumentConflicts(int customerId, string currentDocumentNumber, IEnumerable<PsvDocumentLine> candidateLines)
@@ -1677,7 +1797,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
} }
} }
public DocumentSaveResult SaveDocument(string currentDocumentNumber, DocumentEditorResult document, IEnumerable<PsvDocumentLine> pendingLines) public DocumentSaveResult SaveDocument(string currentDocumentNumber, DocumentEditorResult document, IEnumerable<PsvDocumentLine> documentLines)
{ {
if (document == null) if (document == null)
{ {
@@ -1692,14 +1812,23 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
document.DocumentNumber = normalizedNumber; document.DocumentNumber = normalizedNumber;
var distinctPendingLines = pendingLines == null var materializedDocumentLines = documentLines == null
? new List<PsvDocumentLine>() ? new List<PsvDocumentLine>()
: pendingLines : documentLines
.Where(IsPendingLineReadyForSave) .Where(delegate(PsvDocumentLine line) { return line != null; })
.GroupBy(GetPendingLineSaveKey, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, PsvDocumentLine> group) { return group.First(); })
.ToList(); .ToList();
var distinctPendingLines = materializedDocumentLines
.Where(IsPendingLineReadyForSave)
.GroupBy(GetPendingLineSaveKey, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, PsvDocumentLine> group) { return group.First(); })
.ToList();
var persistedLines = materializedDocumentLines
.Where(delegate(PsvDocumentLine line) { return !line.IsPendingInsert && line.CardId > 0; })
.GroupBy(delegate(PsvDocumentLine line) { return line.CardId; })
.Select(delegate(IGrouping<int, PsvDocumentLine> group) { return group.First(); })
.ToList();
using (var connection = CreateConnection()) using (var connection = CreateConnection())
{ {
connection.Open(); connection.Open();
@@ -1733,6 +1862,12 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
{ {
throw new InvalidOperationException("Строки EKZMK для выбранного ПСВ не найдены."); throw new InvalidOperationException("Строки EKZMK для выбранного ПСВ не найдены.");
} }
var updatedCompletenessCount = UpdateDocumentLineCompleteness(connection, transaction, normalizedNumber, persistedLines);
if (updatedCompletenessCount > updatedEkzMkCount)
{
updatedEkzMkCount = updatedCompletenessCount;
}
} }
var insertedEkzMkCount = 0; var insertedEkzMkCount = 0;
@@ -1774,13 +1909,19 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
continue; continue;
} }
if (!verificationTypeLoaded) var effectiveVerificationTypeId = template.IdSpvdmk;
if (!effectiveVerificationTypeId.HasValue || effectiveVerificationTypeId.Value <= 0)
{ {
verificationTypeId = LoadVerificationTypeId(connection, transaction); if (!verificationTypeLoaded)
verificationTypeLoaded = true; {
verificationTypeId = LoadVerificationTypeId(connection, transaction);
verificationTypeLoaded = true;
}
effectiveVerificationTypeId = verificationTypeId;
} }
var cardId = InsertEkzMk(connection, transaction, verificationTypeId, document, normalizedNumber, instrumentId, template, pendingLine); var cardId = InsertEkzMk(connection, transaction, effectiveVerificationTypeId.Value, document, normalizedNumber, instrumentId, template, pendingLine);
if (!string.IsNullOrWhiteSpace(pendingLine.VerificationDocumentNumber)) if (!string.IsNullOrWhiteSpace(pendingLine.VerificationDocumentNumber))
{ {
if (!pendingLine.VerificationDocumentFormId.HasValue || !pendingLine.VerificationDocumentLinkTypeId.HasValue) if (!pendingLine.VerificationDocumentFormId.HasValue || !pendingLine.VerificationDocumentLinkTypeId.HasValue)
@@ -2044,6 +2185,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
private static bool IsPendingLineReadyForSave(PsvDocumentLine line) private static bool IsPendingLineReadyForSave(PsvDocumentLine line)
{ {
return line != null return line != null
&& line.IsPendingInsert
&& (line.InstrumentId > 0 && (line.InstrumentId > 0
|| (line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber))); || (line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber)));
} }
@@ -2071,6 +2213,11 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
throw new InvalidOperationException("Для новой строки ПСВ не указан заводской номер."); throw new InvalidOperationException("Для новой строки ПСВ не указан заводской номер.");
} }
if (serialNumber.Length > EkzDirectoryRules.SerialNumberMaxLength)
{
throw new InvalidOperationException(string.Format("Заводской номер не должен превышать {0} символов.", EkzDirectoryRules.SerialNumberMaxLength));
}
if (!document.CustomerId.HasValue) if (!document.CustomerId.HasValue)
{ {
throw new InvalidOperationException("Для добавления прибора по типу должен быть выбран заказчик ПСВ."); throw new InvalidOperationException("Для добавления прибора по типу должен быть выбран заказчик ПСВ.");
@@ -2113,7 +2260,7 @@ SELECT TOP (1) z.IDEKZ
FROM dbo.EKZ z FROM dbo.EKZ z
WHERE z.IDTPRZ = @TypeSizeId WHERE z.IDTPRZ = @TypeSizeId
AND z.IDFRPDV = @CustomerId AND z.IDFRPDV = @CustomerId
AND z.NNZV = @SerialNumber AND LTRIM(RTRIM(z.NNZV)) = @SerialNumber
AND ISNULL(z.IsDeleted, 0) = 0 AND ISNULL(z.IsDeleted, 0) = 0
ORDER BY z.IDEKZ DESC;"; ORDER BY z.IDEKZ DESC;";
@@ -2122,7 +2269,7 @@ ORDER BY z.IDEKZ DESC;";
command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds; command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId; command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;
command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId; command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, 30).Value = serialNumber; command.Parameters.Add("@SerialNumber", SqlDbType.NVarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = serialNumber;
var result = command.ExecuteScalar(); var result = command.ExecuteScalar();
return result == null || result == DBNull.Value ? (int?)null : Convert.ToInt32(result); return result == null || result == DBNull.Value ? (int?)null : Convert.ToInt32(result);
@@ -2159,7 +2306,7 @@ SELECT CAST(SCOPE_IDENTITY() AS int);";
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId; command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;
command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId; command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId;
command.Parameters.Add("@Klsipr", SqlDbType.Int).Value = 1; command.Parameters.Add("@Klsipr", SqlDbType.Int).Value = 1;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, 30).Value = serialNumber; command.Parameters.Add("@SerialNumber", SqlDbType.NVarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = serialNumber;
command.Parameters.Add("@GuidEkz", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid(); command.Parameters.Add("@GuidEkz", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
return Convert.ToInt32(command.ExecuteScalar()); return Convert.ToInt32(command.ExecuteScalar());
} }
@@ -2263,7 +2410,7 @@ ORDER BY m.NNZVPV;";
command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds; command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId; command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId;
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = candidateLine.TypeSizeId; command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = candidateLine.TypeSizeId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, 30).Value = candidateLine.SerialNumber.Trim(); command.Parameters.Add("@SerialNumber", SqlDbType.NVarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = candidateLine.SerialNumber.Trim();
command.Parameters.Add("@CurrentDocumentNumber", SqlDbType.NVarChar, 60).Value = currentDocumentNumber ?? string.Empty; command.Parameters.Add("@CurrentDocumentNumber", SqlDbType.NVarChar, 60).Value = currentDocumentNumber ?? string.Empty;
using (var reader = command.ExecuteReader()) using (var reader = command.ExecuteReader())
@@ -2365,7 +2512,7 @@ ORDER BY m.NNZVPV;";
command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds; command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId; command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId;
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = candidateLine.TypeSizeId; command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = candidateLine.TypeSizeId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, 30).Value = candidateLine.SerialNumber.Trim(); command.Parameters.Add("@SerialNumber", SqlDbType.NVarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = candidateLine.SerialNumber.Trim();
command.Parameters.Add("@CurrentDocumentNumber", SqlDbType.NVarChar, 60).Value = currentDocumentNumber ?? string.Empty; command.Parameters.Add("@CurrentDocumentNumber", SqlDbType.NVarChar, 60).Value = currentDocumentNumber ?? string.Empty;
}, },
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
@@ -3064,7 +3211,7 @@ VALUES
@idsptsmp, @idsptsmp,
@idspssmp, @idspssmp,
@GUIDEKZMK, @GUIDEKZMK,
NULL, @DSEKZMK,
NULL, NULL,
NULL, NULL,
NULL, NULL,
@@ -3118,6 +3265,9 @@ SELECT CAST(SCOPE_IDENTITY() AS int);";
command.Parameters.Add("@idsptsmp", SqlDbType.Int).Value = (object)template.IdSptsmp ?? DBNull.Value; command.Parameters.Add("@idsptsmp", SqlDbType.Int).Value = (object)template.IdSptsmp ?? DBNull.Value;
command.Parameters.Add("@idspssmp", SqlDbType.Int).Value = (object)template.IdSpssmp ?? DBNull.Value; command.Parameters.Add("@idspssmp", SqlDbType.Int).Value = (object)template.IdSpssmp ?? DBNull.Value;
command.Parameters.Add("@GUIDEKZMK", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid(); command.Parameters.Add("@GUIDEKZMK", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
command.Parameters.Add("@DSEKZMK", SqlDbType.NVarChar, EkzMkCompletenessMaxLength).Value = pendingLine == null
? DBNull.Value
: (object)NormalizeOptionalCompleteness(pendingLine.Notes) ?? DBNull.Value;
command.Parameters.Add("@NRVRMNDmp", SqlDbType.Decimal).Value = (object)template.Nrvrmndmp ?? DBNull.Value; command.Parameters.Add("@NRVRMNDmp", SqlDbType.Decimal).Value = (object)template.Nrvrmndmp ?? DBNull.Value;
command.Parameters["@NRVRMNDmp"].Precision = 10; command.Parameters["@NRVRMNDmp"].Precision = 10;
command.Parameters["@NRVRMNDmp"].Scale = 2; command.Parameters["@NRVRMNDmp"].Scale = 2;
@@ -3529,8 +3679,17 @@ WHERE z.IDEKZ = @InstrumentId;";
const string sql = @" const string sql = @"
SELECT TOP (1) IDSPVDMK SELECT TOP (1) IDSPVDMK
FROM dbo.SPVDMK FROM dbo.SPVDMK
WHERE OBVDMK = N'Рџ' OR NMVDMK = N'Поверка' WHERE UPPER(LTRIM(RTRIM(ISNULL(OBVDMK, N'')))) = N'П'
ORDER BY CASE WHEN OBVDMK = N'Рџ' THEN 0 ELSE 1 END, IDSPVDMK;"; OR UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) = N'ПОВЕРКА'
OR UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) LIKE N'ПОВЕРК%'
OR UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) LIKE N'%ПОВЕРК%'
ORDER BY CASE
WHEN UPPER(LTRIM(RTRIM(ISNULL(OBVDMK, N'')))) = N'П' THEN 0
WHEN UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) = N'ПОВЕРКА' THEN 1
WHEN UPPER(LTRIM(RTRIM(ISNULL(NMVDMK, N'')))) LIKE N'ПОВЕРК%' THEN 2
ELSE 3
END,
IDSPVDMK;";
using (var command = new SqlCommand(sql, connection, transaction)) using (var command = new SqlCommand(sql, connection, transaction))
{ {
@@ -3560,6 +3719,7 @@ WITH TemplateCandidates AS
m.IDSPMU, m.IDSPMU,
m.IDGRSI, m.IDGRSI,
m.IDKSPRL, m.IDKSPRL,
m.IDSPVDMK,
m.IDSPVDMC, m.IDSPVDMC,
m.IDFRPD, m.IDFRPD,
m.IDSPMPOB, m.IDSPMPOB,
@@ -3598,6 +3758,7 @@ WITH TemplateCandidates AS
m.IDSPMU, m.IDSPMU,
m.IDGRSI, m.IDGRSI,
m.IDKSPRL, m.IDKSPRL,
m.IDSPVDMK,
m.IDSPVDMC, m.IDSPVDMC,
m.IDFRPD, m.IDFRPD,
m.IDSPMPOB, m.IDSPMPOB,
@@ -3646,6 +3807,7 @@ ORDER BY Priority;";
IdSpmu = GetNullableInt32(reader, "IDSPMU"), IdSpmu = GetNullableInt32(reader, "IDSPMU"),
IdGrsi = GetNullableInt32(reader, "IDGRSI"), IdGrsi = GetNullableInt32(reader, "IDGRSI"),
IdKsprl = GetNullableInt32(reader, "IDKSPRL"), IdKsprl = GetNullableInt32(reader, "IDKSPRL"),
IdSpvdmk = GetNullableInt32(reader, "IDSPVDMK"),
IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"), IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"),
IdFrpd = GetInt32(reader, "IDFRPD"), IdFrpd = GetInt32(reader, "IDFRPD"),
IdSpmpob = GetNullableInt32(reader, "IDSPMPOB"), IdSpmpob = GetNullableInt32(reader, "IDSPMPOB"),
@@ -3682,30 +3844,26 @@ ORDER BY Priority;";
private static EkzMkTemplate LoadFallbackTemplate(SqlConnection connection, SqlTransaction transaction, int instrumentId) private static EkzMkTemplate LoadFallbackTemplate(SqlConnection connection, SqlTransaction transaction, int instrumentId)
{ {
const string sql = @" const string sql = @"
WITH DefaultLab AS
(
SELECT
CASE WHEN COUNT(DISTINCT m.IDFRPD) = 1 THEN MIN(m.IDFRPD) ELSE NULL END AS DefaultIdFrpd
FROM dbo.EKZMK m
)
SELECT TOP (1) SELECT TOP (1)
COALESCE(periodByInstrument.IDGRSI, periodByType.IDGRSI) AS IDGRSI, COALESCE(periodByInstrument.IDGRSI, periodByType.IDGRSI, verificationSetup.IDGRSI) AS IDGRSI,
COALESCE(periodByInstrument.IDSPVDMC, periodByType.IDSPVDMC) AS IDSPVDMC, COALESCE(periodByInstrument.IDSPVDMC, periodByType.IDSPVDMC) AS IDSPVDMC,
defaultLab.DefaultIdFrpd AS IDFRPD, verificationSetup.IDFRPD AS IDFRPD,
tprz.IDSPKMMK, tprz.IDSPKMMK,
verificationSetup.IDSPVDMK AS IDSPVDMK,
verificationSetup.IDSPMPOB AS IDSPMPOB,
verificationSetup.IDSPVDKL AS IDSPVDKL,
COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR) AS PRMK, COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR) AS PRMK,
tprz.DPZN AS DPZNmp, tprz.DPZN AS DPZNmp,
tprz.HRTC AS HRTCmp, tprz.HRTC AS HRTCmp,
CAST( CAST(
CASE CASE
WHEN COALESCE(periodByInstrument.PRMK, periodByType.PRMK) IS NOT NULL THEN N'Период РёР· TPRMCP' WHEN COALESCE(periodByInstrument.PRMK, periodByType.PRMK) IS NOT NULL THEN N'Данные TPRMK + период из TPRMCP'
WHEN tips.PRMKGR IS NOT NULL THEN N'Регистрационный период РёР· TIPS' WHEN tips.PRMKGR IS NOT NULL THEN N'Данные TPRMK + регистрационный период из TIPS'
ELSE N'' ELSE N''
END AS nvarchar(60)) AS SourceDescription END AS nvarchar(60)) AS SourceDescription
FROM dbo.EKZ z FROM dbo.EKZ z
JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
CROSS JOIN DefaultLab defaultLab
OUTER APPLY OUTER APPLY
( (
SELECT TOP (1) SELECT TOP (1)
@@ -3727,8 +3885,45 @@ OUTER APPLY
WHERE t.IDTPRZ = z.IDTPRZ WHERE t.IDTPRZ = z.IDTPRZ
ORDER BY t.IDTPRMCP DESC ORDER BY t.IDTPRMCP DESC
) periodByType ) periodByType
OUTER APPLY
(
SELECT TOP (1)
mk.IDTPRMK,
mk.IDSPVDMK,
mk.IDFRPD,
mk.IDGRSI,
mk.IDSPMPOB,
stamp.IDSPVDKL
FROM dbo.TPRMK mk
LEFT JOIN dbo.SPVDMK verificationType ON verificationType.IDSPVDMK = mk.IDSPVDMK
OUTER APPLY
(
SELECT TOP (1)
tp.IDSPVDKL
FROM dbo.TPVDKL tp
WHERE tp.IDTIPS = tprz.IDTIPS
AND tp.IDSPVDMK = mk.IDSPVDMK
ORDER BY tp.IDTPVDKL DESC
) stamp
WHERE mk.IDTPRZ = z.IDTPRZ
ORDER BY
CASE
WHEN UPPER(LTRIM(RTRIM(ISNULL(verificationType.OBVDMK, N'')))) = N'П' THEN 0
WHEN UPPER(LTRIM(RTRIM(ISNULL(verificationType.NMVDMK, N'')))) = N'ПОВЕРКА' THEN 1
WHEN UPPER(LTRIM(RTRIM(ISNULL(verificationType.NMVDMK, N'')))) LIKE N'ПОВЕРК%' THEN 2
WHEN UPPER(LTRIM(RTRIM(ISNULL(verificationType.NMVDMK, N'')))) LIKE N'%ПОВЕРК%' THEN 3
ELSE 4
END,
CASE
WHEN COALESCE(periodByInstrument.IDGRSI, periodByType.IDGRSI) IS NOT NULL
AND mk.IDGRSI = COALESCE(periodByInstrument.IDGRSI, periodByType.IDGRSI) THEN 0
WHEN mk.IDGRSI IS NULL THEN 1
ELSE 2
END,
mk.IDTPRMK DESC
) verificationSetup
WHERE z.IDEKZ = @InstrumentId WHERE z.IDEKZ = @InstrumentId
AND defaultLab.DefaultIdFrpd IS NOT NULL AND verificationSetup.IDFRPD IS NOT NULL
AND COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR) IS NOT NULL;"; AND COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR) IS NOT NULL;";
using (var command = new SqlCommand(sql, connection, transaction)) using (var command = new SqlCommand(sql, connection, transaction))
@@ -3747,12 +3942,13 @@ WHERE z.IDEKZ = @InstrumentId
IdSpmu = null, IdSpmu = null,
IdGrsi = GetNullableInt32(reader, "IDGRSI"), IdGrsi = GetNullableInt32(reader, "IDGRSI"),
IdKsprl = null, IdKsprl = null,
IdSpvdmk = GetNullableInt32(reader, "IDSPVDMK"),
IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"), IdSpvdmc = GetNullableInt32(reader, "IDSPVDMC"),
IdFrpd = GetInt32(reader, "IDFRPD"), IdFrpd = GetInt32(reader, "IDFRPD"),
IdSpmpob = null, IdSpmpob = GetNullableInt32(reader, "IDSPMPOB"),
IdPrsn = null, IdPrsn = null,
IdSpkmmk = GetNullableInt32(reader, "IDSPKMMK"), IdSpkmmk = GetNullableInt32(reader, "IDSPKMMK"),
IdSpvdkl = null, IdSpvdkl = GetNullableInt32(reader, "IDSPVDKL"),
IdPrsnvd = null, IdPrsnvd = null,
Prmk = GetInt32(reader, "PRMK"), Prmk = GetInt32(reader, "PRMK"),
Stmk = null, Stmk = null,
@@ -4158,6 +4354,75 @@ ORDER BY blocker.TableName;";
return normalizedValue; return normalizedValue;
} }
private static int UpdateDocumentLineCompleteness(
SqlConnection connection,
SqlTransaction transaction,
string documentNumber,
IEnumerable<PsvDocumentLine> persistedLines)
{
var materializedLines = persistedLines == null
? new List<PsvDocumentLine>()
: persistedLines
.Where(delegate(PsvDocumentLine line) { return line != null && line.CardId > 0; })
.GroupBy(delegate(PsvDocumentLine line) { return line.CardId; })
.Select(delegate(IGrouping<int, PsvDocumentLine> group) { return group.First(); })
.ToList();
var updatedCount = 0;
foreach (var line in materializedLines)
{
updatedCount += UpdateDocumentLineCompletenessCore(
connection,
transaction,
documentNumber,
line.CardId,
NormalizeOptionalCompleteness(line.Notes));
}
return updatedCount;
}
private static int UpdateDocumentLineCompletenessCore(
SqlConnection connection,
SqlTransaction transaction,
string documentNumber,
int cardId,
string completeness)
{
const string sql = @"
UPDATE dbo.EKZMK
SET DSEKZMK = @Completeness
WHERE NNZVPV = @DocumentNumber
AND IDEKZMK = @CardId;
SELECT @@ROWCOUNT;";
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
command.Parameters.Add("@DocumentNumber", SqlDbType.NVarChar, 60).Value = documentNumber;
command.Parameters.Add("@CardId", SqlDbType.Int).Value = cardId;
command.Parameters.Add("@Completeness", SqlDbType.NVarChar, EkzMkCompletenessMaxLength).Value = (object)completeness ?? DBNull.Value;
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static string NormalizeOptionalCompleteness(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
var normalizedValue = value.Trim();
if (normalizedValue.Length > EkzMkCompletenessMaxLength)
{
throw new InvalidOperationException(string.Format("Комплектность не должна превышать {0} символов.", EkzMkCompletenessMaxLength));
}
return normalizedValue;
}
private static int UpdateDocumentHeader(SqlConnection connection, SqlTransaction transaction, string currentDocumentNumber, DocumentEditorResult document) private static int UpdateDocumentHeader(SqlConnection connection, SqlTransaction transaction, string currentDocumentNumber, DocumentEditorResult document)
{ {
const string sql = @" const string sql = @"

View File

@@ -40,6 +40,7 @@ namespace XLAB2
private DateTime? _issuedOn; private DateTime? _issuedOn;
private int _itemCount; private int _itemCount;
private int _passedCount; private int _passedCount;
private string _serialNumbersText;
public DateTime? AcceptedOn public DateTime? AcceptedOn
{ {
@@ -89,6 +90,12 @@ namespace XLAB2
set { SetProperty(ref _documentNumber, value); } set { SetProperty(ref _documentNumber, value); }
} }
public string SerialNumbersText
{
get { return _serialNumbersText; }
set { SetProperty(ref _serialNumbersText, value); }
}
public int FailedCount public int FailedCount
{ {
get { return _failedCount; } get { return _failedCount; }
@@ -197,6 +204,7 @@ namespace XLAB2
public sealed class PsvDocumentLine : ObservableObject public sealed class PsvDocumentLine : ObservableObject
{ {
private bool _isBatchSelected; private bool _isBatchSelected;
private string _notes;
public int CardId { get; set; } public int CardId { get; set; }
@@ -250,7 +258,23 @@ namespace XLAB2
public string RejectionReason { get; set; } public string RejectionReason { get; set; }
public string Notes { get; set; } public string Notes
{
get { return _notes; }
set
{
if (SetProperty(ref _notes, value))
{
OnPropertyChanged("Completeness");
}
}
}
public string Completeness
{
get { return Notes; }
set { Notes = value; }
}
public bool IsPendingInsert { get; set; } public bool IsPendingInsert { get; set; }
@@ -340,12 +364,18 @@ namespace XLAB2
{ {
private bool _isBatchSelected; private bool _isBatchSelected;
public string InstrumentName { get; set; }
public string InstrumentType { get; set; } public string InstrumentType { get; set; }
public string RangeText { get; set; } public string RangeText { get; set; }
public string AccuracyText { get; set; }
public string RegistryNumber { get; set; } public string RegistryNumber { get; set; }
public string SerialNumbersText { get; set; }
public int InVerificationCount { get; set; } public int InVerificationCount { get; set; }
public int VerifiedCount { get; set; } public int VerifiedCount { get; set; }
@@ -365,6 +395,7 @@ namespace XLAB2
return line != null return line != null
&& string.Equals(InstrumentType ?? string.Empty, line.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(InstrumentType ?? string.Empty, line.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(RangeText ?? string.Empty, line.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase) && string.Equals(RangeText ?? string.Empty, line.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(AccuracyText ?? string.Empty, line.AccuracyText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(RegistryNumber ?? string.Empty, line.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase); && string.Equals(RegistryNumber ?? string.Empty, line.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
} }
} }
@@ -486,8 +517,18 @@ namespace XLAB2
public sealed class DocumentEditorResult public sealed class DocumentEditorResult
{ {
public string AccountingBookKey { get; set; }
public string AccountingBookTitle { get; set; }
public string DocumentNumber { get; set; } public string DocumentNumber { get; set; }
public string DocumentSequenceNumber { get; set; }
public string DocumentTypeKey { get; set; }
public string DocumentTypeTitle { get; set; }
public DateTime AcceptedOn { get; set; } public DateTime AcceptedOn { get; set; }
public DateTime? IssuedOn { get; set; } public DateTime? IssuedOn { get; set; }
@@ -499,7 +540,7 @@ namespace XLAB2
{ {
public AvailableInstrumentItem TypeItem { get; set; } public AvailableInstrumentItem TypeItem { get; set; }
public string SerialNumber { get; set; } public IReadOnlyList<string> SerialNumbers { get; set; }
} }
public sealed class VerificationEditSeed public sealed class VerificationEditSeed
@@ -631,6 +672,8 @@ namespace XLAB2
public int? IdSptsmp { get; set; } public int? IdSptsmp { get; set; }
public int? IdSpvdmk { get; set; }
public int? IdSpvdkl { get; set; } public int? IdSpvdkl { get; set; }
public int? IdSpvdsbmk { get; set; } public int? IdSpvdsbmk { get; set; }

View File

@@ -3,15 +3,21 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace XLAB2 namespace XLAB2
{ {
internal sealed class PsvPrintService internal sealed class PsvPrintService
{ {
private const int OpenPsvTableColumnCount = 7;
private const int ClosePsvTableColumnCount = 12;
private const int PrintDialogId = 88; private const int PrintDialogId = 88;
private const int WordActiveEndAdjustedPageNumber = 1;
private const int WordAlertsNone = 0; private const int WordAlertsNone = 0;
private const int WordParagraphTrue = -1;
private const int WordCloseDoNotSaveChanges = 0; private const int WordCloseDoNotSaveChanges = 0;
private const int WordStatisticPages = 2;
public void PrintDocument(PsvDocumentSummary document, IReadOnlyList<PsvDocumentLine> lines) public void PrintDocument(PsvDocumentSummary document, IReadOnlyList<PsvDocumentLine> lines)
{ {
@@ -165,7 +171,7 @@ namespace XLAB2
ReplacePlaceholder(document, "next", FormatDate(dueDate)); ReplacePlaceholder(document, "next", FormatDate(dueDate));
} }
FillTable(document, groupedLines); FillTable(document, groupedLines, summary.IssuedOn.HasValue);
} }
private static void PopulateVerificationTemplate(dynamic document, PsvDocumentLine line) private static void PopulateVerificationTemplate(dynamic document, PsvDocumentLine line)
@@ -187,13 +193,14 @@ namespace XLAB2
ReplacePlaceholder(document, "date", FormatDate(verificationDate)); ReplacePlaceholder(document, "date", FormatDate(verificationDate));
} }
private static void FillTable(dynamic document, IReadOnlyList<PrintedGroupRow> rowsToPrint) private static void FillTable(dynamic document, IReadOnlyList<PrintedGroupRow> rowsToPrint, bool isClosedDocument)
{ {
dynamic table = null; dynamic table = null;
try try
{ {
table = document.Tables.Item(1); table = document.Tables.Item(1);
EnsurePsvTableLayout(table, isClosedDocument ? ClosePsvTableColumnCount : OpenPsvTableColumnCount);
for (var index = 0; index < rowsToPrint.Count; index++) for (var index = 0; index < rowsToPrint.Count; index++)
{ {
@@ -209,15 +216,29 @@ namespace XLAB2
SetCellText(row, 4, rowData.RangeText, false); SetCellText(row, 4, rowData.RangeText, false);
SetCellText(row, 5, rowData.SerialNumberText, false); SetCellText(row, 5, rowData.SerialNumberText, false);
SetCellText(row, 6, rowData.GroupCount.ToString(CultureInfo.InvariantCulture), true); SetCellText(row, 6, rowData.GroupCount.ToString(CultureInfo.InvariantCulture), true);
SetCellText(row, 7, rowData.PassedCount > 0 ? rowData.PassedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true);
SetCellText(row, 8, rowData.FailedCount > 0 ? rowData.FailedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true); if (isClosedDocument)
SetCellText(row, 9, rowData.Notes, false); {
SetCellText(row, 7, rowData.PassedCount > 0 ? rowData.PassedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true);
SetCellText(row, 8, rowData.FailedCount > 0 ? rowData.FailedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true);
SetCellText(row, 9, rowData.VerificationDocumentText, false);
SetCellText(row, 10, rowData.StickerNumberText, false);
SetCellText(row, 11, rowData.VerifierNameText, false);
SetCellText(row, 12, rowData.Notes, false);
}
else
{
SetCellText(row, 7, rowData.Notes, false);
}
} }
finally finally
{ {
ReleaseComObject(row); ReleaseComObject(row);
} }
} }
// If trailing document text spills onto a new page, move the last table row with it.
EnsureTableSpillsToNextPage(document, table);
} }
finally finally
{ {
@@ -225,6 +246,99 @@ namespace XLAB2
} }
} }
private static void EnsureTableSpillsToNextPage(dynamic document, dynamic table)
{
if (document == null || table == null)
{
return;
}
var rowCount = Convert.ToInt32(table.Rows.Count, CultureInfo.InvariantCulture);
if (rowCount <= 1)
{
return;
}
RepaginateDocument(document);
var totalPages = InvokeComIntMethod(document, "ComputeStatistics", WordStatisticPages);
if (totalPages <= 1)
{
return;
}
dynamic lastRow = null;
dynamic firstCell = null;
dynamic lastRowRange = null;
dynamic firstCellRange = null;
dynamic paragraphFormat = null;
try
{
lastRow = table.Rows.Item(rowCount);
lastRowRange = lastRow.Range;
var lastRowPage = GetRangePageNumber(lastRowRange);
if (lastRowPage >= totalPages)
{
return;
}
firstCell = lastRow.Cells.Item(1);
firstCellRange = firstCell.Range;
paragraphFormat = firstCellRange.ParagraphFormat;
paragraphFormat.PageBreakBefore = WordParagraphTrue;
RepaginateDocument(document);
}
finally
{
ReleaseComObject(paragraphFormat);
ReleaseComObject(firstCellRange);
ReleaseComObject(lastRowRange);
ReleaseComObject(firstCell);
ReleaseComObject(lastRow);
}
}
private static void EnsurePsvTableLayout(dynamic table, int expectedColumnCount)
{
if (table == null)
{
throw new InvalidOperationException("В шаблоне ПСВ не найдена таблица для печати.");
}
var actualColumnCount = 0;
try
{
actualColumnCount = Convert.ToInt32(table.Columns.Count, CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
throw new InvalidOperationException("Не удалось определить структуру таблицы шаблона ПСВ.", ex);
}
if (actualColumnCount != expectedColumnCount)
{
throw new InvalidOperationException(
string.Format(
CultureInfo.InvariantCulture,
"Шаблон ПСВ имеет неверную структуру таблицы: ожидается {0} колонок, найдено {1}.",
expectedColumnCount,
actualColumnCount));
}
}
private static int GetRangePageNumber(object range)
{
return InvokeComIndexedIntProperty(range, "Information", WordActiveEndAdjustedPageNumber);
}
private static void RepaginateDocument(object document)
{
InvokeComMethod(document, "Repaginate");
}
private static void SetCellText(dynamic row, int columnIndex, string value, bool centerAlign) private static void SetCellText(dynamic row, int columnIndex, string value, bool centerAlign)
{ {
dynamic cell = null; dynamic cell = null;
@@ -274,7 +388,56 @@ namespace XLAB2
} }
} }
private static IReadOnlyList<PrintedGroupRow> BuildPrintedGroups(IEnumerable<PsvDocumentLine> lines, bool includeClosedNotes) private static int InvokeComIndexedIntProperty(object target, string propertyName, object argument)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
return Convert.ToInt32(
target.GetType().InvokeMember(
propertyName,
BindingFlags.GetProperty,
null,
target,
new[] { argument }),
CultureInfo.InvariantCulture);
}
private static int InvokeComIntMethod(object target, string methodName, object argument)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
return Convert.ToInt32(
target.GetType().InvokeMember(
methodName,
BindingFlags.InvokeMethod,
null,
target,
new[] { argument }),
CultureInfo.InvariantCulture);
}
private static void InvokeComMethod(object target, string methodName)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
target.GetType().InvokeMember(
methodName,
BindingFlags.InvokeMethod,
null,
target,
Array.Empty<object>());
}
private static IReadOnlyList<PrintedGroupRow> BuildPrintedGroups(IEnumerable<PsvDocumentLine> lines, bool includeClosedDetails)
{ {
return (lines ?? Enumerable.Empty<PsvDocumentLine>()) return (lines ?? Enumerable.Empty<PsvDocumentLine>())
.GroupBy(delegate(PsvDocumentLine line) .GroupBy(delegate(PsvDocumentLine line)
@@ -302,7 +465,10 @@ namespace XLAB2
GroupCount = materializedLines.Count, GroupCount = materializedLines.Count,
PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }), PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }),
FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }), FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }),
Notes = includeClosedNotes ? BuildClosedNotesText(materializedLines) : string.Empty VerificationDocumentText = includeClosedDetails ? BuildVerificationDocumentText(materializedLines) : string.Empty,
StickerNumberText = includeClosedDetails ? BuildStickerNumberText(materializedLines) : string.Empty,
VerifierNameText = includeClosedDetails ? BuildVerifierNameText(materializedLines) : string.Empty,
Notes = BuildNotesText(materializedLines)
}; };
}) })
.ToList(); .ToList();
@@ -332,7 +498,7 @@ namespace XLAB2
return string.Empty; return string.Empty;
} }
if (serialNumbers.Count > 3) if (serialNumbers.Count > 10)
{ {
return string.Format(CultureInfo.InvariantCulture, "{0} зав. номеров", serialNumbers.Count); return string.Format(CultureInfo.InvariantCulture, "{0} зав. номеров", serialNumbers.Count);
} }
@@ -340,35 +506,44 @@ namespace XLAB2
return string.Join(", ", serialNumbers.ToArray()); return string.Join(", ", serialNumbers.ToArray());
} }
private static string BuildClosedNotesText(IReadOnlyList<PsvDocumentLine> lines) private static string BuildNotesText(IReadOnlyList<PsvDocumentLine> lines)
{ {
var parts = new List<string>(); return string.Join("; ", lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line == null ? null : line.Notes); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
var verificationDocuments = lines private static string BuildVerificationDocumentText(IReadOnlyList<PsvDocumentLine> lines)
{
return string.Join("; ", lines
.Select(FormatVerificationDocument) .Select(FormatVerificationDocument)
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); }) .Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase) .OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToList(); .ToArray());
}
if (verificationDocuments.Count > 0) private static string BuildStickerNumberText(IReadOnlyList<PsvDocumentLine> lines)
{ {
parts.Add("Документы: " + string.Join("; ", verificationDocuments.ToArray())); return string.Join(", ", lines
} .Select(delegate(PsvDocumentLine line) { return NormalizeText(line == null ? null : line.StickerNumber); })
var stickerNumbers = lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line.StickerNumber); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); }) .Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase) .OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToList(); .ToArray());
}
if (stickerNumbers.Count > 0) private static string BuildVerifierNameText(IReadOnlyList<PsvDocumentLine> lines)
{ {
parts.Add("Наклейки: " + string.Join(", ", stickerNumbers.ToArray())); return string.Join("; ", lines
} .Select(delegate(PsvDocumentLine line) { return NormalizeText(line == null ? null : line.VerifierName); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
return string.Join(". ", parts.ToArray()); .Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
} }
private static string FormatVerificationDocument(PsvDocumentLine line) private static string FormatVerificationDocument(PsvDocumentLine line)
@@ -445,6 +620,7 @@ namespace XLAB2
var candidates = new[] var candidates = new[]
{ {
Path.Combine(baseDirectory, fileName), Path.Combine(baseDirectory, fileName),
Path.GetFullPath(Path.Combine(baseDirectory, "..", fileName)),
Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", fileName)), Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", fileName)),
Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", fileName)) Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", fileName))
}; };
@@ -551,6 +727,12 @@ namespace XLAB2
public string RangeText { get; set; } public string RangeText { get; set; }
public string SerialNumberText { get; set; } public string SerialNumberText { get; set; }
public string StickerNumberText { get; set; }
public string VerificationDocumentText { get; set; }
public string VerifierNameText { get; set; }
} }
private sealed class PrintGroupKey : IEquatable<PrintGroupKey> private sealed class PrintGroupKey : IEquatable<PrintGroupKey>

View File

@@ -53,9 +53,14 @@ namespace XLAB2
} }
public static List<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, string parentTableName, int id, IEnumerable<string> excludedChildTables = null) public static List<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, string parentTableName, int id, IEnumerable<string> excludedChildTables = null)
{
return LoadDeleteBlockersFromForeignKeys(connection, null, parentTableName, id, excludedChildTables);
}
public static List<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, SqlTransaction transaction, string parentTableName, int id, IEnumerable<string> excludedChildTables = null)
{ {
var excluded = new HashSet<string>(excludedChildTables ?? Enumerable.Empty<string>(), StringComparer.OrdinalIgnoreCase); var excluded = new HashSet<string>(excludedChildTables ?? Enumerable.Empty<string>(), StringComparer.OrdinalIgnoreCase);
var metadata = LoadForeignKeyMetadata(connection, parentTableName) var metadata = LoadForeignKeyMetadata(connection, transaction, parentTableName)
.Where(delegate(ForeignKeyMetadata item) { return !excluded.Contains(item.TableName); }) .Where(delegate(ForeignKeyMetadata item) { return !excluded.Contains(item.TableName); })
.GroupBy(delegate(ForeignKeyMetadata item) { return item.SchemaName + "." + item.TableName; }) .GroupBy(delegate(ForeignKeyMetadata item) { return item.SchemaName + "." + item.TableName; })
.Select(delegate(IGrouping<string, ForeignKeyMetadata> group) .Select(delegate(IGrouping<string, ForeignKeyMetadata> group)
@@ -85,6 +90,7 @@ namespace XLAB2
using (var command = new SqlCommand(sql, connection)) using (var command = new SqlCommand(sql, connection))
{ {
command.Transaction = transaction;
command.CommandTimeout = GetCommandTimeoutSeconds(); command.CommandTimeout = GetCommandTimeoutSeconds();
command.Parameters.Add("@Id", SqlDbType.Int).Value = id; command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
var rowCount = Convert.ToInt32(command.ExecuteScalar()); var rowCount = Convert.ToInt32(command.ExecuteScalar());
@@ -182,7 +188,7 @@ namespace XLAB2
command.Parameters.Add(name, SqlDbType.VarChar, -1).Value = (object)value ?? DBNull.Value; command.Parameters.Add(name, SqlDbType.VarChar, -1).Value = (object)value ?? DBNull.Value;
} }
private static IReadOnlyList<ForeignKeyMetadata> LoadForeignKeyMetadata(SqlConnection connection, string parentTableName) private static IReadOnlyList<ForeignKeyMetadata> LoadForeignKeyMetadata(SqlConnection connection, SqlTransaction transaction, string parentTableName)
{ {
const string sql = @" const string sql = @"
SELECT SELECT
@@ -199,6 +205,7 @@ ORDER BY TableName, ColumnName;";
using (var command = new SqlCommand(sql, connection)) using (var command = new SqlCommand(sql, connection))
{ {
command.Transaction = transaction;
command.CommandTimeout = GetCommandTimeoutSeconds(); command.CommandTimeout = GetCommandTimeoutSeconds();
command.Parameters.Add("@ParentTableName", SqlDbType.VarChar, 128).Value = parentTableName; command.Parameters.Add("@ParentTableName", SqlDbType.VarChar, 128).Value = parentTableName;

View File

@@ -61,14 +61,17 @@
</DataGrid> </DataGrid>
<StackPanel Grid.Row="3" <StackPanel Grid.Row="3"
Margin="0,10,0,0" Margin="0,10,0,0">
Orientation="Horizontal"> <TextBlock Text="Заводские номера" />
<TextBlock Width="150" <TextBlock Margin="0,4,0,0"
Margin="0,0,8,0" Foreground="DimGray"
VerticalAlignment="Center" Text="Перечислите номера через запятую, точку с запятой или с новой строки." />
Text="Заводской номер" /> <TextBox Margin="0,6,0,0"
<TextBox Width="320" Height="96"
Text="{Binding SerialNumber, UpdateSourceTrigger=PropertyChanged}" /> AcceptsReturn="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
Text="{Binding SerialNumbersText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel> </StackPanel>
<TextBlock Grid.Row="4" <TextBlock Grid.Row="4"

View File

@@ -12,7 +12,7 @@ namespace XLAB2
{ {
private string _searchText; private string _searchText;
private AvailableInstrumentItem _selectedType; private AvailableInstrumentItem _selectedType;
private string _serialNumber; private string _serialNumbersText;
private string _statusText; private string _statusText;
public SelectInstrumentTypeWindowViewModel(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes) public SelectInstrumentTypeWindowViewModel(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes)
@@ -65,12 +65,12 @@ namespace XLAB2
} }
} }
public string SerialNumber public string SerialNumbersText
{ {
get { return _serialNumber; } get { return _serialNumbersText; }
set set
{ {
if (SetProperty(ref _serialNumber, value)) if (SetProperty(ref _serialNumbersText, value))
{ {
((RelayCommand)ConfirmCommand).RaiseCanExecuteChanged(); ((RelayCommand)ConfirmCommand).RaiseCanExecuteChanged();
UpdateStatus(); UpdateStatus();
@@ -86,12 +86,13 @@ namespace XLAB2
public InstrumentTypeSelectionResult GetResult() public InstrumentTypeSelectionResult GetResult()
{ {
var serialNumbers = ParseSerialNumbers(SerialNumbersText);
return SelectedType == null return SelectedType == null
? null ? null
: new InstrumentTypeSelectionResult : new InstrumentTypeSelectionResult
{ {
TypeItem = SelectedType, TypeItem = SelectedType,
SerialNumber = string.IsNullOrWhiteSpace(SerialNumber) ? string.Empty : SerialNumber.Trim() SerialNumbers = serialNumbers
}; };
} }
@@ -103,7 +104,7 @@ namespace XLAB2
private bool CanConfirm(object parameter) private bool CanConfirm(object parameter)
{ {
return SelectedType != null return SelectedType != null
&& !string.IsNullOrWhiteSpace(SerialNumber); && ParseSerialNumbers(SerialNumbersText).Count > 0;
} }
private void Confirm(object parameter) private void Confirm(object parameter)
@@ -138,6 +139,30 @@ namespace XLAB2
&& source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0; && source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
} }
private static List<string> ParseSerialNumbers(string value)
{
var serialNumbers = new List<string>();
var unique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(value))
{
return serialNumbers;
}
var separators = new[] { '\r', '\n', '\t', ',', ';' };
foreach (var token in value.Split(separators, StringSplitOptions.RemoveEmptyEntries))
{
var serialNumber = token.Trim();
if (serialNumber.Length == 0 || !unique.Add(serialNumber))
{
continue;
}
serialNumbers.Add(serialNumber);
}
return serialNumbers;
}
private void RaiseCloseRequested(bool? dialogResult) private void RaiseCloseRequested(bool? dialogResult)
{ {
var handler = CloseRequested; var handler = CloseRequested;
@@ -150,12 +175,13 @@ namespace XLAB2
private void UpdateStatus() private void UpdateStatus()
{ {
var visibleCount = InstrumentTypesView.Cast<object>().Count(); var visibleCount = InstrumentTypesView.Cast<object>().Count();
var serialCount = ParseSerialNumbers(SerialNumbersText).Count;
StatusText = string.Format( StatusText = string.Format(
"Всего типов: {0}. По фильтру: {1}. Выбран тип: {2}. Заводской номер: {3}.", "Всего типов: {0}. По фильтру: {1}. Выбран тип: {2}. Уникальных зав. №: {3}.",
InstrumentTypes.Count, InstrumentTypes.Count,
visibleCount, visibleCount,
SelectedType == null ? "нет" : "да", SelectedType == null ? "нет" : "да",
string.IsNullOrWhiteSpace(SerialNumber) ? "не указан" : "указан"); serialCount);
} }
} }
} }

View File

@@ -30,7 +30,8 @@
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" /> Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel> </StackPanel>
<DataGrid Grid.Row="2" <DataGrid x:Name="InstrumentsGrid"
Grid.Row="2"
ItemsSource="{Binding InstrumentsView}" ItemsSource="{Binding InstrumentsView}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"

View File

@@ -1,4 +1,5 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
namespace XLAB2 namespace XLAB2
{ {
@@ -13,6 +14,12 @@ namespace XLAB2
private void ViewModelOnCloseRequested(object sender, bool? dialogResult) private void ViewModelOnCloseRequested(object sender, bool? dialogResult)
{ {
if (dialogResult.GetValueOrDefault())
{
InstrumentsGrid.CommitEdit(DataGridEditingUnit.Cell, true);
InstrumentsGrid.CommitEdit(DataGridEditingUnit.Row, true);
}
DialogResult = dialogResult; DialogResult = dialogResult;
Close(); Close();
} }

View File

@@ -44,6 +44,53 @@
CanUserAddRows="False" CanUserAddRows="False"
IsReadOnly="True" IsReadOnly="True"
HeadersVisibility="Column"> HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить"
Command="{Binding EditCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить"
Command="{Binding DeleteCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="ID" <DataGridTextColumn Header="ID"
Width="90" Width="90"
@@ -66,18 +113,6 @@
Margin="0,12,0,0" Margin="0,12,0,0"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Right"> HorizontalAlignment="Right">
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding AddCommand}"
Content="Добавить" />
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding EditCommand}"
Content="Изменить" />
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding DeleteCommand}"
Content="Удалить" />
<Button Width="90" <Button Width="90"
IsCancel="True" IsCancel="True"
Content="Закрыть" /> Content="Закрыть" />

View File

@@ -1,4 +1,6 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB2 namespace XLAB2
{ {
@@ -13,6 +15,16 @@ namespace XLAB2
DataContext = _viewModel; DataContext = _viewModel;
} }
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row != null)
{
row.IsSelected = true;
row.Focus();
}
}
private async void Window_Loaded(object sender, RoutedEventArgs e) private async void Window_Loaded(object sender, RoutedEventArgs e)
{ {
await _viewModel.InitializeAsync(); await _viewModel.InitializeAsync();

View File

@@ -44,6 +44,53 @@
CanUserAddRows="False" CanUserAddRows="False"
IsReadOnly="True" IsReadOnly="True"
HeadersVisibility="Column"> HeadersVisibility="Column">
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить"
Command="{Binding AddCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить"
Command="{Binding EditCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить"
Command="{Binding DeleteCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="DataGridRow_PreviewMouseRightButtonDown" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="ID" <DataGridTextColumn Header="ID"
Width="90" Width="90"
@@ -66,18 +113,6 @@
Margin="0,12,0,0" Margin="0,12,0,0"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Right"> HorizontalAlignment="Right">
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding AddCommand}"
Content="Добавить" />
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding EditCommand}"
Content="Изменить" />
<Button Width="110"
Margin="0,0,8,0"
Command="{Binding DeleteCommand}"
Content="Удалить" />
<Button Width="90" <Button Width="90"
IsCancel="True" IsCancel="True"
Content="Закрыть" /> Content="Закрыть" />

View File

@@ -1,4 +1,6 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace XLAB2 namespace XLAB2
{ {
@@ -13,6 +15,16 @@ namespace XLAB2
DataContext = _viewModel; DataContext = _viewModel;
} }
private void DataGridRow_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row != null)
{
row.IsSelected = true;
row.Focus();
}
}
private async void Window_Loaded(object sender, RoutedEventArgs e) private async void Window_Loaded(object sender, RoutedEventArgs e)
{ {
await _viewModel.InitializeAsync(); await _viewModel.InitializeAsync();

View File

@@ -30,11 +30,42 @@
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" <MenuItem Header="Добавить"
Command="{Binding AddCommand}" /> Command="{Binding AddCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" <MenuItem Header="Изменить"
Command="{Binding EditCommand}" /> Command="{Binding EditCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeleteCommand}" /> Command="{Binding DeleteCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>

View File

@@ -57,14 +57,54 @@
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" <MenuItem Header="Добавить"
Command="{Binding AddTipsCommand}" /> Command="{Binding AddTipsCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" <MenuItem Header="Изменить"
Command="{Binding EditTipsCommand}" /> Command="{Binding EditTipsCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" <MenuItem Header="Удалить"
Command="{Binding DeleteTipsCommand}" /> Command="{Binding DeleteTipsCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator /> <Separator />
<MenuItem Header="Виды клейм..." <MenuItem Header="Виды клейм..."
Command="{Binding ManageTpvdklCommand}" /> Command="{Binding ManageTpvdklCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Ellipse Canvas.Left="2.5" Canvas.Top="2.5" Width="11" Height="11" Fill="{StaticResource AppMenuIconWarningBrush}" />
<Path Fill="White" Data="M8,4 L8.9,6.2 L11.3,6.3 L9.4,7.8 L10.1,10.1 L8,8.8 L5.9,10.1 L6.6,7.8 L4.7,6.3 L7.1,6.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>
@@ -92,9 +132,40 @@
HeadersVisibility="Column"> HeadersVisibility="Column">
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" Command="{Binding AddTprzCommand}" /> <MenuItem Header="Добавить" Command="{Binding AddTprzCommand}">
<MenuItem Header="Изменить" Command="{Binding EditTprzCommand}" /> <MenuItem.Icon>
<MenuItem Header="Удалить" Command="{Binding DeleteTprzCommand}" /> <Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" Command="{Binding EditTprzCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" Command="{Binding DeleteTprzCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>
@@ -129,9 +200,40 @@
HeadersVisibility="Column"> HeadersVisibility="Column">
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" Command="{Binding AddTprmkCommand}" /> <MenuItem Header="Добавить" Command="{Binding AddTprmkCommand}">
<MenuItem Header="Изменить" Command="{Binding EditTprmkCommand}" /> <MenuItem.Icon>
<MenuItem Header="Удалить" Command="{Binding DeleteTprmkCommand}" /> <Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" Command="{Binding EditTprmkCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" Command="{Binding DeleteTprmkCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>
@@ -158,9 +260,40 @@
HeadersVisibility="Column"> HeadersVisibility="Column">
<DataGrid.ContextMenu> <DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить" Command="{Binding AddTprmcpCommand}" /> <MenuItem Header="Добавить" Command="{Binding AddTprmcpCommand}">
<MenuItem Header="Изменить" Command="{Binding EditTprmcpCommand}" /> <MenuItem.Icon>
<MenuItem Header="Удалить" Command="{Binding DeleteTprmcpCommand}" /> <Viewbox Width="14" Height="14">
<Grid Width="16" Height="16">
<Ellipse Width="14" Height="14" Fill="{StaticResource AppMenuIconAccentBrush}" />
<Rectangle Width="2" Height="8" Fill="White" RadiusX="1" RadiusY="1" />
<Rectangle Width="8" Height="2" Fill="White" RadiusX="1" RadiusY="1" />
</Grid>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Изменить" Command="{Binding EditTprmcpCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Path Fill="{StaticResource AppMenuIconAccentBrush}" Data="M11.7,1.4 L14.6,4.3 L5.5,13.4 L2.5,13.9 L3,10.9 Z" />
<Path Fill="#FFEAF3FB" Data="M10.7,2.4 L13.6,5.3 L12.8,6.1 L9.9,3.2 Z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Удалить" Command="{Binding DeleteTprmcpCommand}">
<MenuItem.Icon>
<Viewbox Width="14" Height="14">
<Canvas Width="16" Height="16">
<Rectangle Canvas.Left="4" Canvas.Top="5" Width="8" Height="8" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="3" Canvas.Top="3" Width="10" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="1.5" Width="4" Height="2" RadiusX="1" RadiusY="1" Fill="{StaticResource AppMenuIconDangerBrush}" />
<Rectangle Canvas.Left="6" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
<Rectangle Canvas.Left="9" Canvas.Top="6.5" Width="1" Height="5" Fill="White" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.RowStyle> <DataGrid.RowStyle>

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
namespace XLAB2
{
internal sealed class VerificationReportFilter
{
public int? CustomerId { get; set; }
public int? MeasurementAreaId { get; set; }
public DateTime? DateFrom { get; set; }
public DateTime? DateTo { get; set; }
}
internal sealed class VerificationReportSummary
{
public int TotalCount { get; set; }
public int GoodCount { get; set; }
public int RejectedCount { get; set; }
public int WithoutResultCount { get; set; }
public int IssuedCount { get; set; }
}
internal sealed class VerificationReportCustomerRow
{
public int? CustomerId { get; set; }
public string CustomerName { get; set; }
public int TotalCount { get; set; }
public int GoodCount { get; set; }
public int RejectedCount { get; set; }
public int WithoutResultCount { get; set; }
public int IssuedCount { get; set; }
}
internal sealed class VerificationReportMeasurementAreaRow
{
public int? MeasurementAreaId { get; set; }
public string MeasurementAreaName { get; set; }
public int TotalCount { get; set; }
public int GoodCount { get; set; }
public int RejectedCount { get; set; }
public int WithoutResultCount { get; set; }
public int IssuedCount { get; set; }
}
internal sealed class VerificationReportData
{
public VerificationReportData()
{
Summary = new VerificationReportSummary();
CustomerRows = Array.Empty<VerificationReportCustomerRow>();
MeasurementAreaRows = Array.Empty<VerificationReportMeasurementAreaRow>();
}
public VerificationReportSummary Summary { get; set; }
public IReadOnlyList<VerificationReportCustomerRow> CustomerRows { get; set; }
public IReadOnlyList<VerificationReportMeasurementAreaRow> MeasurementAreaRows { get; set; }
}
}

View File

@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Data;
using Microsoft.Data.SqlClient;
namespace XLAB2
{
internal sealed class VerificationReportsService
{
private const string ReportSourceSql = @"
FROM dbo.EKZMK m
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
LEFT JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
LEFT JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
LEFT JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
LEFT JOIN dbo.FRPD customers ON customers.IDFRPD = z.IDFRPDV
WHERE m.DTMKFK IS NOT NULL
AND (@DateFrom IS NULL OR m.DTMKFK >= @DateFrom)
AND (@DateToExclusive IS NULL OR m.DTMKFK < @DateToExclusive)
AND (@CustomerId IS NULL OR z.IDFRPDV = @CustomerId)
AND (@MeasurementAreaId IS NULL OR tips.IDSPOI = @MeasurementAreaId)";
public VerificationReportData LoadReport(VerificationReportFilter filter)
{
var normalizedFilter = NormalizeFilter(filter);
using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
{
connection.Open();
return new VerificationReportData
{
Summary = LoadSummary(connection, normalizedFilter),
CustomerRows = LoadCustomerRows(connection, normalizedFilter),
MeasurementAreaRows = LoadMeasurementAreaRows(connection, normalizedFilter)
};
}
}
public IReadOnlyList<DirectoryLookupItem> LoadCustomerItems()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT DISTINCT
customers.IDFRPD AS Id,
customers.NMFRPD AS Name
FROM dbo.EKZMK m
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
JOIN dbo.FRPD customers ON customers.IDFRPD = z.IDFRPDV
WHERE m.DTMKFK IS NOT NULL
AND NULLIF(LTRIM(RTRIM(customers.NMFRPD)), N'') IS NOT NULL
ORDER BY customers.NMFRPD, customers.IDFRPD;");
}
public IReadOnlyList<DirectoryLookupItem> LoadMeasurementAreaItems()
{
return ReferenceDirectorySqlHelpers.LoadLookupItems(@"
SELECT DISTINCT
areas.IDSPOI AS Id,
areas.NMOI AS Name
FROM dbo.EKZMK m
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
LEFT JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
LEFT JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
WHERE m.DTMKFK IS NOT NULL
AND NULLIF(LTRIM(RTRIM(areas.NMOI)), N'') IS NOT NULL
ORDER BY areas.NMOI, areas.IDSPOI;");
}
private static void AddFilterParameters(SqlCommand command, VerificationReportFilter filter)
{
command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@CustomerId", filter.CustomerId);
ReferenceDirectorySqlHelpers.AddNullableIntParameter(command, "@MeasurementAreaId", filter.MeasurementAreaId);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@DateFrom", filter.DateFrom);
ReferenceDirectorySqlHelpers.AddNullableDateTimeParameter(command, "@DateToExclusive", GetDateToExclusive(filter.DateTo));
}
private static DateTime? GetDateToExclusive(DateTime? dateTo)
{
return dateTo.HasValue ? dateTo.Value.Date.AddDays(1) : (DateTime?)null;
}
private static VerificationReportSummary LoadSummary(SqlConnection connection, VerificationReportFilter filter)
{
var sql = @"
SELECT
COUNT(*) AS TotalCount,
COALESCE(SUM(CASE WHEN m.GDN = 1 THEN 1 ELSE 0 END), 0) AS GoodCount,
COALESCE(SUM(CASE WHEN m.GDN = 0 THEN 1 ELSE 0 END), 0) AS RejectedCount,
COALESCE(SUM(CASE WHEN m.GDN IS NULL THEN 1 ELSE 0 END), 0) AS WithoutResultCount,
COALESCE(SUM(CASE WHEN m.DTVDM IS NOT NULL THEN 1 ELSE 0 END), 0) AS IssuedCount
" + ReportSourceSql + ";";
using (var command = new SqlCommand(sql, connection))
{
AddFilterParameters(command, filter);
using (var reader = command.ExecuteReader())
{
if (!reader.Read())
{
return new VerificationReportSummary();
}
return new VerificationReportSummary
{
TotalCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "TotalCount"),
GoodCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "GoodCount"),
RejectedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "RejectedCount"),
WithoutResultCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "WithoutResultCount"),
IssuedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "IssuedCount")
};
}
}
}
private static IReadOnlyList<VerificationReportCustomerRow> LoadCustomerRows(SqlConnection connection, VerificationReportFilter filter)
{
var sql = @"
SELECT
z.IDFRPDV AS CustomerId,
COALESCE(NULLIF(LTRIM(RTRIM(customers.NMFRPD)), N''), N'Не указано') AS CustomerName,
COUNT(*) AS TotalCount,
COALESCE(SUM(CASE WHEN m.GDN = 1 THEN 1 ELSE 0 END), 0) AS GoodCount,
COALESCE(SUM(CASE WHEN m.GDN = 0 THEN 1 ELSE 0 END), 0) AS RejectedCount,
COALESCE(SUM(CASE WHEN m.GDN IS NULL THEN 1 ELSE 0 END), 0) AS WithoutResultCount,
COALESCE(SUM(CASE WHEN m.DTVDM IS NOT NULL THEN 1 ELSE 0 END), 0) AS IssuedCount
" + ReportSourceSql + @"
GROUP BY
z.IDFRPDV,
COALESCE(NULLIF(LTRIM(RTRIM(customers.NMFRPD)), N''), N'Не указано')
ORDER BY
CustomerName;";
var rows = new List<VerificationReportCustomerRow>();
using (var command = new SqlCommand(sql, connection))
{
AddFilterParameters(command, filter);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
rows.Add(new VerificationReportCustomerRow
{
CustomerId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "CustomerId"),
CustomerName = ReferenceDirectorySqlHelpers.GetString(reader, "CustomerName"),
TotalCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "TotalCount"),
GoodCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "GoodCount"),
RejectedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "RejectedCount"),
WithoutResultCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "WithoutResultCount"),
IssuedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "IssuedCount")
});
}
}
}
return rows;
}
private static IReadOnlyList<VerificationReportMeasurementAreaRow> LoadMeasurementAreaRows(SqlConnection connection, VerificationReportFilter filter)
{
var sql = @"
SELECT
tips.IDSPOI AS MeasurementAreaId,
COALESCE(NULLIF(LTRIM(RTRIM(areas.NMOI)), N''), N'Не указано') AS MeasurementAreaName,
COUNT(*) AS TotalCount,
COALESCE(SUM(CASE WHEN m.GDN = 1 THEN 1 ELSE 0 END), 0) AS GoodCount,
COALESCE(SUM(CASE WHEN m.GDN = 0 THEN 1 ELSE 0 END), 0) AS RejectedCount,
COALESCE(SUM(CASE WHEN m.GDN IS NULL THEN 1 ELSE 0 END), 0) AS WithoutResultCount,
COALESCE(SUM(CASE WHEN m.DTVDM IS NOT NULL THEN 1 ELSE 0 END), 0) AS IssuedCount
" + ReportSourceSql + @"
GROUP BY
tips.IDSPOI,
COALESCE(NULLIF(LTRIM(RTRIM(areas.NMOI)), N''), N'Не указано')
ORDER BY
MeasurementAreaName;";
var rows = new List<VerificationReportMeasurementAreaRow>();
using (var command = new SqlCommand(sql, connection))
{
AddFilterParameters(command, filter);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
rows.Add(new VerificationReportMeasurementAreaRow
{
MeasurementAreaId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "MeasurementAreaId"),
MeasurementAreaName = ReferenceDirectorySqlHelpers.GetString(reader, "MeasurementAreaName"),
TotalCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "TotalCount"),
GoodCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "GoodCount"),
RejectedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "RejectedCount"),
WithoutResultCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "WithoutResultCount"),
IssuedCount = ReferenceDirectorySqlHelpers.GetInt32(reader, "IssuedCount")
});
}
}
}
return rows;
}
private static VerificationReportFilter NormalizeFilter(VerificationReportFilter filter)
{
var normalizedFilter = filter ?? new VerificationReportFilter();
var dateFrom = normalizedFilter.DateFrom.HasValue ? normalizedFilter.DateFrom.Value.Date : (DateTime?)null;
var dateTo = normalizedFilter.DateTo.HasValue ? normalizedFilter.DateTo.Value.Date : (DateTime?)null;
if (dateFrom.HasValue && dateTo.HasValue && dateFrom.Value > dateTo.Value)
{
throw new InvalidOperationException("Дата \"с\" не может быть позже даты \"по\".");
}
return new VerificationReportFilter
{
CustomerId = normalizedFilter.CustomerId,
MeasurementAreaId = normalizedFilter.MeasurementAreaId,
DateFrom = dateFrom,
DateTo = dateTo
};
}
}
}

View File

@@ -0,0 +1,238 @@
<Window x:Class="XLAB2.VerificationReportsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Отчеты"
Height="760"
Width="1280"
MinHeight="640"
MinWidth="1100"
Loaded="Window_Loaded"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox Grid.Row="0"
Header="Параметры отчета">
<Grid Margin="8">
<DockPanel LastChildFill="False">
<Button DockPanel.Dock="Right"
Width="130"
Margin="12,0,0,0"
Command="{Binding RefreshCommand}"
Content="Сформировать" />
<WrapPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Заказчик" />
<ComboBox Width="280"
ItemsSource="{Binding CustomerItems}"
SelectedValue="{Binding SelectedCustomerId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Область измерений" />
<ComboBox Width="280"
ItemsSource="{Binding MeasurementAreaItems}"
SelectedValue="{Binding SelectedMeasurementAreaId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsTextSearchEnabled="True" />
</StackPanel>
<StackPanel Margin="0,0,16,8">
<TextBlock Margin="0,0,0,4"
Text="Дата поверки с" />
<DatePicker Width="145"
SelectedDate="{Binding DateFrom, Mode=TwoWay}"
SelectedDateFormat="Short" />
</StackPanel>
<StackPanel Margin="0,0,0,8">
<TextBlock Margin="0,0,0,4"
Text="Дата поверки по" />
<DatePicker Width="145"
SelectedDate="{Binding DateTo, Mode=TwoWay}"
SelectedDateFormat="Short" />
</StackPanel>
</WrapPanel>
</DockPanel>
</Grid>
</GroupBox>
<GroupBox Grid.Row="1"
Margin="0,12,0,0"
Header="Итоги">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Margin="0,0,0,8"
Foreground="DimGray"
Text="{Binding PeriodText}" />
<UniformGrid Grid.Row="1"
Columns="5">
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Поверено" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.TotalCount}" />
</StackPanel>
</Border>
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Годен" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.GoodCount}" />
</StackPanel>
</Border>
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Забракован" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.RejectedCount}" />
</StackPanel>
</Border>
<Border Margin="0,0,8,0"
Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="Без результата" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.WithoutResultCount}" />
</StackPanel>
</Border>
<Border Padding="12,8"
Background="{StaticResource AppSurfaceBrush}"
BorderBrush="{StaticResource AppBorderBrush}"
BorderThickness="1">
<StackPanel>
<TextBlock Foreground="DimGray"
Text="С выдачей" />
<TextBlock FontSize="24"
FontWeight="SemiBold"
Text="{Binding Summary.IssuedCount}" />
</StackPanel>
</Border>
</UniformGrid>
</Grid>
</GroupBox>
<GroupBox Grid.Row="2"
Margin="0,12,0,0"
Header="Сводные таблицы">
<TabControl Margin="8">
<TabItem Header="По заказчикам">
<DataGrid ItemsSource="{Binding CustomerRows}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="Заказчик"
Width="360"
Binding="{Binding CustomerName}" />
<DataGridTextColumn Header="Поверено"
Width="110"
Binding="{Binding TotalCount}" />
<DataGridTextColumn Header="Годен"
Width="110"
Binding="{Binding GoodCount}" />
<DataGridTextColumn Header="Забракован"
Width="120"
Binding="{Binding RejectedCount}" />
<DataGridTextColumn Header="Без результата"
Width="125"
Binding="{Binding WithoutResultCount}" />
<DataGridTextColumn Header="С выдачей"
Width="110"
Binding="{Binding IssuedCount}" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="По видам измерений">
<DataGrid ItemsSource="{Binding MeasurementAreaRows}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="Область измерений"
Width="360"
Binding="{Binding MeasurementAreaName}" />
<DataGridTextColumn Header="Поверено"
Width="110"
Binding="{Binding TotalCount}" />
<DataGridTextColumn Header="Годен"
Width="110"
Binding="{Binding GoodCount}" />
<DataGridTextColumn Header="Забракован"
Width="120"
Binding="{Binding RejectedCount}" />
<DataGridTextColumn Header="Без результата"
Width="125"
Binding="{Binding WithoutResultCount}" />
<DataGridTextColumn Header="С выдачей"
Width="110"
Binding="{Binding IssuedCount}" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
</GroupBox>
<TextBlock Grid.Row="3"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<StackPanel Grid.Row="4"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
IsCancel="True"
Content="Закрыть" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,21 @@
using System.Windows;
namespace XLAB2
{
public partial class VerificationReportsWindow : Window
{
private readonly VerificationReportsWindowViewModel _viewModel;
public VerificationReportsWindow()
{
InitializeComponent();
_viewModel = new VerificationReportsWindowViewModel(new VerificationReportsService(), new DialogService(this));
DataContext = _viewModel;
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await _viewModel.InitializeAsync();
}
}
}

View File

@@ -0,0 +1,249 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace XLAB2
{
internal sealed class VerificationReportsWindowViewModel : ObservableObject
{
private readonly IDialogService _dialogService;
private readonly VerificationReportsService _service;
private DateTime? _dateFrom;
private DateTime? _dateTo;
private bool _isBusy;
private int _selectedCustomerId;
private int _selectedMeasurementAreaId;
private VerificationReportSummary _summary;
private string _statusText;
public VerificationReportsWindowViewModel(VerificationReportsService service, IDialogService dialogService)
{
_service = service;
_dialogService = dialogService;
CustomerItems = new ObservableCollection<DirectoryLookupItem>();
MeasurementAreaItems = new ObservableCollection<DirectoryLookupItem>();
CustomerRows = new ObservableCollection<VerificationReportCustomerRow>();
MeasurementAreaRows = new ObservableCollection<VerificationReportMeasurementAreaRow>();
Summary = new VerificationReportSummary();
CustomerItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все заказчики" });
MeasurementAreaItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все области измерений" });
RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });
UpdateStatus();
}
public ObservableCollection<DirectoryLookupItem> CustomerItems { get; private set; }
public ObservableCollection<VerificationReportCustomerRow> CustomerRows { get; private set; }
public DateTime? DateFrom
{
get { return _dateFrom; }
set
{
if (SetProperty(ref _dateFrom, value))
{
OnPropertyChanged("PeriodText");
}
}
}
public DateTime? DateTo
{
get { return _dateTo; }
set
{
if (SetProperty(ref _dateTo, value))
{
OnPropertyChanged("PeriodText");
}
}
}
public bool IsBusy
{
get { return _isBusy; }
private set
{
if (SetProperty(ref _isBusy, value))
{
((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
}
}
}
public ObservableCollection<DirectoryLookupItem> MeasurementAreaItems { get; private set; }
public ObservableCollection<VerificationReportMeasurementAreaRow> MeasurementAreaRows { get; private set; }
public string PeriodText
{
get
{
if (!DateFrom.HasValue && !DateTo.HasValue)
{
return "Период: все время.";
}
if (DateFrom.HasValue && DateTo.HasValue)
{
return string.Format("Период: с {0:d} по {1:d}.", DateFrom.Value, DateTo.Value);
}
if (DateFrom.HasValue)
{
return string.Format("Период: с {0:d}.", DateFrom.Value);
}
return string.Format("Период: по {0:d}.", DateTo.Value);
}
}
public ICommand RefreshCommand { get; private set; }
public int SelectedCustomerId
{
get { return _selectedCustomerId; }
set { SetProperty(ref _selectedCustomerId, value); }
}
public int SelectedMeasurementAreaId
{
get { return _selectedMeasurementAreaId; }
set { SetProperty(ref _selectedMeasurementAreaId, value); }
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public VerificationReportSummary Summary
{
get { return _summary; }
private set { SetProperty(ref _summary, value); }
}
public async Task InitializeAsync()
{
await ExecuteBusyOperationAsync(async delegate
{
await LoadFiltersAsync();
await RefreshCoreAsync();
});
}
private VerificationReportFilter BuildFilter()
{
if (DateFrom.HasValue && DateTo.HasValue && DateFrom.Value.Date > DateTo.Value.Date)
{
throw new InvalidOperationException("Дата \"с\" не может быть позже даты \"по\".");
}
return new VerificationReportFilter
{
CustomerId = SelectedCustomerId > 0 ? SelectedCustomerId : (int?)null,
MeasurementAreaId = SelectedMeasurementAreaId > 0 ? SelectedMeasurementAreaId : (int?)null,
DateFrom = DateFrom,
DateTo = DateTo
};
}
private async Task ExecuteBusyOperationAsync(Func<Task> operation)
{
try
{
IsBusy = true;
await operation();
}
catch (InvalidOperationException ex)
{
_dialogService.ShowWarning(ex.Message);
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
}
finally
{
IsBusy = false;
}
}
private async Task LoadFiltersAsync()
{
var customersTask = Task.Run(delegate { return _service.LoadCustomerItems(); });
var measurementAreasTask = Task.Run(delegate { return _service.LoadMeasurementAreaItems(); });
await Task.WhenAll(customersTask, measurementAreasTask);
ApplyLookupItems(CustomerItems, customersTask.Result, "Все заказчики");
ApplyLookupItems(MeasurementAreaItems, measurementAreasTask.Result, "Все области измерений");
if (!CustomerItems.Any(delegate(DirectoryLookupItem item) { return item.Id == SelectedCustomerId; }))
{
SelectedCustomerId = 0;
}
if (!MeasurementAreaItems.Any(delegate(DirectoryLookupItem item) { return item.Id == SelectedMeasurementAreaId; }))
{
SelectedMeasurementAreaId = 0;
}
}
private static void ApplyLookupItems(ObservableCollection<DirectoryLookupItem> target, System.Collections.Generic.IReadOnlyList<DirectoryLookupItem> source, string allItemText)
{
target.Clear();
target.Add(new DirectoryLookupItem { Id = 0, Name = allItemText });
foreach (var item in source)
{
target.Add(item);
}
}
private async Task RefreshCoreAsync()
{
var filter = BuildFilter();
var report = await Task.Run(delegate { return _service.LoadReport(filter); });
Summary = report.Summary ?? new VerificationReportSummary();
CustomerRows.Clear();
foreach (var row in report.CustomerRows ?? Array.Empty<VerificationReportCustomerRow>())
{
CustomerRows.Add(row);
}
MeasurementAreaRows.Clear();
foreach (var row in report.MeasurementAreaRows ?? Array.Empty<VerificationReportMeasurementAreaRow>())
{
MeasurementAreaRows.Add(row);
}
UpdateStatus();
}
private async void RefreshAsync()
{
await ExecuteBusyOperationAsync(RefreshCoreAsync);
}
private void UpdateStatus()
{
StatusText = string.Format(
"Заказчиков в своде: {0}. Видов измерений в своде: {1}. Поверено: {2}. Годен: {3}. Забракован: {4}.",
CustomerRows.Count,
MeasurementAreaRows.Count,
Summary == null ? 0 : Summary.TotalCount,
Summary == null ? 0 : Summary.GoodCount,
Summary == null ? 0 : Summary.RejectedCount);
}
}
}

View File

@@ -5,9 +5,18 @@
<TargetFramework>net10.0-windows</TargetFramework> <TargetFramework>net10.0-windows</TargetFramework>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<ApplicationIcon>Assets\XlabApp.ico</ApplicationIcon>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Resource Include="Assets\XlabApp.ico" />
</ItemGroup>
<ItemGroup>
<Compile Remove="obj-temp-*\**\*.cs;bin-temp-*\**\*.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
@@ -23,16 +32,17 @@
<Content Include="appsettings.json"> <Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="..\ClosePsv.docx"> <Content Include="Assets\document-number-directory.json">
<Link>ClosePsv.docx</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="ClosePsv.docx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="..\Izv.docx"> <Content Include="..\Izv.docx">
<Link>Izv.docx</Link> <Link>Izv.docx</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="..\OpenPsv.docx"> <Content Include="OpenPsv.docx">
<Link>OpenPsv.docx</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="..\Svid.docx"> <Content Include="..\Svid.docx">

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More