edit
This commit is contained in:
58
XLAB2/EkzDirectoryDialogService.cs
Normal file
58
XLAB2/EkzDirectoryDialogService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
XLAB2/EkzDirectoryModels.cs
Normal file
132
XLAB2/EkzDirectoryModels.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
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 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 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;
|
||||
}
|
||||
}
|
||||
832
XLAB2/EkzDirectoryService.cs
Normal file
832
XLAB2/EkzDirectoryService.cs
Normal file
@@ -0,0 +1,832 @@
|
||||
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,
|
||||
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
|
||||
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"),
|
||||
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.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
|
||||
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"),
|
||||
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("Экземпляр с таким типоразмером, владельцем и заводским номером уже существует.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
170
XLAB2/EkzDirectoryWindow.xaml
Normal file
170
XLAB2/EkzDirectoryWindow.xaml
Normal file
@@ -0,0 +1,170 @@
|
||||
<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 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="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>
|
||||
33
XLAB2/EkzDirectoryWindow.xaml.cs
Normal file
33
XLAB2/EkzDirectoryWindow.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
566
XLAB2/EkzDirectoryWindowViewModel.cs
Normal file
566
XLAB2/EkzDirectoryWindowViewModel.cs
Normal file
@@ -0,0 +1,566 @@
|
||||
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,
|
||||
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.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
109
XLAB2/EkzEditWindow.xaml
Normal 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>
|
||||
20
XLAB2/EkzEditWindow.xaml.cs
Normal file
20
XLAB2/EkzEditWindow.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
185
XLAB2/EkzEditWindowViewModel.cs
Normal file
185
XLAB2/EkzEditWindowViewModel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,10 @@
|
||||
Click="PrsnDirectoryMenuItem_Click" />
|
||||
<MenuItem Header="Типоразмеры"
|
||||
Click="TypeSizeDirectoryMenuItem_Click" />
|
||||
<MenuItem Header="Экземпляры"
|
||||
Click="EkzDirectoryMenuItem_Click" />
|
||||
<MenuItem Header="Отчеты"
|
||||
Click="VerificationReportsMenuItem_Click" />
|
||||
</Menu>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
@@ -304,7 +308,28 @@
|
||||
|
||||
<GroupBox Grid.Row="1" Header="Группы приборов выбранного документа">
|
||||
<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}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
|
||||
@@ -86,6 +86,20 @@ namespace XLAB2
|
||||
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)
|
||||
{
|
||||
var window = new SpoiDirectoryWindow();
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace XLAB2
|
||||
private string _documentNumberEditor;
|
||||
private string _documentStatusText;
|
||||
private string _detailTableCountText;
|
||||
private string _groupFilterText;
|
||||
private string _groupDetailFilterText;
|
||||
private string _headerDepartmentName;
|
||||
private int _headerInstrumentCount;
|
||||
@@ -50,6 +51,9 @@ namespace XLAB2
|
||||
DocumentsView = CollectionViewSource.GetDefaultView(Documents);
|
||||
DocumentsView.Filter = FilterDocuments;
|
||||
|
||||
DocumentGroupsView = CollectionViewSource.GetDefaultView(DocumentGroupSummaries);
|
||||
DocumentGroupsView.Filter = FilterDocumentGroups;
|
||||
|
||||
DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines);
|
||||
DocumentLinesView.Filter = FilterDocumentLines;
|
||||
|
||||
@@ -132,6 +136,8 @@ namespace XLAB2
|
||||
|
||||
public ObservableCollection<PsvDocumentGroupSummary> DocumentGroupSummaries { get; private set; }
|
||||
|
||||
public ICollectionView DocumentGroupsView { get; private set; }
|
||||
|
||||
public string DocumentStatusText
|
||||
{
|
||||
get { return _documentStatusText; }
|
||||
@@ -154,6 +160,18 @@ namespace XLAB2
|
||||
|
||||
public ICommand DeleteSelectedGroupsCommand { get; private set; }
|
||||
|
||||
public string GroupFilterText
|
||||
{
|
||||
get { return _groupFilterText; }
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _groupFilterText, value))
|
||||
{
|
||||
RefreshDocumentGroupsView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string GroupDetailFilterText
|
||||
{
|
||||
get { return _groupDetailFilterText; }
|
||||
@@ -466,6 +484,23 @@ namespace XLAB2
|
||||
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 bool HasVerificationData(PsvDocumentLine line)
|
||||
{
|
||||
return line != null
|
||||
@@ -1662,6 +1697,7 @@ namespace XLAB2
|
||||
document.PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; });
|
||||
document.FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; });
|
||||
document.IssuedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IssuedOn.HasValue; });
|
||||
document.SerialNumbersText = BuildSerialNumbersText(materializedLines);
|
||||
}
|
||||
|
||||
private async Task ExecuteBusyOperationAsync(Func<Task> operation)
|
||||
@@ -1701,7 +1737,8 @@ namespace XLAB2
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(DocumentFilterText)
|
||||
&& !Contains(document.DocumentNumber, DocumentFilterText)
|
||||
&& !Contains(document.CustomerName, DocumentFilterText))
|
||||
&& !Contains(document.CustomerName, DocumentFilterText)
|
||||
&& !Contains(document.SerialNumbersText, DocumentFilterText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1709,6 +1746,25 @@ namespace XLAB2
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool FilterDocumentGroups(object item)
|
||||
{
|
||||
var group = item as PsvDocumentGroupSummary;
|
||||
if (group == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(GroupFilterText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Contains(group.InstrumentType, GroupFilterText)
|
||||
|| Contains(group.RangeText, GroupFilterText)
|
||||
|| Contains(group.RegistryNumber, GroupFilterText)
|
||||
|| Contains(group.SerialNumbersText, GroupFilterText);
|
||||
}
|
||||
|
||||
private string BuildDocumentStatusText(int count)
|
||||
{
|
||||
if (ShowClosedDocuments)
|
||||
@@ -1753,6 +1809,26 @@ 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)
|
||||
{
|
||||
return left != null
|
||||
@@ -2068,7 +2144,7 @@ namespace XLAB2
|
||||
|
||||
if (SelectedDocument.IsDraft)
|
||||
{
|
||||
SelectedDocument.ItemCount = pendingLines.Count;
|
||||
UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines);
|
||||
}
|
||||
|
||||
LoadSelectedDocumentAsync();
|
||||
@@ -2176,7 +2252,7 @@ namespace XLAB2
|
||||
|
||||
if (SelectedDocument.IsDraft)
|
||||
{
|
||||
SelectedDocument.ItemCount = pendingLines.Count;
|
||||
UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines);
|
||||
}
|
||||
|
||||
LoadSelectedDocumentAsync();
|
||||
@@ -2196,7 +2272,8 @@ namespace XLAB2
|
||||
}
|
||||
|
||||
RebuildDocumentGroupSummaries(DocumentLines);
|
||||
SelectedDocumentGroup = FindMatchingGroup(previousGroup) ?? DocumentGroupSummaries.FirstOrDefault();
|
||||
SelectedDocumentGroup = FindMatchingVisibleGroup(previousGroup)
|
||||
?? GetVisibleDocumentGroups().FirstOrDefault();
|
||||
SelectedDocumentLine = previousLine == null
|
||||
? null
|
||||
: DocumentLines.FirstOrDefault(delegate(PsvDocumentLine line)
|
||||
@@ -2211,6 +2288,7 @@ namespace XLAB2
|
||||
&& string.Equals(line.DuplicateKey, previousLine.DuplicateKey, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
HeaderInstrumentCount = DocumentLines.Count;
|
||||
RefreshDocumentGroupsView();
|
||||
RefreshDocumentLinesView();
|
||||
RaiseCommandStates();
|
||||
}
|
||||
@@ -2255,6 +2333,7 @@ namespace XLAB2
|
||||
InstrumentType = group.Key.InstrumentType,
|
||||
RangeText = group.Key.RangeText,
|
||||
RegistryNumber = group.Key.RegistryNumber,
|
||||
SerialNumbersText = BuildSerialNumbersText(group),
|
||||
IsBatchSelected = checkedGroups.Any(delegate(PsvDocumentGroupSummary previous)
|
||||
{
|
||||
return string.Equals(previous.InstrumentType ?? string.Empty, group.Key.InstrumentType, StringComparison.OrdinalIgnoreCase)
|
||||
@@ -2274,6 +2353,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()
|
||||
{
|
||||
DocumentLinesView.Refresh();
|
||||
@@ -2456,6 +2565,8 @@ namespace XLAB2
|
||||
|
||||
private void UpdateLineStatus()
|
||||
{
|
||||
var visibleGroupCount = GetVisibleDocumentGroups().Count;
|
||||
|
||||
if (SelectedDocument == null)
|
||||
{
|
||||
DetailTableCountText = "Приборов в таблице: 0.";
|
||||
@@ -2472,10 +2583,19 @@ namespace XLAB2
|
||||
return;
|
||||
}
|
||||
|
||||
if (visibleGroupCount == 0)
|
||||
{
|
||||
DetailTableCountText = "Приборов в таблице: 0.";
|
||||
LineStatusText = string.IsNullOrWhiteSpace(GroupFilterText)
|
||||
? "Выберите группу."
|
||||
: string.Format("Группы по фильтру \"{0}\" не найдены.", GroupFilterText.Trim());
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedDocumentGroup == null)
|
||||
{
|
||||
DetailTableCountText = "Приборов в таблице: 0.";
|
||||
LineStatusText = string.Format("Групп: {0}. Выберите группу.", DocumentGroupSummaries.Count);
|
||||
LineStatusText = string.Format("Групп: {0}/{1}. Выберите группу.", visibleGroupCount, DocumentGroupSummaries.Count);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2489,7 +2609,8 @@ namespace XLAB2
|
||||
DetailTableCountText = string.Format("Приборов в таблице: {0}.", filteredCount);
|
||||
|
||||
LineStatusText = string.Format(
|
||||
"Групп: {0}. Приборов в выбранной группе: {1}. Отображено по фильтру: {2}. Не сохранено строк: {3}.",
|
||||
"Групп: {0}/{1}. Приборов в выбранной группе: {2}. Отображено по фильтру: {3}. Не сохранено строк: {4}.",
|
||||
visibleGroupCount,
|
||||
DocumentGroupSummaries.Count,
|
||||
groupLineCount,
|
||||
filteredCount,
|
||||
|
||||
@@ -724,12 +724,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;
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<PsvDocumentSummary>> LoadDocumentsAsync(bool loadClosedDocumentsForCurrentYear, CancellationToken cancellationToken = default)
|
||||
public async Task<IReadOnlyList<PsvDocumentSummary>> LoadDocumentsAsync(bool loadClosedDocumentsForCurrentYear, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var sql = @"
|
||||
SELECT
|
||||
@@ -760,12 +769,25 @@ ORDER BY " + (loadClosedDocumentsForCurrentYear
|
||||
var currentYearStart = new DateTime(today.Year, 1, 1);
|
||||
var nextYearStart = currentYearStart.AddYears(1);
|
||||
|
||||
return SqlServerConnectionFactory.Current.QueryOpenConnectionAsync(
|
||||
sql,
|
||||
delegate(SqlDataReader reader)
|
||||
var documents = new List<PsvDocumentSummary>();
|
||||
|
||||
await using (var connection = await SqlServerConnectionFactory.Current.OpenConnectionAsync(cancellationToken).ConfigureAwait(false))
|
||||
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");
|
||||
return new PsvDocumentSummary
|
||||
documents.Add(new PsvDocumentSummary
|
||||
{
|
||||
DocumentKey = documentNumber,
|
||||
DocumentNumber = documentNumber,
|
||||
@@ -779,9 +801,48 @@ ORDER BY " + (loadClosedDocumentsForCurrentYear
|
||||
PassedCount = GetInt32(reader, "PassedCount"),
|
||||
FailedCount = GetInt32(reader, "FailedCount"),
|
||||
IsDraft = false
|
||||
};
|
||||
},
|
||||
delegate(SqlCommand command)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -790,11 +851,89 @@ ORDER BY " + (loadClosedDocumentsForCurrentYear
|
||||
command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart;
|
||||
command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart;
|
||||
}
|
||||
},
|
||||
cancellationToken).ContinueWith<IReadOnlyList<PsvDocumentSummary>>(delegate(Task<List<PsvDocumentSummary>> task)
|
||||
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
return task.Result;
|
||||
}, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
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)
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace XLAB2
|
||||
private DateTime? _issuedOn;
|
||||
private int _itemCount;
|
||||
private int _passedCount;
|
||||
private string _serialNumbersText;
|
||||
|
||||
public DateTime? AcceptedOn
|
||||
{
|
||||
@@ -89,6 +90,12 @@ namespace XLAB2
|
||||
set { SetProperty(ref _documentNumber, value); }
|
||||
}
|
||||
|
||||
public string SerialNumbersText
|
||||
{
|
||||
get { return _serialNumbersText; }
|
||||
set { SetProperty(ref _serialNumbersText, value); }
|
||||
}
|
||||
|
||||
public int FailedCount
|
||||
{
|
||||
get { return _failedCount; }
|
||||
@@ -346,6 +353,8 @@ namespace XLAB2
|
||||
|
||||
public string RegistryNumber { get; set; }
|
||||
|
||||
public string SerialNumbersText { get; set; }
|
||||
|
||||
public int InVerificationCount { get; set; }
|
||||
|
||||
public int VerifiedCount { get; set; }
|
||||
|
||||
@@ -53,9 +53,14 @@ namespace XLAB2
|
||||
}
|
||||
|
||||
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 metadata = LoadForeignKeyMetadata(connection, parentTableName)
|
||||
var metadata = LoadForeignKeyMetadata(connection, transaction, parentTableName)
|
||||
.Where(delegate(ForeignKeyMetadata item) { return !excluded.Contains(item.TableName); })
|
||||
.GroupBy(delegate(ForeignKeyMetadata item) { return item.SchemaName + "." + item.TableName; })
|
||||
.Select(delegate(IGrouping<string, ForeignKeyMetadata> group)
|
||||
@@ -85,6 +90,7 @@ namespace XLAB2
|
||||
|
||||
using (var command = new SqlCommand(sql, connection))
|
||||
{
|
||||
command.Transaction = transaction;
|
||||
command.CommandTimeout = GetCommandTimeoutSeconds();
|
||||
command.Parameters.Add("@Id", SqlDbType.Int).Value = id;
|
||||
var rowCount = Convert.ToInt32(command.ExecuteScalar());
|
||||
@@ -182,7 +188,7 @@ namespace XLAB2
|
||||
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 = @"
|
||||
SELECT
|
||||
@@ -199,6 +205,7 @@ ORDER BY TableName, ColumnName;";
|
||||
|
||||
using (var command = new SqlCommand(sql, connection))
|
||||
{
|
||||
command.Transaction = transaction;
|
||||
command.CommandTimeout = GetCommandTimeoutSeconds();
|
||||
command.Parameters.Add("@ParentTableName", SqlDbType.VarChar, 128).Value = parentTableName;
|
||||
|
||||
|
||||
79
XLAB2/VerificationReportsModels.cs
Normal file
79
XLAB2/VerificationReportsModels.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
228
XLAB2/VerificationReportsService.cs
Normal file
228
XLAB2/VerificationReportsService.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
238
XLAB2/VerificationReportsWindow.xaml
Normal file
238
XLAB2/VerificationReportsWindow.xaml
Normal 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>
|
||||
21
XLAB2/VerificationReportsWindow.xaml.cs
Normal file
21
XLAB2/VerificationReportsWindow.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
249
XLAB2/VerificationReportsWindowViewModel.cs
Normal file
249
XLAB2/VerificationReportsWindowViewModel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/Azure.Core.dll
Normal file
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/Azure.Core.dll
Normal file
Binary file not shown.
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/Azure.Identity.dll
Normal file
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/Azure.Identity.dll
Normal file
Binary file not shown.
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/ClosePsv.docx
Normal file
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/ClosePsv.docx
Normal file
Binary file not shown.
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/Izv.docx
Normal file
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/Izv.docx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/OpenPsv.docx
Normal file
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/OpenPsv.docx
Normal file
Binary file not shown.
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/Svid.docx
Normal file
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/Svid.docx
Normal file
Binary file not shown.
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/System.ClientModel.dll
Normal file
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/System.ClientModel.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/System.Memory.Data.dll
Normal file
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/System.Memory.Data.dll
Normal file
Binary file not shown.
1007
XLAB2/bin-temp-verify-ekz-delete-cascade/XLAB2.deps.json
Normal file
1007
XLAB2/bin-temp-verify-ekz-delete-cascade/XLAB2.deps.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/XLAB2.dll
Normal file
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/XLAB2.dll
Normal file
Binary file not shown.
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/XLAB2.exe
Normal file
BIN
XLAB2/bin-temp-verify-ekz-delete-cascade/XLAB2.exe
Normal file
Binary file not shown.
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net10.0",
|
||||
"frameworks": [
|
||||
{
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "10.0.0"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.WindowsDesktop.App",
|
||||
"version": "10.0.0"
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
|
||||
"CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
|
||||
}
|
||||
}
|
||||
}
|
||||
18
XLAB2/bin-temp-verify-ekz-delete-cascade/appsettings.json
Normal file
18
XLAB2/bin-temp-verify-ekz-delete-cascade/appsettings.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Database": {
|
||||
"ApplicationName": "XLAB2",
|
||||
"CommandTimeoutSeconds": 60,
|
||||
"ConnectRetryCount": 3,
|
||||
"ConnectRetryIntervalSeconds": 10,
|
||||
"ConnectTimeoutSeconds": 15,
|
||||
"Database": "ASUMS",
|
||||
"Encrypt": false,
|
||||
"IntegratedSecurity": true,
|
||||
"MultipleActiveResultSets": true,
|
||||
"Pooling": true,
|
||||
"MaxPoolSize": 100,
|
||||
"MinPoolSize": 0,
|
||||
"Server": "SEVENHILL\\SQLEXPRESS",
|
||||
"TrustServerCertificate": true
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
XLAB2/bin-temp-verify-mainwindow-empty/Azure.Core.dll
Normal file
BIN
XLAB2/bin-temp-verify-mainwindow-empty/Azure.Core.dll
Normal file
Binary file not shown.
BIN
XLAB2/bin-temp-verify-mainwindow-empty/Azure.Identity.dll
Normal file
BIN
XLAB2/bin-temp-verify-mainwindow-empty/Azure.Identity.dll
Normal file
Binary file not shown.
BIN
XLAB2/bin-temp-verify-mainwindow-empty/ClosePsv.docx
Normal file
BIN
XLAB2/bin-temp-verify-mainwindow-empty/ClosePsv.docx
Normal file
Binary file not shown.
BIN
XLAB2/bin-temp-verify-mainwindow-empty/Izv.docx
Normal file
BIN
XLAB2/bin-temp-verify-mainwindow-empty/Izv.docx
Normal file
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
Reference in New Issue
Block a user