This commit is contained in:
Курнат Андрей
2026-03-22 21:44:29 +03:00
parent a47a7a5a3b
commit 7bbca6ba55
750 changed files with 13718 additions and 43 deletions

View File

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

132
XLAB2/EkzDirectoryModels.cs Normal file
View 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;
}
}

View 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("Экземпляр с таким типоразмером, владельцем и заводским номером уже существует.");
}
}
}
}
}

View 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>

View File

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

View File

@@ -0,0 +1,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
View File

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

View File

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

View File

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

View File

@@ -44,6 +44,10 @@
Click="PrsnDirectoryMenuItem_Click" /> Click="PrsnDirectoryMenuItem_Click" />
<MenuItem Header="Типоразмеры" <MenuItem Header="Типоразмеры"
Click="TypeSizeDirectoryMenuItem_Click" /> Click="TypeSizeDirectoryMenuItem_Click" />
<MenuItem Header="Экземпляры"
Click="EkzDirectoryMenuItem_Click" />
<MenuItem Header="Отчеты"
Click="VerificationReportsMenuItem_Click" />
</Menu> </Menu>
<Grid Grid.Row="1"> <Grid Grid.Row="1">
@@ -304,7 +308,28 @@
<GroupBox Grid.Row="1" Header="Группы приборов выбранного документа"> <GroupBox Grid.Row="1" Header="Группы приборов выбранного документа">
<Grid Margin="8"> <Grid Margin="8">
<DataGrid ItemsSource="{Binding DocumentGroupSummaries}" <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="290" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding GroupFilterText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Column="1"
Margin="12,0,0,0"
VerticalAlignment="Center"
Foreground="DimGray"
Text="Поиск по типу, диапазону, госреестру или зав. №" />
</Grid>
<DataGrid Grid.Row="1"
ItemsSource="{Binding DocumentGroupsView}"
SelectedItem="{Binding SelectedDocumentGroup, Mode=TwoWay}" SelectedItem="{Binding SelectedDocumentGroup, Mode=TwoWay}"
AutoGenerateColumns="False" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserAddRows="False"

View File

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

View File

