This commit is contained in:
Курнат Андрей
2026-03-13 21:26:27 +03:00
parent 9df4de1ba1
commit d4cc15a7cf
9 changed files with 1067 additions and 9 deletions

View File

@@ -9,6 +9,8 @@ namespace XLAB
IReadOnlyList<AvailableInstrumentItem> ShowInstrumentPickerDialog(string customerName, IReadOnlyList<AvailableInstrumentItem> instruments);
InstrumentTypeSelectionResult ShowInstrumentTypeDialog(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes);
VerificationEditResult ShowVerificationDialog(
VerificationEditSeed seed,
IReadOnlyList<PersonReference> verifiers,
@@ -52,6 +54,16 @@ namespace XLAB
return result.HasValue && result.Value ? viewModel.GetSelectedItems() : null;
}
public InstrumentTypeSelectionResult ShowInstrumentTypeDialog(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes)
{
var viewModel = new SelectInstrumentTypeWindowViewModel(customerName, instrumentTypes);
var window = new SelectInstrumentTypeWindow(viewModel);
window.Owner = _owner;
var result = window.ShowDialog();
return result.HasValue && result.Value ? viewModel.GetResult() : null;
}
public VerificationEditResult ShowVerificationDialog(
VerificationEditSeed seed,
IReadOnlyList<PersonReference> verifiers,

View File

@@ -198,6 +198,8 @@
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Добавить по заводским номерам"
Command="{Binding OpenInstrumentPickerCommand}" />
<MenuItem Header="Добавить по типу"
Command="{Binding OpenInstrumentTypePickerCommand}" />
<MenuItem Header="Удалить"
Command="{Binding DeleteSelectedGroupsCommand}" />
</ContextMenu>

View File

@@ -53,6 +53,7 @@ namespace XLAB
MarkLinePassedCommand = new RelayCommand(delegate { EditLineVerificationAsync(true); }, delegate { return CanEditSelectedLineVerification(); });
MarkLineRejectedCommand = new RelayCommand(delegate { EditLineVerificationAsync(false); }, delegate { return CanEditSelectedLineVerification(); });
OpenInstrumentPickerCommand = new RelayCommand(delegate { OpenInstrumentPickerAsync(); }, delegate { return !IsBusy && SelectedDocument != null && SelectedDocument.CustomerId.HasValue; });
OpenInstrumentTypePickerCommand = new RelayCommand(delegate { OpenInstrumentTypePickerAsync(); }, delegate { return !IsBusy && SelectedDocument != null && SelectedDocument.CustomerId.HasValue; });
RefreshDocumentsCommand = new RelayCommand(delegate { RefreshDocumentsAsync(null, null); }, delegate { return !IsBusy; });
ResetLineVerificationCommand = new RelayCommand(delegate { ResetSelectedLineVerificationAsync(); }, delegate { return CanResetSelectedLineVerification(); });
SaveDocumentHeaderCommand = new RelayCommand(delegate { SaveDocumentAsync(); }, delegate { return CanSaveDocument(); });
@@ -169,6 +170,8 @@ namespace XLAB
public ICommand OpenInstrumentPickerCommand { get; private set; }
public ICommand OpenInstrumentTypePickerCommand { get; private set; }
public ICommand RefreshDocumentsCommand { get; private set; }
public ICommand ResetLineVerificationCommand { get; private set; }
@@ -513,12 +516,141 @@ namespace XLAB
&& source.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
}
private string BuildOpenDocumentConflictMessage(IEnumerable<OpenDocumentConflictInfo> conflicts)
{
var materializedConflicts = NormalizeOpenDocumentConflicts(conflicts);
if (materializedConflicts.Count == 0)
{
return "Прибор уже находится в другой открытой ПСВ этого заказчика.";
}
var preview = materializedConflicts
.Take(5)
.Select(delegate(OpenDocumentConflictInfo conflict)
{
return string.Format("зав. № {0} -> {1}", conflict.SerialNumber, conflict.DocumentNumber);
})
.ToList();
var suffix = materializedConflicts.Count > preview.Count
? string.Format(" Еще конфликтов: {0}.", materializedConflicts.Count - preview.Count)
: string.Empty;
return string.Format(
"Приборы уже находятся в других открытых ПСВ этого заказчика: {0}.{1}",
string.Join("; ", preview.ToArray()),
suffix);
}
private List<OpenDocumentConflictInfo> FindOpenDocumentConflicts(IEnumerable<PsvDocumentLine> candidateLines)
{
if (SelectedDocument == null || !SelectedDocument.CustomerId.HasValue || candidateLines == null)
{
return new List<OpenDocumentConflictInfo>();
}
var conflicts = FindPendingOpenDocumentConflicts(SelectedDocument, candidateLines);
conflicts.AddRange(_service.FindOpenDocumentConflicts(
SelectedDocument.CustomerId.Value,
SelectedDocument.IsDraft ? null : SelectedDocument.DocumentNumber,
candidateLines));
return NormalizeOpenDocumentConflicts(conflicts);
}
private List<OpenDocumentConflictInfo> FindPendingOpenDocumentConflicts(PsvDocumentSummary currentDocument, IEnumerable<PsvDocumentLine> candidateLines)
{
if (currentDocument == null || !currentDocument.CustomerId.HasValue || candidateLines == null)
{
return new List<OpenDocumentConflictInfo>();
}
var candidateKeys = new HashSet<string>(
candidateLines
.Where(delegate(PsvDocumentLine line)
{
return line != null
&& line.TypeSizeId > 0
&& !string.IsNullOrWhiteSpace(line.SerialNumber);
})
.Select(delegate(PsvDocumentLine line) { return line.OpenDocumentConflictKey; }),
StringComparer.OrdinalIgnoreCase);
if (candidateKeys.Count == 0)
{
return new List<OpenDocumentConflictInfo>();
}
var conflicts = new List<OpenDocumentConflictInfo>();
foreach (var pendingLinesByDocument in _pendingLinesByDocumentKey)
{
if (string.Equals(pendingLinesByDocument.Key, currentDocument.DocumentKey, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var otherDocument = Documents.FirstOrDefault(delegate(PsvDocumentSummary document)
{
return string.Equals(document.DocumentKey, pendingLinesByDocument.Key, StringComparison.OrdinalIgnoreCase);
});
if (otherDocument == null
|| !otherDocument.CustomerId.HasValue
|| otherDocument.CustomerId.Value != currentDocument.CustomerId.Value)
{
continue;
}
foreach (var otherLine in pendingLinesByDocument.Value.Where(delegate(PsvDocumentLine line)
{
return line != null
&& line.TypeSizeId > 0
&& !string.IsNullOrWhiteSpace(line.SerialNumber);
}))
{
if (!candidateKeys.Contains(otherLine.OpenDocumentConflictKey))
{
continue;
}
conflicts.Add(new OpenDocumentConflictInfo
{
DocumentNumber = otherDocument.DocumentNumber,
TypeSizeId = otherLine.TypeSizeId,
SerialNumber = otherLine.SerialNumber
});
}
}
return NormalizeOpenDocumentConflicts(conflicts);
}
private static List<OpenDocumentConflictInfo> NormalizeOpenDocumentConflicts(IEnumerable<OpenDocumentConflictInfo> conflicts)
{
return (conflicts ?? Enumerable.Empty<OpenDocumentConflictInfo>())
.Where(delegate(OpenDocumentConflictInfo conflict) { return conflict != null; })
.GroupBy(delegate(OpenDocumentConflictInfo conflict)
{
return string.Format(
"{0}|{1}|{2}",
conflict.DocumentNumber ?? string.Empty,
conflict.TypeSizeId,
conflict.SerialNumber ?? string.Empty);
}, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, OpenDocumentConflictInfo> group) { return group.First(); })
.OrderBy(delegate(OpenDocumentConflictInfo conflict) { return conflict.SerialNumber ?? string.Empty; })
.ThenBy(delegate(OpenDocumentConflictInfo conflict) { return conflict.DocumentNumber ?? string.Empty; })
.ToList();
}
private PsvDocumentLine CreatePendingLine(AvailableInstrumentItem item)
{
return new PsvDocumentLine
{
CardId = 0,
InstrumentId = item.InstrumentId,
TypeSizeId = item.TypeSizeId,
SerialNumber = item.SerialNumber,
InventoryNumber = item.InventoryNumber,
CustomerName = item.CustomerName,
@@ -547,6 +679,41 @@ namespace XLAB
};
}
private PsvDocumentLine CreatePendingTypeLine(AvailableInstrumentItem item, string serialNumber)
{
return new PsvDocumentLine
{
CardId = 0,
InstrumentId = 0,
TypeSizeId = item.TypeSizeId,
SerialNumber = string.IsNullOrWhiteSpace(serialNumber) ? string.Empty : serialNumber.Trim(),
InventoryNumber = string.Empty,
CustomerName = SelectedDocument == null ? string.Empty : SelectedDocument.CustomerName,
InstrumentType = item.InstrumentType,
InstrumentName = item.InstrumentName,
MeasurementArea = item.MeasurementArea,
RangeText = item.RangeText,
RegistryNumber = item.RegistryNumber,
AccuracyText = item.AccuracyText,
VerificationType = string.Empty,
PeriodMonths = 0,
AcceptedOn = HeaderReceivedOn,
IssuedOn = null,
IsPassed = null,
VerificationPerformedOn = null,
VerifierId = null,
VerifierName = string.Empty,
StickerNumber = string.Empty,
VerificationDocumentFormId = null,
VerificationDocumentLinkTypeId = null,
VerificationDocumentNumber = string.Empty,
VerificationDocumentDate = null,
RejectionReason = string.Empty,
Notes = string.Empty,
IsPendingInsert = true
};
}
private static void ApplyVerificationResultCore(PsvDocumentLine line, VerificationEditResult result)
{
if (line == null || result == null)
@@ -1146,6 +1313,50 @@ namespace XLAB
AddSelectedInstruments(selectedItems);
}
private void OpenInstrumentTypePickerAsync()
{
if (SelectedDocument == null)
{
return;
}
if (!SelectedDocument.CustomerId.HasValue)
{
_dialogService.ShowWarning("Сначала выберите заказчика в ПСВ.");
return;
}
OpenInstrumentTypePickerCoreAsync(SelectedDocument.CustomerName);
}
private async void OpenInstrumentTypePickerCoreAsync(string customerName)
{
IReadOnlyList<AvailableInstrumentItem> instrumentTypes;
try
{
IsBusy = true;
instrumentTypes = await Task.Run(delegate { return _service.LoadInstrumentTypes(); });
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
return;
}
finally
{
IsBusy = false;
}
var result = _dialogService.ShowInstrumentTypeDialog(customerName, instrumentTypes);
if (result == null || result.TypeItem == null)
{
return;
}
AddSelectedTypeInstrument(result);
}
private void AddSelectedInstruments(IReadOnlyList<AvailableInstrumentItem> selectedItems)
{
if (SelectedDocument == null)
@@ -1160,13 +1371,45 @@ namespace XLAB
_pendingLinesByDocumentKey[SelectedDocument.DocumentKey] = pendingLines;
}
var candidateLines = selectedItems
.Where(delegate(AvailableInstrumentItem item) { return item != null; })
.Select(CreatePendingLine)
.ToList();
List<OpenDocumentConflictInfo> openDocumentConflicts;
try
{
openDocumentConflicts = FindOpenDocumentConflicts(candidateLines);
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
return;
}
var openConflictKeys = new HashSet<string>(
openDocumentConflicts.Select(delegate(OpenDocumentConflictInfo conflict) { return conflict.OpenDocumentConflictKey; }),
StringComparer.OrdinalIgnoreCase);
var duplicateKeys = new HashSet<string>(DocumentLines.Select(delegate(PsvDocumentLine line) { return line.DuplicateKey; }), StringComparer.OrdinalIgnoreCase);
var addedCount = 0;
var skippedDuplicateCount = 0;
var skippedOpenDocumentCount = 0;
var skippedWithoutTemplateCount = 0;
foreach (var item in selectedItems)
{
if (item == null)
{
continue;
}
if (openConflictKeys.Contains(PsvDocumentLine.BuildOpenDocumentConflictKey(item.TypeSizeId, item.SerialNumber)))
{
skippedOpenDocumentCount++;
continue;
}
if (!item.HasTemplate)
{
skippedWithoutTemplateCount++;
@@ -1208,11 +1451,98 @@ namespace XLAB
messages.Add(string.Format("Пропущено без шаблона EKZMK: {0}.", skippedWithoutTemplateCount));
}
if (messages.Count > 0)
if (skippedOpenDocumentCount > 0)
{
_dialogService.ShowInfo(string.Join(" ", messages.ToArray()));
messages.Add(string.Format("Пропущено из-за других открытых ПСВ: {0}.", skippedOpenDocumentCount));
messages.Add(BuildOpenDocumentConflictMessage(openDocumentConflicts));
}
if (messages.Count > 0)
{
var message = string.Join(" ", messages.ToArray());
if (addedCount == 0 && skippedOpenDocumentCount > 0)
{
_dialogService.ShowWarning(message);
}
else
{
_dialogService.ShowInfo(message);
}
}
RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable");
}
private void AddSelectedTypeInstrument(InstrumentTypeSelectionResult result)
{
if (SelectedDocument == null || result == null || result.TypeItem == null)
{
return;
}
List<PsvDocumentLine> pendingLines;
if (!_pendingLinesByDocumentKey.TryGetValue(SelectedDocument.DocumentKey, out pendingLines))
{
pendingLines = new List<PsvDocumentLine>();
_pendingLinesByDocumentKey[SelectedDocument.DocumentKey] = pendingLines;
}
var serialNumber = string.IsNullOrWhiteSpace(result.SerialNumber) ? string.Empty : result.SerialNumber.Trim();
if (string.IsNullOrWhiteSpace(serialNumber))
{
_dialogService.ShowWarning("Введите заводской номер.");
return;
}
if (!result.TypeItem.HasTemplate)
{
_dialogService.ShowWarning("Выбранный тип нельзя добавить: для него не найден шаблон EKZMK или период МК.");
return;
}
var candidateLine = CreatePendingTypeLine(result.TypeItem, serialNumber);
List<OpenDocumentConflictInfo> openDocumentConflicts;
try
{
openDocumentConflicts = FindOpenDocumentConflicts(new[] { candidateLine });
}
catch (Exception ex)
{
_dialogService.ShowError(ex.Message);
return;
}
if (openDocumentConflicts.Count > 0)
{
_dialogService.ShowWarning(BuildOpenDocumentConflictMessage(openDocumentConflicts));
return;
}
var duplicateKey = PsvDocumentLine.BuildDuplicateKey(
result.TypeItem.InstrumentType,
result.TypeItem.RangeText,
result.TypeItem.RegistryNumber,
serialNumber);
if (DocumentLines.Any(delegate(PsvDocumentLine line)
{
return string.Equals(line.DuplicateKey, duplicateKey, StringComparison.OrdinalIgnoreCase);
}))
{
_dialogService.ShowWarning("Такой прибор уже есть в ПСВ.");
return;
}
pendingLines.Add(candidateLine);
if (SelectedDocument.IsDraft)
{
SelectedDocument.ItemCount = pendingLines.Count;
}
LoadSelectedDocumentAsync();
_dialogService.ShowInfo("Прибор по типу добавлен в ПСВ.");
RaiseCommandStates();
OnPropertyChanged("IsCustomerEditable");
}
@@ -1254,6 +1584,7 @@ namespace XLAB
((RelayCommand)MarkLinePassedCommand).RaiseCanExecuteChanged();
((RelayCommand)MarkLineRejectedCommand).RaiseCanExecuteChanged();
((RelayCommand)OpenInstrumentPickerCommand).RaiseCanExecuteChanged();
((RelayCommand)OpenInstrumentTypePickerCommand).RaiseCanExecuteChanged();
((RelayCommand)RefreshDocumentsCommand).RaiseCanExecuteChanged();
((RelayCommand)ResetLineVerificationCommand).RaiseCanExecuteChanged();
((RelayCommand)SaveDocumentHeaderCommand).RaiseCanExecuteChanged();
@@ -1398,7 +1729,12 @@ namespace XLAB
}
var pendingLines = GetPendingLines(SelectedDocument)
.Where(delegate(PsvDocumentLine line) { return line.InstrumentId > 0; })
.Where(delegate(PsvDocumentLine line)
{
return line != null
&& (line.InstrumentId > 0
|| (line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber)));
})
.ToList();
if (SelectedDocument.IsDraft && pendingLines.Count == 0)
{
@@ -1406,11 +1742,19 @@ namespace XLAB
return;
}
var openDocumentConflicts = FindPendingOpenDocumentConflicts(SelectedDocument, pendingLines);
if (openDocumentConflicts.Count > 0)
{
_dialogService.ShowWarning(BuildOpenDocumentConflictMessage(openDocumentConflicts));
return;
}
var request = new DocumentEditorResult
{
DocumentNumber = DocumentNumberEditor.Trim(),
AcceptedOn = HeaderReceivedOn.Value,
IssuedOn = HeaderIssuedOn
IssuedOn = HeaderIssuedOn,
CustomerId = SelectedDocument.CustomerId ?? SelectedCustomerId
};
var currentDocumentNumber = SelectedDocument.IsDraft ? null : SelectedDocument.DocumentNumber;

