Files
XLAB/XLAB2/PsvPrintService.cs
Курнат Андрей a47a7a5a3b edit
2026-03-19 23:31:41 +03:00

588 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace XLAB2
{
internal sealed class PsvPrintService
{
private const int PrintDialogId = 88;
private const int WordAlertsNone = 0;
private const int WordCloseDoNotSaveChanges = 0;
public void PrintDocument(PsvDocumentSummary document, IReadOnlyList<PsvDocumentLine> lines)
{
if (document == null)
{
throw new ArgumentNullException("document");
}
if (lines == null)
{
throw new ArgumentNullException("lines");
}
if (lines.Count == 0)
{
throw new InvalidOperationException("В выбранном ПСВ нет строк для печати.");
}
var templateFileName = document.IssuedOn.HasValue ? "ClosePsv.docx" : "OpenPsv.docx";
PrintWordTemplate(
templateFileName,
document.DocumentNumber,
delegate(object wordDocument)
{
PopulatePsvTemplate((dynamic)wordDocument, document, lines);
});
}
public void PrintVerificationDocument(PsvDocumentLine line)
{
if (line == null)
{
throw new ArgumentNullException("line");
}
if (!line.IsPassed.HasValue)
{
throw new InvalidOperationException("Для печати документа о поверке не указан результат поверки.");
}
if (string.IsNullOrWhiteSpace(line.VerificationDocumentNumber))
{
throw new InvalidOperationException("Для печати документа о поверке не указан номер документа.");
}
if (!line.VerificationPerformedOn.HasValue && !line.VerificationDocumentDate.HasValue)
{
throw new InvalidOperationException("Для печати документа о поверке не указана дата поверки.");
}
if (!line.IsPassed.Value && string.IsNullOrWhiteSpace(line.RejectionReason))
{
throw new InvalidOperationException("Для печати извещения о непригодности не указана причина непригодности.");
}
var templateFileName = line.IsPassed.Value ? "Svid.docx" : "Izv.docx";
PrintWordTemplate(
templateFileName,
line.VerificationDocumentNumber,
delegate(object wordDocument)
{
PopulateVerificationTemplate((dynamic)wordDocument, line);
});
}
private static void PrintWordTemplate(string templateFileName, string printNumber, Action<object> populateDocument)
{
if (string.IsNullOrWhiteSpace(templateFileName))
{
throw new ArgumentException("Не указано имя шаблона печати.", "templateFileName");
}
if (populateDocument == null)
{
throw new ArgumentNullException("populateDocument");
}
var templatePath = ResolveTemplatePath(templateFileName);
var workingCopyPath = CreateWorkingCopy(templatePath, printNumber);
object wordApplication = null;
object wordDocument = null;
object dialogs = null;
object printDialog = null;
try
{
var wordType = Type.GetTypeFromProgID("Word.Application");
if (wordType == null)
{
throw new InvalidOperationException("Microsoft Word не найден. Печать ПСВ по шаблону DOCX недоступна.");
}
wordApplication = Activator.CreateInstance(wordType);
dynamic word = wordApplication;
word.Visible = true;
word.DisplayAlerts = WordAlertsNone;
wordDocument = word.Documents.Open(
FileName: workingCopyPath,
ConfirmConversions: false,
ReadOnly: false,
AddToRecentFiles: false,
Visible: true);
populateDocument(wordDocument);
((dynamic)wordDocument).Save();
word.Activate();
((dynamic)wordDocument).Activate();
dialogs = word.Dialogs;
printDialog = ((dynamic)dialogs).Item(PrintDialogId);
((dynamic)printDialog).Show();
((dynamic)wordDocument).Close(WordCloseDoNotSaveChanges);
wordDocument = null;
word.Quit();
wordApplication = null;
}
finally
{
ReleaseComObject(printDialog);
ReleaseComObject(dialogs);
ReleaseComObject(wordDocument);
ReleaseComObject(wordApplication);
TryDeleteFile(workingCopyPath);
}
}
private static void PopulatePsvTemplate(dynamic document, PsvDocumentSummary summary, IReadOnlyList<PsvDocumentLine> lines)
{
var groupedLines = BuildPrintedGroups(lines, summary.IssuedOn.HasValue);
ReplacePlaceholder(document, "number", NormalizeText(summary.DocumentNumber));
ReplacePlaceholder(document, "div", NormalizeText(summary.CustomerName));
ReplacePlaceholder(document, "date", FormatDate(summary.AcceptedOn));
ReplacePlaceholder(document, "count", lines.Count.ToString(CultureInfo.InvariantCulture));
ReplacePlaceholder(document, "person", string.Empty);
if (summary.IssuedOn.HasValue)
{
ReplacePlaceholder(document, "today", FormatDate(summary.IssuedOn));
ReplacePlaceholder(document, "good", lines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }).ToString(CultureInfo.InvariantCulture));
ReplacePlaceholder(document, "bad", lines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }).ToString(CultureInfo.InvariantCulture));
}
else
{
var dueDate = summary.AcceptedOn.HasValue
? summary.AcceptedOn.Value.AddDays(30)
: (DateTime?)null;
ReplacePlaceholder(document, "next", FormatDate(dueDate));
}
FillTable(document, groupedLines);
}
private static void PopulateVerificationTemplate(dynamic document, PsvDocumentLine line)
{
var verificationDate = ResolveVerificationDate(line);
ReplacePlaceholder(document, "number", NormalizeText(line.VerificationDocumentNumber));
ReplacePlaceholder(document, "nextpovdate", FormatDate(ResolveNextVerificationDate(line, verificationDate)));
ReplacePlaceholder(document, "name", NormalizeText(line.InstrumentName));
ReplacePlaceholder(document, "type (рег. № gr)", BuildVerificationTypeText(line));
ReplacePlaceholder(document, "serial", NormalizeText(line.SerialNumber));
ReplacePlaceholder(document, "erial", NormalizeText(line.SerialNumber));
ReplacePlaceholder(document, "method", string.Empty);
ReplacePlaceholder(document, "temp", string.Empty);
ReplacePlaceholder(document, "hum", string.Empty);
ReplacePlaceholder(document, "press", string.Empty);
ReplacePlaceholder(document, "person", NormalizeText(line.VerifierName));
ReplacePlaceholder(document, "reason", NormalizeText(line.RejectionReason));
ReplacePlaceholder(document, "date", FormatDate(verificationDate));
}
private static void FillTable(dynamic document, IReadOnlyList<PrintedGroupRow> rowsToPrint)
{
dynamic table = null;
try
{
table = document.Tables.Item(1);
for (var index = 0; index < rowsToPrint.Count; index++)
{
var rowData = rowsToPrint[index];
dynamic row = null;
try
{
row = table.Rows.Add();
SetCellText(row, 1, (index + 1).ToString(CultureInfo.InvariantCulture), true);
SetCellText(row, 2, rowData.InstrumentName, false);
SetCellText(row, 3, rowData.InstrumentType, false);
SetCellText(row, 4, rowData.RangeText, false);
SetCellText(row, 5, rowData.SerialNumberText, false);
SetCellText(row, 6, rowData.GroupCount.ToString(CultureInfo.InvariantCulture), true);
SetCellText(row, 7, rowData.PassedCount > 0 ? rowData.PassedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true);
SetCellText(row, 8, rowData.FailedCount > 0 ? rowData.FailedCount.ToString(CultureInfo.InvariantCulture) : string.Empty, true);
SetCellText(row, 9, rowData.Notes, false);
}
finally
{
ReleaseComObject(row);
}
}
}
finally
{
ReleaseComObject(table);
}
}
private static void SetCellText(dynamic row, int columnIndex, string value, bool centerAlign)
{
dynamic cell = null;
try
{
cell = row.Cells.Item(columnIndex);
cell.Range.Text = NormalizeText(value);
cell.Range.Bold = 0;
cell.Range.Font.Underline = 0;
cell.Range.ParagraphFormat.Alignment = centerAlign ? 1 : 0;
}
finally
{
ReleaseComObject(cell);
}
}
private static void ReplacePlaceholder(dynamic document, string placeholder, string replacement)
{
dynamic range = null;
dynamic find = null;
try
{
range = document.Content;
find = range.Find;
find.ClearFormatting();
find.Replacement.ClearFormatting();
find.Execute(
FindText: placeholder,
MatchCase: false,
MatchWholeWord: false,
MatchWildcards: false,
MatchSoundsLike: false,
MatchAllWordForms: false,
Forward: true,
Wrap: 1,
Format: false,
ReplaceWith: NormalizeText(replacement),
Replace: 2);
}
finally
{
ReleaseComObject(find);
ReleaseComObject(range);
}
}
private static IReadOnlyList<PrintedGroupRow> BuildPrintedGroups(IEnumerable<PsvDocumentLine> lines, bool includeClosedNotes)
{
return (lines ?? Enumerable.Empty<PsvDocumentLine>())
.GroupBy(delegate(PsvDocumentLine line)
{
return new PrintGroupKey
{
InstrumentType = NormalizeText(line == null ? null : line.InstrumentType),
RangeText = NormalizeText(line == null ? null : line.RangeText),
RegistryNumber = NormalizeText(line == null ? null : line.RegistryNumber)
};
})
.OrderBy(delegate(IGrouping<PrintGroupKey, PsvDocumentLine> group) { return group.Key.InstrumentType; }, StringComparer.OrdinalIgnoreCase)
.ThenBy(delegate(IGrouping<PrintGroupKey, PsvDocumentLine> group) { return group.Key.RegistryNumber; }, StringComparer.OrdinalIgnoreCase)
.ThenBy(delegate(IGrouping<PrintGroupKey, PsvDocumentLine> group) { return group.Key.RangeText; }, StringComparer.OrdinalIgnoreCase)
.Select(delegate(IGrouping<PrintGroupKey, PsvDocumentLine> group)
{
var materializedLines = group.Where(delegate(PsvDocumentLine line) { return line != null; }).ToList();
return new PrintedGroupRow
{
InstrumentName = BuildInstrumentNameText(materializedLines),
InstrumentType = group.Key.InstrumentType,
RangeText = group.Key.RangeText,
SerialNumberText = BuildSerialNumberText(materializedLines),
GroupCount = materializedLines.Count,
PassedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == true; }),
FailedCount = materializedLines.Count(delegate(PsvDocumentLine line) { return line.IsPassed == false; }),
Notes = includeClosedNotes ? BuildClosedNotesText(materializedLines) : string.Empty
};
})
.ToList();
}
private static string BuildInstrumentNameText(IReadOnlyList<PsvDocumentLine> lines)
{
return string.Join("; ", lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line.InstrumentName); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToArray());
}
private static string BuildSerialNumberText(IReadOnlyList<PsvDocumentLine> lines)
{
var serialNumbers = lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line.SerialNumber); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToList();
if (serialNumbers.Count == 0)
{
return string.Empty;
}
if (serialNumbers.Count > 3)
{
return string.Format(CultureInfo.InvariantCulture, "{0} зав. номеров", serialNumbers.Count);
}
return string.Join(", ", serialNumbers.ToArray());
}
private static string BuildClosedNotesText(IReadOnlyList<PsvDocumentLine> lines)
{
var parts = new List<string>();
var verificationDocuments = lines
.Select(FormatVerificationDocument)
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToList();
if (verificationDocuments.Count > 0)
{
parts.Add("Документы: " + string.Join("; ", verificationDocuments.ToArray()));
}
var stickerNumbers = lines
.Select(delegate(PsvDocumentLine line) { return NormalizeText(line.StickerNumber); })
.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); })
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(delegate(string value) { return value; }, StringComparer.OrdinalIgnoreCase)
.ToList();
if (stickerNumbers.Count > 0)
{
parts.Add("Наклейки: " + string.Join(", ", stickerNumbers.ToArray()));
}
return string.Join(". ", parts.ToArray());
}
private static string FormatVerificationDocument(PsvDocumentLine line)
{
if (line == null)
{
return string.Empty;
}
var number = NormalizeText(line.VerificationDocumentNumber);
var date = FormatDate(line.VerificationDocumentDate);
if (string.IsNullOrWhiteSpace(number))
{
return date;
}
if (string.IsNullOrWhiteSpace(date))
{
return number;
}
return string.Format(CultureInfo.InvariantCulture, "{0} от {1}", number, date);
}
private static string BuildVerificationTypeText(PsvDocumentLine line)
{
if (line == null)
{
return string.Empty;
}
var type = NormalizeText(line.InstrumentType);
var registryNumber = NormalizeText(line.RegistryNumber);
if (string.IsNullOrWhiteSpace(type))
{
return string.Empty;
}
return string.IsNullOrWhiteSpace(registryNumber)
? type
: string.Format(CultureInfo.InvariantCulture, "{0} (рег. № {1})", type, registryNumber);
}
private static DateTime? ResolveVerificationDate(PsvDocumentLine line)
{
if (line == null)
{
return null;
}
return line.VerificationPerformedOn ?? line.VerificationDocumentDate;
}
private static DateTime? ResolveNextVerificationDate(PsvDocumentLine line, DateTime? verificationDate)
{
if (line == null || !line.IsPassed.HasValue || !line.IsPassed.Value)
{
return null;
}
if (!verificationDate.HasValue || line.PeriodMonths <= 0)
{
return null;
}
return verificationDate.Value.AddMonths(line.PeriodMonths);
}
private static string ResolveTemplatePath(string fileName)
{
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
var candidates = new[]
{
Path.Combine(baseDirectory, fileName),
Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", fileName)),
Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", fileName))
};
var templatePath = candidates.FirstOrDefault(File.Exists);
if (templatePath == null)
{
throw new FileNotFoundException(string.Format("Не найден шаблон печати ПСВ: {0}.", fileName), fileName);
}
return templatePath;
}
private static string CreateWorkingCopy(string templatePath, string documentNumber)
{
var tempDirectory = Path.Combine(Path.GetTempPath(), "XLAB", "Print");
Directory.CreateDirectory(tempDirectory);
var safeDocumentNumber = string.IsNullOrWhiteSpace(documentNumber)
? "PSV"
: string.Concat(documentNumber.Where(delegate(char ch)
{
return !Path.GetInvalidFileNameChars().Contains(ch);
}));
if (string.IsNullOrWhiteSpace(safeDocumentNumber))
{
safeDocumentNumber = "PSV";
}
var tempFileName = string.Format(
CultureInfo.InvariantCulture,
"{0}_{1:yyyyMMdd_HHmmss_fff}_{2}",
safeDocumentNumber,
DateTime.Now,
Path.GetFileName(templatePath));
var workingCopyPath = Path.Combine(tempDirectory, tempFileName);
File.Copy(templatePath, workingCopyPath, true);
return workingCopyPath;
}
private static string FormatDate(DateTime? value)
{
return value.HasValue
? value.Value.ToString("dd.MM.yyyy", CultureInfo.InvariantCulture)
: string.Empty;
}
private static string NormalizeText(string value)
{
return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
}
private static void TryDeleteFile(string path)
{
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
{
return;
}
try
{
File.Delete(path);
}
catch (IOException)
{
}
catch (UnauthorizedAccessException)
{
}
}
private static void ReleaseComObject(object value)
{
if (value == null || !Marshal.IsComObject(value))
{
return;
}
try
{
Marshal.FinalReleaseComObject(value);
}
catch (ArgumentException)
{
}
}
private sealed class PrintedGroupRow
{
public int FailedCount { get; set; }
public int GroupCount { get; set; }
public string InstrumentName { get; set; }
public string InstrumentType { get; set; }
public string Notes { get; set; }
public int PassedCount { get; set; }
public string RangeText { get; set; }
public string SerialNumberText { get; set; }
}
private sealed class PrintGroupKey : IEquatable<PrintGroupKey>
{
public string InstrumentType { get; set; }
public string RangeText { get; set; }
public string RegistryNumber { get; set; }
public bool Equals(PrintGroupKey other)
{
return other != null
&& string.Equals(InstrumentType ?? string.Empty, other.InstrumentType ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(RangeText ?? string.Empty, other.RangeText ?? string.Empty, StringComparison.OrdinalIgnoreCase)
&& string.Equals(RegistryNumber ?? string.Empty, other.RegistryNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}
public override bool Equals(object obj)
{
return Equals(obj as PrintGroupKey);
}
public override int GetHashCode()
{
return string.Concat(
(InstrumentType ?? string.Empty).ToUpperInvariant(), "|",
(RangeText ?? string.Empty).ToUpperInvariant(), "|",
(RegistryNumber ?? string.Empty).ToUpperInvariant())
.GetHashCode();
}
}
}
}