@@ -20,6 +20,7 @@ namespace XLAB2
private string _documentNumberEditor; private string _documentNumberEditor;
private string _documentStatusText; private string _documentStatusText;
private string _detailTableCountText; private string _detailTableCountText;
private string _groupFilterText;
private string _groupDetailFilterText; private string _groupDetailFilterText;
private string _headerDepartmentName; private string _headerDepartmentName;
private int _headerInstrumentCount; private int _headerInstrumentCount;
@@ -50,6 +51,9 @@ namespace XLAB2
DocumentsView = CollectionViewSource.GetDefaultView(Documents); DocumentsView = CollectionViewSource.GetDefaultView(Documents);
DocumentsView.Filter = FilterDocuments; DocumentsView.Filter = FilterDocuments;
DocumentGroupsView = CollectionViewSource.GetDefaultView(DocumentGroupSummaries);
DocumentGroupsView.Filter = FilterDocumentGroups;
DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines); DocumentLinesView = CollectionViewSource.GetDefaultView(DocumentLines);
DocumentLinesView.Filter = FilterDocumentLines; DocumentLinesView.Filter = FilterDocumentLines;
@@ -132,6 +136,8 @@ namespace XLAB2
public ObservableCollection<PsvDocumentGroupSummary> DocumentGroupSummaries { get; private set; } public ObservableCollection<PsvDocumentGroupSummary> DocumentGroupSummaries { get; private set; }
public ICollectionView DocumentGroupsView { get; private set; }
public string DocumentStatusText public string DocumentStatusText
{ {
get { return _documentStatusText; } get { return _documentStatusText; }
@@ -154,6 +160,18 @@ namespace XLAB2
public ICommand DeleteSelectedGroupsCommand { get; private set; } public ICommand DeleteSelectedGroupsCommand { get; private set; }
public string GroupFilterText
{
get { return _groupFilterText; }
set
{
if (SetProperty(ref _groupFilterText, value))
{
RefreshDocumentGroupsView();
}
}
}
public string GroupDetailFilterText public string GroupDetailFilterText
{ {
get { return _groupDetailFilterText; } get { return _groupDetailFilterText; }
@@ -466,6 +484,23 @@ namespace XLAB2
return document != null && document.IssuedOn.HasValue; return document != null && document.IssuedOn.HasValue;
} }
private static string BuildSerialNumbersText(IEnumerable<PsvDocumentLine> lines)
{
var serialNumbers = (lines ?? Enumerable.Empty<PsvDocumentLine>())
.Select(delegate(PsvDocumentLine line)
{
return line == null || string.IsNullOrWhiteSpace(line.SerialNumber)
? null
: line.SerialNumber.Trim();
})
.Where(delegate(string serialNumber) { return !string.IsNullOrWhiteSpace(serialNumber); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string serialNumber) { return serialNumber; }, StringComparer.OrdinalIgnoreCase)
.ToList();
return serialNumbers.Count == 0 ? string.Empty : string.Join(", ", serialNumbers.ToArray());
}
private static bool HasVerificationData(PsvDocumentLine line) private static bool HasVerificationData(PsvDocumentLine line)
{ {
return line != null return line != null
@@ -1662,6 +1697,7 @@ namespace XLAB2
document.PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }); document.PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; });
document.FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }); document.FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; });
document.IssuedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IssuedOn.HasValue; }); document.IssuedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IssuedOn.HasValue; });
document.SerialNumbersText = BuildSerialNumbersText(materializedLines);
} }
private async Task ExecuteBusyOperationAsync(Func<Task> operation) private async Task ExecuteBusyOperationAsync(Func<Task> operation)
@@ -1701,7 +1737,8 @@ namespace XLAB2
if (!string.IsNullOrWhiteSpace(DocumentFilterText) if (!string.IsNullOrWhiteSpace(DocumentFilterText)
&& !Contains(document.DocumentNumber, DocumentFilterText) && !Contains(document.DocumentNumber, DocumentFilterText)
&& !Contains(document.CustomerName, DocumentFilterText)) && !Contains(document.CustomerName, DocumentFilterText)
&& !Contains(document.SerialNumbersText, DocumentFilterText))
{ {
return false; return false;
} }
@@ -1709,6 +1746,25 @@ namespace XLAB2
return true; return true;
} }
private bool FilterDocumentGroups(object item)
{
var group = item as PsvDocumentGroupSummary;
if (group == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(GroupFilterText))
{
return true;
}
return Contains(group.InstrumentType, GroupFilterText)
|| Contains(group.RangeText, GroupFilterText)
|| Contains(group.RegistryNumber, GroupFilterText)
|| Contains(group.SerialNumbersText, GroupFilterText);
}
private string BuildDocumentStatusText(int count) private string BuildDocumentStatusText(int count)
{ {
if (ShowClosedDocuments) 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) private static bool AreSameGroup(PsvDocumentGroupSummary left, PsvDocumentGroupSummary right)
{ {
return left != null return left != null
@@ -2068,7 +2144,7 @@ namespace XLAB2
if (SelectedDocument.IsDraft) if (SelectedDocument.IsDraft)
{ {
SelectedDocument.ItemCount = pendingLines.Count; UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines);
} }
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
@@ -2176,7 +2252,7 @@ namespace XLAB2
if (SelectedDocument.IsDraft) if (SelectedDocument.IsDraft)
{ {
SelectedDocument.ItemCount = pendingLines.Count; UpdateDocumentSummaryFromLines(SelectedDocument, pendingLines);
} }
LoadSelectedDocumentAsync(); LoadSelectedDocumentAsync();
@@ -2196,7 +2272,8 @@ namespace XLAB2
} }
RebuildDocumentGroupSummaries(DocumentLines); RebuildDocumentGroupSummaries(DocumentLines);
SelectedDocumentGroup = FindMatchingGroup(previousGroup) ?? DocumentGroupSummaries.FirstOrDefault(); SelectedDocumentGroup = FindMatchingVisibleGroup(previousGroup)
?? GetVisibleDocumentGroups().FirstOrDefault();
SelectedDocumentLine = previousLine == null SelectedDocumentLine = previousLine == null
? null ? null
: DocumentLines.FirstOrDefault(delegate(PsvDocumentLine line) : DocumentLines.FirstOrDefault(delegate(PsvDocumentLine line)
@@ -2211,6 +2288,7 @@ namespace XLAB2
&& string.Equals(line.DuplicateKey, previousLine.DuplicateKey, StringComparison.OrdinalIgnoreCase); && string.Equals(line.DuplicateKey, previousLine.DuplicateKey, StringComparison.OrdinalIgnoreCase);
}); });
HeaderInstrumentCount = DocumentLines.Count; HeaderInstrumentCount = DocumentLines.Count;
RefreshDocumentGroupsView();
RefreshDocumentLinesView(); RefreshDocumentLinesView();
RaiseCommandStates(); RaiseCommandStates();
} }
@@ -2255,6 +2333,7 @@ namespace XLAB2
InstrumentType = group.Key.InstrumentType, InstrumentType = group.Key.InstrumentType,
RangeText = group.Key.RangeText, RangeText = group.Key.RangeText,
RegistryNumber = group.Key.RegistryNumber, RegistryNumber = group.Key.RegistryNumber,
SerialNumbersText = BuildSerialNumbersText(group),
IsBatchSelected = checkedGroups.Any(delegate(PsvDocumentGroupSummary previous) IsBatchSelected = checkedGroups.Any(delegate(PsvDocumentGroupSummary previous)
{ {
return string.Equals(previous.InstrumentType ?? string.Empty, group.Key.InstrumentType, StringComparison.OrdinalIgnoreCase) return string.Equals(previous.InstrumentType ?? string.Empty, group.Key.InstrumentType, StringComparison.OrdinalIgnoreCase)
@@ -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() private void RefreshDocumentLinesView()
{ {
DocumentLinesView.Refresh(); DocumentLinesView.Refresh();
@@ -2456,6 +2565,8 @@ namespace XLAB2
private void UpdateLineStatus() private void UpdateLineStatus()
{ {
var visibleGroupCount = GetVisibleDocumentGroups().Count;
if (SelectedDocument == null) if (SelectedDocument == null)
{ {
DetailTableCountText = "Приборов в таблице: 0."; DetailTableCountText = "Приборов в таблице: 0.";
@@ -2472,10 +2583,19 @@ namespace XLAB2
return; return;
} }
if (visibleGroupCount == 0)
{
DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = string.IsNullOrWhiteSpace(GroupFilterText)
? "Выберите группу."
: string.Format("Группы по фильтру \"{0}\" не найдены.", GroupFilterText.Trim());
return;
}
if (SelectedDocumentGroup == null) if (SelectedDocumentGroup == null)
{ {
DetailTableCountText = "Приборов в таблице: 0."; DetailTableCountText = "Приборов в таблице: 0.";
LineStatusText = string.Format("Групп: {0}. Выберите группу.", DocumentGroupSummaries.Count); LineStatusText = string.Format("Групп: {0}/{1}. Выберите группу.", visibleGroupCount, DocumentGroupSummaries.Count);
return; return;
} }
@@ -2489,7 +2609,8 @@ namespace XLAB2
DetailTableCountText = string.Format("Приборов в таблице: {0}.", filteredCount); DetailTableCountText = string.Format("Приборов в таблице: {0}.", filteredCount);
LineStatusText = string.Format( LineStatusText = string.Format(
"Групп: {0}. Приборов в выбранной группе: {1}. Отображено по фильтру: {2}. Не сохранено строк: {3}.", "Групп: {0}/{1}. Приборов в выбранной группе: {2}. Отображено по фильтру: {3}. Не сохранено строк: {4}.",
visibleGroupCount,
DocumentGroupSummaries.Count, DocumentGroupSummaries.Count,
groupLineCount, groupLineCount,
filteredCount, filteredCount,

View File

@@ -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; return documents;
} }
public Task<IReadOnlyList<PsvDocumentSummary>> LoadDocumentsAsync(bool loadClosedDocumentsForCurrentYear, CancellationToken cancellationToken = default) public async Task<IReadOnlyList<PsvDocumentSummary>> LoadDocumentsAsync(bool loadClosedDocumentsForCurrentYear, CancellationToken cancellationToken = default)
{ {
var sql = @" var sql = @"
SELECT SELECT
@@ -760,41 +769,171 @@ ORDER BY " + (loadClosedDocumentsForCurrentYear
var currentYearStart = new DateTime(today.Year, 1, 1); var currentYearStart = new DateTime(today.Year, 1, 1);
var nextYearStart = currentYearStart.AddYears(1); var nextYearStart = currentYearStart.AddYears(1);
return SqlServerConnectionFactory.Current.QueryOpenConnectionAsync( var documents = new List<PsvDocumentSummary>();
sql,
delegate(SqlDataReader reader)
{
var documentNumber = GetString(reader, "DocumentNumber");
return new PsvDocumentSummary
{
DocumentKey = documentNumber,
DocumentNumber = documentNumber,
AcceptedOn = GetNullableDateTime(reader, "AcceptedOn"),
IssuedOn = GetNullableDateTime(reader, "IssuedOn"),
DepartmentName = GetString(reader, "DepartmentName"),
CustomerId = GetNullableInt32(reader, "CustomerId"),
CustomerName = GetString(reader, "CustomerName"),
ItemCount = GetInt32(reader, "ItemCount"),
IssuedCount = GetInt32(reader, "IssuedCount"),
PassedCount = GetInt32(reader, "PassedCount"),
FailedCount = GetInt32(reader, "FailedCount"),
IsDraft = false
};
},
delegate(SqlCommand command)
{
ConfigureCommandTimeout(command);
if (loadClosedDocumentsForCurrentYear) await using (var connection = await SqlServerConnectionFactory.Current.OpenConnectionAsync(cancellationToken).ConfigureAwait(false))
{ using (var command = new SqlCommand(sql, connection))
command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart; {
command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart; ConfigureCommandTimeout(command);
}
}, if (loadClosedDocumentsForCurrentYear)
cancellationToken).ContinueWith<IReadOnlyList<PsvDocumentSummary>>(delegate(Task<List<PsvDocumentSummary>> task)
{ {
return task.Result; command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart;
}, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart;
}
using (var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
{
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
var documentNumber = GetString(reader, "DocumentNumber");
documents.Add(new PsvDocumentSummary
{
DocumentKey = documentNumber,
DocumentNumber = documentNumber,
AcceptedOn = GetNullableDateTime(reader, "AcceptedOn"),
IssuedOn = GetNullableDateTime(reader, "IssuedOn"),
DepartmentName = GetString(reader, "DepartmentName"),
CustomerId = GetNullableInt32(reader, "CustomerId"),
CustomerName = GetString(reader, "CustomerName"),
ItemCount = GetInt32(reader, "ItemCount"),
IssuedCount = GetInt32(reader, "IssuedCount"),
PassedCount = GetInt32(reader, "PassedCount"),
FailedCount = GetInt32(reader, "FailedCount"),
IsDraft = false
});
}
}
var serialNumbersByDocument = await LoadDocumentSerialNumbersAsync(connection, loadClosedDocumentsForCurrentYear, currentYearStart, nextYearStart, cancellationToken).ConfigureAwait(false);
foreach (var document in documents)
{
if (serialNumbersByDocument.TryGetValue(document.DocumentNumber, out var serialNumbersText))
{
document.SerialNumbersText = serialNumbersText;
}
}
}
return documents;
}
private Dictionary<string, string> LoadDocumentSerialNumbers(SqlConnection connection, bool loadClosedDocumentsForCurrentYear, DateTime currentYearStart, DateTime nextYearStart)
{
var sql = @"
WITH filteredDocuments AS
(
SELECT m.NNZVPV
FROM dbo.EKZMK m
WHERE NULLIF(LTRIM(RTRIM(m.NNZVPV)), N'') IS NOT NULL
GROUP BY m.NNZVPV
HAVING " + (loadClosedDocumentsForCurrentYear
? "MAX(m.DTVDM) >= @IssuedFrom AND MAX(m.DTVDM) < @IssuedTo"
: "MAX(m.DTVDM) IS NULL") + @"
)
SELECT
m.NNZVPV AS DocumentNumber,
z.NNZV AS SerialNumber
FROM dbo.EKZMK m
JOIN filteredDocuments documents ON documents.NNZVPV = m.NNZVPV
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
WHERE NULLIF(LTRIM(RTRIM(z.NNZV)), N'') IS NOT NULL
ORDER BY m.NNZVPV, z.NNZV;";
var serialNumbersByDocument = new Dictionary<string, SortedSet<string>>(StringComparer.OrdinalIgnoreCase);
using (var command = new SqlCommand(sql, connection))
{
ConfigureCommandTimeout(command);
if (loadClosedDocumentsForCurrentYear)
{
command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart;
command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart;
}
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var documentNumber = GetString(reader, "DocumentNumber");
var serialNumber = GetString(reader, "SerialNumber");
if (string.IsNullOrWhiteSpace(documentNumber) || string.IsNullOrWhiteSpace(serialNumber))
{
continue;
}
if (!serialNumbersByDocument.TryGetValue(documentNumber, out var serialNumbers))
{
serialNumbers = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
serialNumbersByDocument[documentNumber] = serialNumbers;
}
serialNumbers.Add(serialNumber.Trim());
}
}
}
return serialNumbersByDocument.ToDictionary(pair => pair.Key, pair => string.Join(", ", pair.Value), StringComparer.OrdinalIgnoreCase);
}
private async Task<Dictionary<string, string>> LoadDocumentSerialNumbersAsync(SqlConnection connection, bool loadClosedDocumentsForCurrentYear, DateTime currentYearStart, DateTime nextYearStart, CancellationToken cancellationToken)
{
var sql = @"
WITH filteredDocuments AS
(
SELECT m.NNZVPV
FROM dbo.EKZMK m
WHERE NULLIF(LTRIM(RTRIM(m.NNZVPV)), N'') IS NOT NULL
GROUP BY m.NNZVPV
HAVING " + (loadClosedDocumentsForCurrentYear
? "MAX(m.DTVDM) >= @IssuedFrom AND MAX(m.DTVDM) < @IssuedTo"
: "MAX(m.DTVDM) IS NULL") + @"
)
SELECT
m.NNZVPV AS DocumentNumber,
z.NNZV AS SerialNumber
FROM dbo.EKZMK m
JOIN filteredDocuments documents ON documents.NNZVPV = m.NNZVPV
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
WHERE NULLIF(LTRIM(RTRIM(z.NNZV)), N'') IS NOT NULL
ORDER BY m.NNZVPV, z.NNZV;";
var serialNumbersByDocument = new Dictionary<string, SortedSet<string>>(StringComparer.OrdinalIgnoreCase);
using (var command = new SqlCommand(sql, connection))
{
ConfigureCommandTimeout(command);
if (loadClosedDocumentsForCurrentYear)
{
command.Parameters.Add("@IssuedFrom", SqlDbType.DateTime).Value = currentYearStart;
command.Parameters.Add("@IssuedTo", SqlDbType.DateTime).Value = nextYearStart;
}
using (var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
{
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
var documentNumber = GetString(reader, "DocumentNumber");
var serialNumber = GetString(reader, "SerialNumber");
if (string.IsNullOrWhiteSpace(documentNumber) || string.IsNullOrWhiteSpace(serialNumber))
{
continue;
}
if (!serialNumbersByDocument.TryGetValue(documentNumber, out var serialNumbers))
{
serialNumbers = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
serialNumbersByDocument[documentNumber] = serialNumbers;
}
serialNumbers.Add(serialNumber.Trim());
}
}
}
return serialNumbersByDocument.ToDictionary(pair => pair.Key, pair => string.Join(", ", pair.Value), StringComparer.OrdinalIgnoreCase);
} }
public IReadOnlyList<PsvDocumentLine> LoadDocumentLines(string documentNumber) public IReadOnlyList<PsvDocumentLine> LoadDocumentLines(string documentNumber)

View File

@@ -40,6 +40,7 @@ namespace XLAB2
private DateTime? _issuedOn; private DateTime? _issuedOn;
private int _itemCount; private int _itemCount;
private int _passedCount; private int _passedCount;
private string _serialNumbersText;
public DateTime? AcceptedOn public DateTime? AcceptedOn
{ {
@@ -89,6 +90,12 @@ namespace XLAB2
set { SetProperty(ref _documentNumber, value); } set { SetProperty(ref _documentNumber, value); }
} }
public string SerialNumbersText
{
get { return _serialNumbersText; }
set { SetProperty(ref _serialNumbersText, value); }
}
public int FailedCount public int FailedCount
{ {
get { return _failedCount; } get { return _failedCount; }
@@ -346,6 +353,8 @@ namespace XLAB2
public string RegistryNumber { get; set; } public string RegistryNumber { get; set; }
public string SerialNumbersText { get; set; }
public int InVerificationCount { get; set; } public int InVerificationCount { get; set; }
public int VerifiedCount { get; set; } public int VerifiedCount { get; set; }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@@ -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
}
}
}

View 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.

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