View File

@@ -210,6 +210,7 @@ ORDER BY MAX(m.DTPRM) DESC, m.NNZVPV DESC;";
SELECT
m.IDEKZMK AS CardId,
z.IDEKZ AS InstrumentId,
z.IDTPRZ AS TypeSizeId,
z.NNZV AS SerialNumber,
z.NNIN AS InventoryNumber,
ownerOrg.NMFRPD AS CustomerName,
@@ -279,6 +280,7 @@ ORDER BY areas.NMOI, names.NMTP, tips.TP, z.NNZV;";
{
CardId = GetInt32(reader, "CardId"),
InstrumentId = GetInt32(reader, "InstrumentId"),
TypeSizeId = GetInt32(reader, "TypeSizeId"),
SerialNumber = GetString(reader, "SerialNumber"),
InventoryNumber = GetString(reader, "InventoryNumber"),
CustomerName = GetString(reader, "CustomerName"),
@@ -425,6 +427,109 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;";
return instruments;
}
public IReadOnlyList<AvailableInstrumentItem> LoadInstrumentTypes()
{
const string sql = @"
SELECT
0 AS InstrumentId,
sizeInfo.IDTPRZ AS TypeSizeId,
CAST(N'' AS nvarchar(30)) AS SerialNumber,
CAST(N'' AS nvarchar(30)) AS InventoryNumber,
CAST(N'' AS nvarchar(255)) AS CustomerName,
tips.TP AS InstrumentType,
names.NMTP AS InstrumentName,
areas.NMOI AS MeasurementArea,
sizeInfo.NNGSRS AS RegistryNumber,
sizeInfo.DPZN AS RangeText,
sizeInfo.HRTC AS AccuracyText,
CASE
WHEN typeTemplate.LastDocumentNumber IS NOT NULL
OR periodByType.PRMK IS NOT NULL
THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS HasTemplate,
typeTemplate.LastDocumentNumber AS LastDocumentNumber,
typeTemplate.LastAcceptedOn AS LastAcceptedOn,
CASE
WHEN typeTemplate.LastDocumentNumber IS NOT NULL THEN N'Шаблон по типоразмеру'
WHEN periodByType.PRMK IS NOT NULL THEN N'Период из TPRMCP'
ELSE N''
END AS TemplateSource
FROM dbo.TPRZ sizeInfo
LEFT JOIN dbo.TIPS tips ON tips.IDTIPS = sizeInfo.IDTIPS
LEFT JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
LEFT JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
OUTER APPLY
(
SELECT TOP (1)
history.NNZVPV AS LastDocumentNumber,
history.DTPRM AS LastAcceptedOn
FROM dbo.EKZMK history
JOIN dbo.EKZ instrumentOfSameType ON instrumentOfSameType.IDEKZ = history.IDEKZ
WHERE instrumentOfSameType.IDTPRZ = sizeInfo.IDTPRZ
ORDER BY ISNULL(history.DTPRM, CONVERT(datetime, '19000101', 112)) DESC, history.IDEKZMK DESC
) typeTemplate
OUTER APPLY
(
SELECT TOP (1)
t.PRMK
FROM dbo.TPRMCP t
WHERE t.IDTPRZ = sizeInfo.IDTPRZ
ORDER BY t.IDTPRMCP DESC
) periodByType
ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, sizeInfo.NNGSRS;";
var instrumentTypes = new List<AvailableInstrumentItem>();
using (var connection = CreateConnection())
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
command.CommandTimeout = 60;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
instrumentTypes.Add(new AvailableInstrumentItem
{
InstrumentId = GetInt32(reader, "InstrumentId"),
TypeSizeId = GetInt32(reader, "TypeSizeId"),
SerialNumber = GetString(reader, "SerialNumber"),
InventoryNumber = GetString(reader, "InventoryNumber"),
CustomerName = GetString(reader, "CustomerName"),
InstrumentType = GetString(reader, "InstrumentType"),
InstrumentName = GetString(reader, "InstrumentName"),
MeasurementArea = GetString(reader, "MeasurementArea"),
RegistryNumber = GetString(reader, "RegistryNumber"),
RangeText = GetString(reader, "RangeText"),
AccuracyText = GetString(reader, "AccuracyText"),
HasTemplate = GetBoolean(reader, "HasTemplate"),
LastDocumentNumber = GetString(reader, "LastDocumentNumber"),
LastAcceptedOn = GetNullableDateTime(reader, "LastAcceptedOn"),
TemplateSource = GetString(reader, "TemplateSource")
});
}
}
}
return instrumentTypes;
}
public IReadOnlyList<OpenDocumentConflictInfo> FindOpenDocumentConflicts(int customerId, string currentDocumentNumber, IEnumerable<PsvDocumentLine> candidateLines)
{
if (customerId <= 0 || candidateLines == null)
{
return new List<OpenDocumentConflictInfo>();
}
using (var connection = CreateConnection())
{
connection.Open();
return LoadOpenDocumentConflicts(connection, null, customerId, currentDocumentNumber, candidateLines);
}
}
public void ResetLineVerification(int cardId)
{
if (cardId <= 0)
@@ -576,9 +681,9 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;";
var distinctPendingLines = pendingLines == null
? new List<PsvDocumentLine>()
: pendingLines
.Where(delegate(PsvDocumentLine line) { return line != null && line.InstrumentId > 0; })
.GroupBy(delegate(PsvDocumentLine line) { return line.InstrumentId; })
.Select(delegate(IGrouping<int, PsvDocumentLine> group) { return group.First(); })
.Where(IsPendingLineReadyForSave)
.GroupBy(GetPendingLineSaveKey, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, PsvDocumentLine> group) { return group.First(); })
.ToList();
using (var connection = CreateConnection())
@@ -592,6 +697,20 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;";
throw new InvalidOperationException(string.Format("ПСВ с номером \"{0}\" уже существует.", normalizedNumber));
}
if (document.CustomerId.HasValue)
{
var openDocumentConflicts = LoadOpenDocumentConflicts(
connection,
transaction,
document.CustomerId.Value,
currentDocumentNumber,
distinctPendingLines);
if (openDocumentConflicts.Count > 0)
{
throw new InvalidOperationException(BuildOpenDocumentConflictMessage(openDocumentConflicts));
}
}
var updatedEkzMkCount = 0;
if (!string.IsNullOrWhiteSpace(currentDocumentNumber))
{
@@ -611,9 +730,18 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;";
foreach (var pendingLine in distinctPendingLines)
{
var duplicateKey = pendingLine.DuplicateKey;
if (string.IsNullOrWhiteSpace(duplicateKey))
{
throw new InvalidOperationException("Не удалось определить ключ дубликата для строки ПСВ.");
}
var instrumentId = pendingLine.InstrumentId;
var instrumentIdentity = LoadInstrumentIdentity(connection, transaction, instrumentId);
if (instrumentIdentity == null)
var instrumentIdentity = new InstrumentIdentityInfo
{
DuplicateKey = duplicateKey
};
if (string.IsNullOrWhiteSpace(duplicateKey))
{
throw new InvalidOperationException(string.Format("Прибор IDEKZ={0} не найден.", instrumentId));
}
@@ -624,6 +752,7 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;";
continue;
}
instrumentId = ResolveInstrumentIdForPendingLine(connection, transaction, document, pendingLine);
var template = LoadTemplate(connection, transaction, instrumentId);
if (template == null)
{
@@ -786,6 +915,62 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;";
}
}
private static string GetPendingLineSaveKey(PsvDocumentLine line)
{
if (line == null)
{
return string.Empty;
}
return line.InstrumentId > 0
? string.Format("ID:{0}", line.InstrumentId)
: string.Format("NEW:{0}", line.DuplicateKey ?? string.Empty);
}
private static bool IsPendingLineReadyForSave(PsvDocumentLine line)
{
return line != null
&& (line.InstrumentId > 0
|| (line.TypeSizeId > 0 && !string.IsNullOrWhiteSpace(line.SerialNumber)));
}
private static int ResolveInstrumentIdForPendingLine(SqlConnection connection, SqlTransaction transaction, DocumentEditorResult document, PsvDocumentLine pendingLine)
{
if (pendingLine == null)
{
throw new InvalidOperationException("Строка ПСВ для сохранения не задана.");
}
if (pendingLine.InstrumentId > 0)
{
return pendingLine.InstrumentId;
}
if (pendingLine.TypeSizeId <= 0)
{
throw new InvalidOperationException("Для новой строки ПСВ не указан типоразмер прибора.");
}
var serialNumber = string.IsNullOrWhiteSpace(pendingLine.SerialNumber) ? null : pendingLine.SerialNumber.Trim();
if (serialNumber == null)
{
throw new InvalidOperationException("Для новой строки ПСВ не указан заводской номер.");
}
if (!document.CustomerId.HasValue)
{
throw new InvalidOperationException("Для добавления прибора по типу должен быть выбран заказчик ПСВ.");
}
var existingInstrumentId = FindExistingInstrumentId(connection, transaction, pendingLine.TypeSizeId, document.CustomerId.Value, serialNumber);
if (existingInstrumentId.HasValue)
{
return existingInstrumentId.Value;
}
return InsertInstrument(connection, transaction, pendingLine.TypeSizeId, document.CustomerId.Value, serialNumber);
}
private static List<int> NormalizeCardIds(IEnumerable<int> cardIds, string invalidCardMessage, string emptyListMessage)
{
if (cardIds == null)
@@ -807,6 +992,196 @@ ORDER BY names.NMTP, tips.TP, sizeInfo.DPZN, z.NNZV;";
return normalizedCardIds;
}
private static int? FindExistingInstrumentId(SqlConnection connection, SqlTransaction transaction, int typeSizeId, int customerId, string serialNumber)
{
const string sql = @"
SELECT TOP (1) z.IDEKZ
FROM dbo.EKZ z
WHERE z.IDTPRZ = @TypeSizeId
AND z.IDFRPDV = @CustomerId
AND z.NNZV = @SerialNumber
AND ISNULL(z.IsDeleted, 0) = 0
ORDER BY z.IDEKZ DESC;";
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = 60;
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;
command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, 30).Value = serialNumber;
var result = command.ExecuteScalar();
return result == null || result == DBNull.Value ? (int?)null : Convert.ToInt32(result);
}
}
private static int InsertInstrument(SqlConnection connection, SqlTransaction transaction, int typeSizeId, int customerId, string serialNumber)
{
const string sql = @"
INSERT INTO dbo.EKZ
(
IDTPRZ,
IDFRPDV,
KLSIPR,
NNZV,
GUIDEKZ,
IsDeleted
)
VALUES
(
@TypeSizeId,
@CustomerId,
@Klsipr,
@SerialNumber,
@GuidEkz,
0
);
SELECT CAST(SCOPE_IDENTITY() AS int);";
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = 60;
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;
command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId;
command.Parameters.Add("@Klsipr", SqlDbType.Int).Value = 1;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, 30).Value = serialNumber;
command.Parameters.Add("@GuidEkz", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
return Convert.ToInt32(command.ExecuteScalar());
}
}
private static string BuildOpenDocumentConflictMessage(IEnumerable<OpenDocumentConflictInfo> conflicts)
{
var materializedConflicts = conflicts == null
? new List<OpenDocumentConflictInfo>()
: conflicts
.Where(delegate(OpenDocumentConflictInfo conflict) { return conflict != null; })
.GroupBy(delegate(OpenDocumentConflictInfo conflict)
{
return string.Format(
"{0}|{1}|{2}",
conflict.DocumentNumber ?? string.Empty,
conflict.TypeSizeId,
conflict.SerialNumber ?? string.Empty);
}, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, OpenDocumentConflictInfo> group) { return group.First(); })
.OrderBy(delegate(OpenDocumentConflictInfo conflict) { return conflict.SerialNumber ?? string.Empty; })
.ThenBy(delegate(OpenDocumentConflictInfo conflict) { return conflict.DocumentNumber ?? string.Empty; })
.ToList();
if (materializedConflicts.Count == 0)
{
return "Прибор уже находится в другой открытой ПСВ этого заказчика.";
}
var preview = materializedConflicts
.Take(5)
.Select(delegate(OpenDocumentConflictInfo conflict)
{
return string.Format("зав. № {0} -> {1}", conflict.SerialNumber, conflict.DocumentNumber);
})
.ToList();
var suffix = materializedConflicts.Count > preview.Count
? string.Format(" Еще конфликтов: {0}.", materializedConflicts.Count - preview.Count)
: string.Empty;
return string.Format(
"Приборы уже находятся в других открытых ПСВ этого заказчика: {0}.{1}",
string.Join("; ", preview.ToArray()),
suffix);
}
private static List<OpenDocumentConflictInfo> LoadOpenDocumentConflicts(
SqlConnection connection,
SqlTransaction transaction,
int customerId,
string currentDocumentNumber,
IEnumerable<PsvDocumentLine> candidateLines)
{
var normalizedCandidates = candidateLines == null
? new List<PsvDocumentLine>()
: candidateLines
.Where(delegate(PsvDocumentLine line)
{
return line != null
&& line.TypeSizeId > 0
&& !string.IsNullOrWhiteSpace(line.SerialNumber);
})
.GroupBy(delegate(PsvDocumentLine line) { return line.OpenDocumentConflictKey; }, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, PsvDocumentLine> group) { return group.First(); })
.ToList();
if (customerId <= 0 || normalizedCandidates.Count == 0)
{
return new List<OpenDocumentConflictInfo>();
}
const string sql = @"
WITH ActiveDocuments AS
(
SELECT m.NNZVPV
FROM dbo.EKZMK m
WHERE NULLIF(LTRIM(RTRIM(m.NNZVPV)), N'') IS NOT NULL
GROUP BY m.NNZVPV
HAVING MAX(m.DTVDM) IS NULL
)
SELECT DISTINCT
m.NNZVPV AS DocumentNumber,
z.IDTPRZ AS TypeSizeId,
LTRIM(RTRIM(z.NNZV)) AS SerialNumber
FROM ActiveDocuments activeDocuments
JOIN dbo.EKZMK m ON m.NNZVPV = activeDocuments.NNZVPV
JOIN dbo.EKZ z ON z.IDEKZ = m.IDEKZ
WHERE z.IDFRPDV = @CustomerId
AND z.IDTPRZ = @TypeSizeId
AND LTRIM(RTRIM(z.NNZV)) = @SerialNumber
AND (@CurrentDocumentNumber = N'' OR m.NNZVPV <> @CurrentDocumentNumber)
ORDER BY m.NNZVPV;";
var conflicts = new List<OpenDocumentConflictInfo>();
foreach (var candidateLine in normalizedCandidates)
{
using (var command = new SqlCommand(sql, connection, transaction))
{
command.CommandTimeout = 60;
command.Parameters.Add("@CustomerId", SqlDbType.Int).Value = customerId;
command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = candidateLine.TypeSizeId;
command.Parameters.Add("@SerialNumber", SqlDbType.VarChar, 30).Value = candidateLine.SerialNumber.Trim();
command.Parameters.Add("@CurrentDocumentNumber", SqlDbType.NVarChar, 60).Value = currentDocumentNumber ?? string.Empty;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
conflicts.Add(new OpenDocumentConflictInfo
{
DocumentNumber = GetString(reader, "DocumentNumber"),
TypeSizeId = GetInt32(reader, "TypeSizeId"),
SerialNumber = GetString(reader, "SerialNumber")
});
}
}
}
}
return conflicts
.GroupBy(delegate(OpenDocumentConflictInfo conflict)
{
return string.Format(
"{0}|{1}|{2}",
conflict.DocumentNumber ?? string.Empty,
conflict.TypeSizeId,
conflict.SerialNumber ?? string.Empty);
}, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<string, OpenDocumentConflictInfo> group) { return group.First(); })
.OrderBy(delegate(OpenDocumentConflictInfo conflict) { return conflict.SerialNumber ?? string.Empty; })
.ThenBy(delegate(OpenDocumentConflictInfo conflict) { return conflict.DocumentNumber ?? string.Empty; })
.ToList();
}
private static SqlConnection CreateConnection()
{
var connectionString = ConfigurationManager.ConnectionStrings["AsumsSql"];
@@ -1179,6 +1554,7 @@ VALUES
NULL,
NULL,
@NNZVPV,
NULL,
@NNNKL,
@PRMK,
@DTMKFK,

View File

@@ -133,6 +133,8 @@ namespace XLAB
public int InstrumentId { get; set; }
public int TypeSizeId { get; set; }
public string SerialNumber { get; set; }
public string InventoryNumber { get; set; }
@@ -197,6 +199,14 @@ namespace XLAB
}
}
public string OpenDocumentConflictKey
{
get
{
return BuildOpenDocumentConflictKey(TypeSizeId, SerialNumber);
}
}
public string ResultText
{
get
@@ -237,6 +247,11 @@ namespace XLAB
NormalizeKeyPart(serialNumber));
}
public static string BuildOpenDocumentConflictKey(int typeSizeId, string serialNumber)
{
return string.Format("{0}|{1}", typeSizeId, NormalizeKeyPart(serialNumber));
}
private static string NormalizeKeyPart(string value)
{
return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim().ToUpperInvariant();
@@ -352,6 +367,15 @@ namespace XLAB
public DateTime AcceptedOn { get; set; }
public DateTime? IssuedOn { get; set; }
public int? CustomerId { get; set; }
}
public sealed class InstrumentTypeSelectionResult
{
public AvailableInstrumentItem TypeItem { get; set; }
public string SerialNumber { get; set; }
}
public sealed class VerificationEditSeed
@@ -392,6 +416,23 @@ namespace XLAB
public string VerifierName { get; set; }
}
internal sealed class OpenDocumentConflictInfo
{
public string DocumentNumber { get; set; }
public int TypeSizeId { get; set; }
public string SerialNumber { get; set; }
public string OpenDocumentConflictKey
{
get
{
return PsvDocumentLine.BuildOpenDocumentConflictKey(TypeSizeId, SerialNumber);
}
}
}
internal sealed class DocumentDeleteResult
{
public int DeletedEkzMkFctvlCount { get; set; }

View File

@@ -0,0 +1,93 @@
<Window x:Class="XLAB.SelectInstrumentTypeWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Добавить по типу"
Height="720"
Width="1120"
MinHeight="520"
MinWidth="900"
WindowStartupLocation="CenterOwner">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock FontWeight="SemiBold"
Text="{Binding CustomerName, StringFormat=Заказчик: {0}}" />
<StackPanel Grid.Row="1"
Margin="0,8,0,8"
Orientation="Horizontal">
<TextBlock Width="150"
Margin="0,0,8,0"
VerticalAlignment="Center"
Text="Поиск" />
<TextBox Width="320"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<DataGrid Grid.Row="2"
ItemsSource="{Binding InstrumentTypesView}"
SelectedItem="{Binding SelectedType, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="Номер госреестра"
Width="120"
Binding="{Binding RegistryNumber}" />
<DataGridTextColumn Header="Наименование"
Width="240"
Binding="{Binding InstrumentName}" />
<DataGridTextColumn Header="Тип"
Width="150"
Binding="{Binding InstrumentType}" />
<DataGridTextColumn Header="Диапазон"
Width="180"
Binding="{Binding RangeText}" />
<DataGridTextColumn Header="Характеристики"
Width="160"
Binding="{Binding AccuracyText}" />
<DataGridTextColumn Header="Область"
Width="*"
Binding="{Binding MeasurementArea}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="3"
Margin="0,10,0,0"
Orientation="Horizontal">
<TextBlock Width="150"
Margin="0,0,8,0"
VerticalAlignment="Center"
Text="Заводской номер" />
<TextBox Width="320"
Text="{Binding SerialNumber, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<TextBlock Grid.Row="4"
Margin="0,8,0,0"
Foreground="DimGray"
Text="{Binding StatusText}" />
<StackPanel Grid.Row="5"
Margin="0,12,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="90"
Margin="0,0,8,0"
IsDefault="True"
Command="{Binding ConfirmCommand}"
Content="Сохранить" />
<Button Width="90"
Command="{Binding CancelCommand}"
Content="Отмена" />
</StackPanel>
</Grid>
</Window>

View File

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

View File

@@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
using System.Windows.Input;
namespace XLAB
{
internal sealed class SelectInstrumentTypeWindowViewModel : ObservableObject
{
private string _searchText;
private AvailableInstrumentItem _selectedType;
private string _serialNumber;
private string _statusText;
public SelectInstrumentTypeWindowViewModel(string customerName, IReadOnlyList<AvailableInstrumentItem> instrumentTypes)
{
CustomerName = customerName ?? string.Empty;
InstrumentTypes = new ObservableCollection<AvailableInstrumentItem>(instrumentTypes ?? new List<AvailableInstrumentItem>());
InstrumentTypesView = CollectionViewSource.GetDefaultView(InstrumentTypes);
InstrumentTypesView.Filter = FilterTypes;
ConfirmCommand = new RelayCommand(Confirm, CanConfirm);
CancelCommand = new RelayCommand(Cancel);
UpdateStatus();
}
public event EventHandler<bool?> CloseRequested;
public ICommand CancelCommand { get; private set; }
public ICommand ConfirmCommand { get; private set; }
public string CustomerName { get; private set; }
public ObservableCollection<AvailableInstrumentItem> InstrumentTypes { get; private set; }
public ICollectionView InstrumentTypesView { get; private set; }
public string SearchText
{
get { return _searchText; }
set
{
if (SetProperty(ref _searchText, value))
{
InstrumentTypesView.Refresh();
UpdateStatus();
}
}
}
public AvailableInstrumentItem SelectedType
{
get { return _selectedType; }
set
{
if (SetProperty(ref _selectedType, value))
{
((RelayCommand)ConfirmCommand).RaiseCanExecuteChanged();
UpdateStatus();
}
}
}
public string SerialNumber
{
get { return _serialNumber; }
set
{
if (SetProperty(ref _serialNumber, value))
{
((RelayCommand)ConfirmCommand).RaiseCanExecuteChanged();
UpdateStatus();
}
}
}
public string StatusText
{
get { return _statusText; }
private set { SetProperty(ref _statusText, value); }
}
public InstrumentTypeSelectionResult GetResult()
{
return SelectedType == null
? null
: new InstrumentTypeSelectionResult
{
TypeItem = SelectedType,
SerialNumber = string.IsNullOrWhiteSpace(SerialNumber) ? string.Empty : SerialNumber.Trim()
};
}
private void Cancel(object parameter)
{
RaiseCloseRequested(false);
}
private bool CanConfirm(object parameter)
{
return SelectedType != null
&& !string.IsNullOrWhiteSpace(SerialNumber);
}
private void Confirm(object parameter)
{
RaiseCloseRequested(true);
}
private bool FilterTypes(object item)
{
var instrumentType = item as AvailableInstrumentItem;
if (instrumentType == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(SearchText))
{
return true;
}
return Contains(instrumentType.RegistryNumber, SearchText)
|| Contains(instrumentType.InstrumentName, SearchText)
|| Contains(instrumentType.InstrumentType, SearchText)
|| Contains(instrumentType.RangeText, SearchText)
|| Contains(instrumentType.AccuracyText, SearchText)
|| Contains(instrumentType.MeasurementArea, SearchText);
}
private static bool Contains(string source, string searchText)
{
return !string.IsNullOrWhiteSpace(source)
&& source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
}
private void RaiseCloseRequested(bool? dialogResult)
{
var handler = CloseRequested;
if (handler != null)
{
handler(this, dialogResult);
}
}
private void UpdateStatus()
{
var visibleCount = InstrumentTypesView.Cast<object>().Count();
StatusText = string.Format(
"Всего типов: {0}. По фильтру: {1}. Выбран тип: {2}. Заводской номер: {3}.",
InstrumentTypes.Count,
visibleCount,
SelectedType == null ? "нет" : "да",
string.IsNullOrWhiteSpace(SerialNumber) ? "не указан" : "указан");
}
}
}

View File

@@ -90,6 +90,15 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="SelectInstrumentsWindowViewModel.cs" />
<Page Include="SelectInstrumentTypeWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="SelectInstrumentTypeWindow.xaml.cs">
<DependentUpon>SelectInstrumentTypeWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="SelectInstrumentTypeWindowViewModel.cs" />
<Page Include="VerificationEditWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>