diff --git a/XLAB/EkzDirectoryDialogService.cs b/XLAB/EkzDirectoryDialogService.cs new file mode 100644 index 0000000..dd6c8f3 --- /dev/null +++ b/XLAB/EkzDirectoryDialogService.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Windows; + +namespace XLAB +{ + internal interface IEkzDirectoryDialogService + { + EkzDirectoryItem ShowEkzEditDialog(EkzDirectoryItem seed, bool isNew, IReadOnlyList 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 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); + } + } +} diff --git a/XLAB/EkzDirectoryModels.cs b/XLAB/EkzDirectoryModels.cs new file mode 100644 index 0000000..0925f48 --- /dev/null +++ b/XLAB/EkzDirectoryModels.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; + +namespace XLAB +{ + public sealed class EkzDirectoryItem + { + public int Id { get; set; } + + public int TypeSizeId { get; set; } + + public string MeasurementAreaName { get; set; } + + public string InstrumentName { get; set; } + + public string TypeName { get; set; } + + public string RangeText { get; set; } + + public string AccuracyText { get; set; } + + public string RegistryNumber { get; set; } + + public int OwnerOrganizationId { get; set; } + + public string OwnerOrganizationName { get; set; } + + public string SerialNumber { get; set; } + + public string InventoryNumber { get; set; } + + public string 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 ImpactItems { get; set; } + + public string ConfirmationMessage { get; set; } + + public string WarningMessage { get; set; } + } + + internal sealed class EkzDeleteResult + { + public bool IsDeleted { get; set; } + + public IReadOnlyList 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; + } +} diff --git a/XLAB/EkzDirectoryService.cs b/XLAB/EkzDirectoryService.cs new file mode 100644 index 0000000..e6ec0ff --- /dev/null +++ b/XLAB/EkzDirectoryService.cs @@ -0,0 +1,832 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Data.SqlClient; + +namespace XLAB +{ + internal sealed class EkzDirectoryService + { + private static readonly string[] CascadingEkzChildTables = { "EKZMK", "EKZMCP" }; + + private static readonly string[] CascadingEkzMkChildTables = { "EKZMKFCTVL", "EKZMKDH", "EKZMKEKZK", "EKZMKND", "KSPELEKZMK" }; + + public int AddEkzItem(EkzDirectoryItem item) + { + var normalizedItem = NormalizeEkzItem(item); + + const string sql = @" +INSERT INTO dbo.EKZ +( + IDTPRZ, + IDFRPDV, + KLSIPR, + NNZV, + NNIN, + DSEKZ, + GUIDEKZ, + IsDeleted +) +VALUES +( + @TypeSizeId, + @OwnerOrganizationId, + 1, + @SerialNumber, + @InventoryNumber, + @Notes, + @Guid, + 0 +); + +SELECT CAST(SCOPE_IDENTITY() AS int);"; + + using (var connection = ReferenceDirectorySqlHelpers.CreateConnection()) + using (var command = new SqlCommand(sql, connection)) + { + connection.Open(); + EnsureEkzIsUnique(connection, normalizedItem.TypeSizeId, normalizedItem.OwnerOrganizationId, normalizedItem.SerialNumber, null); + + command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds(); + command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId; + command.Parameters.Add("@OwnerOrganizationId", SqlDbType.Int).Value = normalizedItem.OwnerOrganizationId; + command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, EkzDirectoryRules.SerialNumberMaxLength).Value = normalizedItem.SerialNumber; + ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@InventoryNumber", SqlDbType.VarChar, EkzDirectoryRules.InventoryNumberMaxLength, normalizedItem.InventoryNumber); + ReferenceDirectorySqlHelpers.AddNullableStringParameter(command, "@Notes", SqlDbType.VarChar, EkzDirectoryRules.NotesMaxLength, normalizedItem.Notes); + command.Parameters.Add("@Guid", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid(); + + return Convert.ToInt32(command.ExecuteScalar()); + } + } + + public EkzDeletePreview GetEkzDeletePreview(int id) + { + if (id <= 0) + { + return new EkzDeletePreview + { + CanDelete = false, + ImpactItems = Array.Empty(), + 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(), + WarningMessage = preview.WarningMessage + }; + } + + var impactItems = new List(); + 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(), + 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(), + WarningMessage = CreateEkzDeleteFailedMessage(ex) + }; + } + } + + public IReadOnlyList 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(); + + 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 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(); + + 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 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 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(), + WarningMessage = "Запись EKZ для удаления не найдена." + }; + } + + var cardIds = LoadEkzMkCardIds(connection, transaction, id); + var blockers = new List(); + 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 BuildImpactItems(SqlConnection connection, SqlTransaction transaction, int instrumentId, IReadOnlyCollection cardIds) + { + var impactItems = new List(); + 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 OrderImpactItems(IEnumerable impactItems) + { + var order = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "EKZ", 1 }, + { "EKZMK", 2 }, + { "DMS", 3 }, + { "EKZMKFCTVL", 4 }, + { "EKZMKDH", 5 }, + { "EKZMKEKZK", 6 }, + { "EKZMKND", 7 }, + { "KSPELEKZMK", 8 }, + { "EKZMCP", 9 } + }; + + return (impactItems ?? Enumerable.Empty()) + .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 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 impactItems) + { + var items = OrderImpactItems(impactItems).ToList(); + var lines = new List(); + 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 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 blockers) + { + var details = string.Join(", ", (blockers ?? Enumerable.Empty()) + .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 MergeBlockers(IEnumerable blockers) + { + return (blockers ?? Enumerable.Empty()) + .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 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 LoadUnhandledDeleteBlockers(SqlConnection connection, SqlTransaction transaction, string parentTableName, IEnumerable ids, IEnumerable excludedChildTables) + { + var blockers = new List(); + foreach (var id in (ids ?? Enumerable.Empty()).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 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 ExecuteIdList(SqlConnection connection, SqlTransaction transaction, string sql, string parameterName, int parameterValue) + { + var result = new List(); + 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("Экземпляр с таким типоразмером, владельцем и заводским номером уже существует."); + } + } + } + } +} diff --git a/XLAB/EkzDirectoryWindow.xaml b/XLAB/EkzDirectoryWindow.xaml new file mode 100644 index 0000000..791e4c3 --- /dev/null +++ b/XLAB/EkzDirectoryWindow.xaml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + +