Report-Erzeugung auf neue Bedürfnisse aktualisiert

This commit is contained in:
Daniel Schick 2017-11-05 20:15:03 +00:00
parent e93e065443
commit dd453176e4
12 changed files with 330 additions and 22 deletions

Binary file not shown.

View File

@ -45,6 +45,9 @@
<setting name="LogoPath" serializeAs="String"> <setting name="LogoPath" serializeAs="String">
<value /> <value />
</setting> </setting>
<setting name="DeleteFileAfterSend" serializeAs="String">
<value>False</value>
</setting>
</bsmd.ReportGenerator.Properties.Settings> </bsmd.ReportGenerator.Properties.Settings>
</applicationSettings> </applicationSettings>
</configuration> </configuration>

View File

@ -51,6 +51,23 @@ namespace bsmd.ReportGenerator
return document; return document;
} }
internal static Document CreateSingleClassDocument(MessageCore reportCore, List<Message> reportMessages,
Dictionary<string, string> coverInfos, string classes, ReportingParty rp)
{
Document document = new Document();
document.Info.Title = string.Format("{0} for {1}", classes, reportCore.DisplayId);
document.Info.Subject = "Reporting class info";
document.Info.Author = Properties.Settings.Default.ReportAuthor;
BSMDDocument.DefineSingleStyle(document);
BSMDDocument.DefineSingleHeader(document, coverInfos, rp);
BSMDDocument.DefineContentSectionInitial(document);
return document;
}
/// <summary> /// <summary>
/// create the final output document /// create the final output document
/// </summary> /// </summary>
@ -130,9 +147,43 @@ namespace bsmd.ReportGenerator
style.ParagraphFormat.Font.Color = Colors.Blue; style.ParagraphFormat.Font.Color = Colors.Blue;
} }
static void DefineSingleStyle(Document doc)
{
Style style = doc.Styles["Normal"];
style.Font.Name = "Verdana";
Style tableStyle = doc.Styles.AddStyle("Table", "Normal");
tableStyle.Font.Name = "Verdana";
tableStyle.Font.Size = 9;
// usw
Style tableHeaderStyle = doc.Styles.AddStyle("TableHeader", "Normal");
//tableHeaderStyle.Font.Name = "Verdana";
tableHeaderStyle.Font.Size = 10;
tableHeaderStyle.Font.Bold = true;
Style tableValueStyle = doc.Styles.AddStyle("TableValue", "Normal");
tableValueStyle.Font.Size = 9;
tableValueStyle.Font.Bold = true;
style = doc.Styles["Heading2"];
style.Font.Size = 12;
style.Font.Bold = true;
style.ParagraphFormat.SpaceBefore = 6;
style.ParagraphFormat.SpaceAfter = 6;
style = doc.Styles["Heading3"];
style.Font.Size = 10;
style.Font.Bold = true;
style.Font.Italic = true;
style.ParagraphFormat.SpaceBefore = 6;
style.ParagraphFormat.SpaceAfter = 3;
}
#endregion #endregion
#region Cover #region Cover (alt, EU-NOAD)
/// <summary> /// <summary>
/// Defines the cover page. /// Defines the cover page.
@ -177,13 +228,110 @@ namespace bsmd.ReportGenerator
row.Cells[0].AddParagraph(key); row.Cells[0].AddParagraph(key);
row.Cells[1].AddParagraph(coverInfos[key] ?? string.Empty); row.Cells[1].AddParagraph(coverInfos[key] ?? string.Empty);
} }
} }
#endregion #endregion
#region "Neuer" EUREPORT Header Block für Einzelnachrichten
internal static void DefineSingleHeader(Document document, Dictionary<string, string> coverInfos, ReportingParty rp)
{
Section section = document.AddSection();
section.PageSetup.StartingNumber = 1;
Table table = document.LastSection.AddTable();
table.Style = "Table";
table.Borders.Color = Colors.LightGray;
table.Borders.Width = 0.25;
// Define Colums
Column col = table.AddColumn(80); // die Bildspalte
col.Format.Alignment = ParagraphAlignment.Center;
col = table.AddColumn(70);
col = table.AddColumn(140);
col = table.AddColumn(60);
col = table.AddColumn(140);
Row row = table.AddRow();
Image logoImage = row.Cells[0].AddImage(Properties.Settings.Default.LogoPath);
logoImage.Width = 80;
row.Cells[0].MergeDown = 6;
Paragraph paragraph = row.Cells[1].AddParagraph("Visit Details");
row.Cells[1].MergeRight = 3;
row.Cells[1].Shading.Color = Colors.LightGray;
paragraph.Style = "TableHeader";
row = table.AddRow();
row.Cells[1].AddParagraph("Visit-ID");
paragraph = row.Cells[2].AddParagraph(coverInfos["Visit-ID"]);
paragraph.Style = "TableValue";
row.Cells[3].AddParagraph("Name");
paragraph = row.Cells[4].AddParagraph(coverInfos["Ship"]);
paragraph.Style = "TableValue";
row = table.AddRow();
row.Cells[1].AddParagraph("Port of call");
paragraph = row.Cells[2].AddParagraph(coverInfos["Port of call"]);
paragraph.Style = "TableValue";
row.Cells[3].AddParagraph("ETA");
paragraph = row.Cells[4].AddParagraph(coverInfos["ETA"]);
paragraph.Style = "TableValue";
row = table.AddRow();
row.Shading.Color = Colors.LightGray;
paragraph = row.Cells[1].AddParagraph("Reporting Party");
row.Cells[1].MergeRight = 3;
paragraph.Style = "TableHeader";
row = table.AddRow();
row.Cells[1].AddParagraph("Organization");
paragraph = row.Cells[2].AddParagraph(rp.Name);
paragraph.Style = "TableValue";
row.Cells[2].MergeRight = 2;
row = table.AddRow();
row.Cells[1].AddParagraph("Last name");
paragraph = row.Cells[2].AddParagraph(rp.LastName);
paragraph.Style = "TableValue";
row.Cells[3].AddParagraph("City");
paragraph = row.Cells[4].AddParagraph(rp.City);
paragraph.Style = "TableValue";
row = table.AddRow();
row.Cells[1].AddParagraph("Phone");
paragraph = row.Cells[2].AddParagraph(rp.Phone);
paragraph.Style = "TableValue";
row.Cells[3].AddParagraph("Email");
paragraph = row.Cells[4].AddParagraph(rp.EMail);
paragraph.Style = "TableValue";
}
#endregion
#region setup, header and footers #region setup, header and footers
public static void DefineContentSectionInitial(Document document)
{
Section section = document.LastSection;
section.PageSetup.OddAndEvenPagesHeaderFooter = false;
section.PageSetup.Orientation = Orientation.Portrait;
HeaderFooter header = section.Headers.Primary;
header.AddParagraph(string.Format("EUREPORT - created {0}", DateTime.Now.ToString()));
Paragraph paragraph = new Paragraph();
paragraph.AddTab();
paragraph.AddPageField();
paragraph.AddText(" / ");
paragraph.AddNumPagesField();
section.Footers.Primary.Add(paragraph);
}
/// <summary> /// <summary>
/// Defines page setup, headers, and footers. /// Defines page setup, headers, and footers.
/// </summary> /// </summary>
@ -211,7 +359,7 @@ namespace bsmd.ReportGenerator
// Add clone of paragraph to footer for odd pages. Cloning is necessary because an object must // Add clone of paragraph to footer for odd pages. Cloning is necessary because an object must
// not belong to more than one other object. If you forget cloning an exception is thrown. // not belong to more than one other object. If you forget cloning an exception is thrown.
section.Footers.EvenPage.Add(paragraph.Clone()); section.Footers.EvenPage.Add(paragraph.Clone());
} }
#endregion #endregion
@ -301,7 +449,7 @@ namespace bsmd.ReportGenerator
BSMDDocument.AddActualTableParagraph(document, childParagraph.MessageText, true); BSMDDocument.AddActualTableParagraph(document, childParagraph.MessageText, true);
} }
} }
} }
#region CREW #region CREW
private static void CreateCrewTable(Document document, Message message) private static void CreateCrewTable(Document document, Message message)
@ -778,16 +926,16 @@ namespace bsmd.ReportGenerator
private static void AddActualTableParagraph(Document document, List<KeyValuePair<string, string>> messageText, bool isSubTable) private static void AddActualTableParagraph(Document document, List<KeyValuePair<string, string>> messageText, bool isSubTable)
{ {
Table table = document.LastSection.AddTable(); Table table = document.LastSection.AddTable();
table.Borders.Color = Colors.LightGray;
// table.LeftPadding = new Unit(0.5, UnitType.Centimeter); // table.LeftPadding = new Unit(0.5, UnitType.Centimeter);
Column leadColumn = table.AddColumn(Unit.FromCentimeter(8)); Column leadColumn = table.AddColumn(200);
leadColumn.Format.Alignment = ParagraphAlignment.Left; leadColumn.Format.Alignment = ParagraphAlignment.Left;
Column mainColumn = table.AddColumn(Unit.FromCentimeter(8)); Column mainColumn = table.AddColumn(290);
mainColumn.Format.Alignment = ParagraphAlignment.Left; mainColumn.Format.Alignment = ParagraphAlignment.Left;
for (int i = 0; i < messageText.Count; i++) for (int i = 0; i < messageText.Count; i++)

View File

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Runtime Version:4.0.30319.34209 // Runtime Version:4.0.30319.42000
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.
@ -12,7 +12,7 @@ namespace bsmd.ReportGenerator.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@ -114,5 +114,14 @@ namespace bsmd.ReportGenerator.Properties {
return ((string)(this["LogoPath"])); return ((string)(this["LogoPath"]));
} }
} }
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool DeleteFileAfterSend {
get {
return ((bool)(this["DeleteFileAfterSend"]));
}
}
} }
} }

View File

@ -35,5 +35,8 @@
<Setting Name="LogoPath" Type="System.String" Scope="Application"> <Setting Name="LogoPath" Type="System.String" Scope="Application">
<Value Profile="(Default)" /> <Value Profile="(Default)" />
</Setting> </Setting>
<Setting Name="DeleteFileAfterSend" Type="System.Boolean" Scope="Application">
<Value Profile="(Default)">False</Value>
</Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>

View File

@ -1,4 +1,7 @@
using System; // Copyright (c) 2015-2017 schick Informatik
// Service entry point
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -10,6 +13,7 @@ using MigraDoc.DocumentObjectModel;
using bsmd.database; using bsmd.database;
using bsmd.email; using bsmd.email;
using System.Text;
namespace bsmd.ReportGenerator namespace bsmd.ReportGenerator
{ {
@ -27,6 +31,8 @@ namespace bsmd.ReportGenerator
InitializeComponent(); InitializeComponent();
} }
#region windows service overrides
protected override void OnStart(string[] args) protected override void OnStart(string[] args)
{ {
this.EventLog.Source = this.ServiceName; this.EventLog.Source = this.ServiceName;
@ -40,6 +46,15 @@ namespace bsmd.ReportGenerator
this.DoOnce(); this.DoOnce();
} }
protected override void OnStop()
{
_log.Info("Report Service stopping.");
}
#endregion
#region timer functions
private void Init(string[] args) private void Init(string[] args)
{ {
this._timer = new Timer(); this._timer = new Timer();
@ -96,19 +111,16 @@ namespace bsmd.ReportGenerator
{ {
this.processRunning = false; this.processRunning = false;
} }
} }
protected override void OnStop()
{
_log.Info("Report Service stopping.");
}
internal void DoOnce() internal void DoOnce()
{ {
this._timer_Elapsed(null, null); this._timer_Elapsed(null, null);
} }
#region create and send report #endregion
#region create and send "old" EU-NOAD report
private void CreateReport(MessageCore reportCore) private void CreateReport(MessageCore reportCore)
{ {
@ -118,7 +130,7 @@ namespace bsmd.ReportGenerator
bool isReportUpdate = reportCore.ReportStatus != MessageCore.ReportStatusEnum.COMPLETE; bool isReportUpdate = reportCore.ReportStatus != MessageCore.ReportStatusEnum.COMPLETE;
coverInfos.Add("Type", reportCore.HerbergReportType); coverInfos.Add("Type", reportCore.HerbergReportType);
coverInfos.Add("Ship", DBManager.Instance.GetShipNameFromCore(reportCore)); coverInfos.Add("Ship", DBManager.Instance.GetShipNameFromCore(reportCore));
coverInfos.Add("E-Mail Ship", reportCore.HerbergEmailContactReportingVessel); coverInfos.Add("E-Mail Ship", reportCore.HerbergEmailContactReportingVessel);
coverInfos.Add("IMO", reportCore.IMO); coverInfos.Add("IMO", reportCore.IMO);
DateTime eta = reportCore.ETA ?? (reportCore.ETAKielCanal ?? new DateTime(0)); DateTime eta = reportCore.ETA ?? (reportCore.ETAKielCanal ?? new DateTime(0));
@ -213,19 +225,93 @@ namespace bsmd.ReportGenerator
} }
} }
#region create (and send) "single" report (a report per message class)
internal void CreateSingleReport(MessageCore reportCore) internal void CreateSingleReport(MessageCore reportCore)
{ {
List<Message> messages = DBManager.Instance.GetMessagesForCore(reportCore, DBManager.MessageLoad.ALL); List<Message> messages = DBManager.Instance.GetMessagesForCore(reportCore, DBManager.MessageLoad.ALL);
List<Message> reportMessages = new List<Message>();
messages.Sort(new ANSWMessageComparer()); messages.Sort(new ANSWMessageComparer());
Dictionary<string, string> coverInfos = new Dictionary<string, string>(); StringBuilder sb = new StringBuilder();
Guid reportingPartyId = Guid.Empty;
string subject = string.Format("NEW EU-NOAD message IMO {0}", reportCore.IMO); foreach (Message aMessage in messages)
{
if (aMessage.InternalStatus == Message.BSMDStatus.REPORT)
{
reportMessages.Add(aMessage);
aMessage.InternalStatus = Message.BSMDStatus.PREPARE;
DBManager.Instance.Save(aMessage);
sb.Append(aMessage.MessageNotificationClassDisplay);
sb.Append(" ");
if (aMessage.ReportingPartyId.HasValue)
reportingPartyId = aMessage.ReportingPartyId.Value;
}
}
string classes = sb.ToString();
if (!classes.IsNullOrEmpty())
{
ReportingParty rp = null;
if (DBManager.Instance.GetReportingPartyDict().ContainsKey(reportingPartyId))
{
rp = DBManager.Instance.GetReportingPartyDict()[reportingPartyId];
Dictionary<string, string> coverInfos = new Dictionary<string, string>();
coverInfos.Add("Ship", DBManager.Instance.GetShipNameFromCore(reportCore));
coverInfos.Add("ETA", reportCore.ETADisplay.ToString());
coverInfos.Add("Port of call", reportCore.PoC);
coverInfos.Add("Visit-ID", reportCore.DisplayId);
coverInfos.Add("Class", classes);
string subject = string.Format("PDF report IMO {0} class(es) {1}", reportCore.IMO, classes);
Document migraDocument = BSMDDocument.CreateSingleClassDocument(reportCore, reportMessages, coverInfos, classes, rp);
// print messages to document
foreach (Message message in reportMessages)
{
BSMDDocument.AddNSWMessageParagraph(migraDocument, message);
}
// prepare and send E-Mail with generated attachment
string fullPath = string.Format("{0}\\{1}.pdf", Properties.Settings.Default.OutputDirectory, reportCore.Id);
BSMDDocument.RenderDocument(migraDocument, fullPath);
_log.InfoFormat("Document created for MessageCoreId {0}, IMO {1}", reportCore.Id, reportCore.IMO);
List<string> attachments = new List<string>();
attachments.Add(fullPath);
BSMDMail.SendNSWReportWithAttachments(subject, attachments, rp.EMail);
// remove
if (Properties.Settings.Default.DeleteFileAfterSend)
{
try
{
File.Delete(fullPath);
}
catch (Exception ex)
{
_log.ErrorFormat("Cannot delete {0}: {1}", fullPath, ex.Message);
}
}
}
else
{
_log.WarnFormat("Default reporting party not set on core {0}", reportCore.Id);
}
}
else
{
_log.WarnFormat("Core {0} set for report, but no message classes!", reportCore.Id);
}
// reset report status // reset report status
reportCore.ReportStatus = MessageCore.ReportStatusEnum.NONE; reportCore.ReportStatus = MessageCore.ReportStatusEnum.NONE;
DBManager.Instance.Save(reportCore); DBManager.Instance.Save(reportCore);
} }
#endregion
#endregion #endregion

View File

@ -108,6 +108,7 @@
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <None Include="App.config" />
<None Include="..\bsmdKey.snk" /> <None Include="..\bsmdKey.snk" />
<None Include="bsmd.ReportGenerator.licenseheader" />
<None Include="packages.config" /> <None Include="packages.config" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>
@ -132,6 +133,9 @@
<Name>bsmd.email</Name> <Name>bsmd.email</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="readme.txt" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@ -0,0 +1,15 @@
extensions: designer.cs generated.cs
extensions: .cs .cpp .h
// Copyright (c) 2015-2017 schick Informatik
//
extensions: .aspx .ascx
<%--
Copyright (c) 2015-2017 schick Informatik
--%>
extensions: .vb
' Copyright (c) 2015-2017 schick Informatik
extensions: .xml .config .xsd
<!--
Copyright (c) 2015-2017 schick Informatik
-->

View File

@ -0,0 +1,17 @@
Es gibt zwei unterschiedliche Verfahren:
1) MigraDoc Document (MigraDoc.*.dll)
Das erfolgt im HTML Style bei dem man einfach Paragrafen an ein Dokument anfügt und der
Flow wird dann vom Renderer berechnet. Es gehen auch absolute Positionen aber keine
grafischen Elemente außer Bildern und Tabellen
2) PdfDocument (PdfSharp.*.dll)
Das ist der "Drawing" Style, hier können auch MigraDoc Dokumente eingebettet werden wenn
man beides machen will. Es geht aber über eine graph. Context (XGraphics) und absolute
Koordinaten. Man kann damit z.B. auch mehrere MigraDoc's in kleine Rectangles "reinrendern".
Auf dieser Seite wird das sehr gut an einem Beispiel gegenübergestellt:
http://www.pdfsharp.net/wiki/MixMigraDocAndPdfSharp-sample.ashx
Koordinatensystem ist "Points" (72pt = 1 inch), es gibt Umrechnungsfunktionen für cm etc.
In Points hat eine DIN-A4 Seite dann die Ausdehnung von 595 x 842.

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="log4net" version="2.0.8" targetFramework="net45" />
</packages>

View File

@ -71,11 +71,13 @@ namespace bsmd.dbh
// neue VISIT - ID // neue VISIT - ID
aMessage.MessageCore.VisitId = VisitId; aMessage.MessageCore.VisitId = VisitId;
aMessage.MessageCore.BSMDStatusInternal = MessageCore.BSMDStatus.PREPARE; aMessage.MessageCore.BSMDStatusInternal = MessageCore.BSMDStatus.PREPARE;
aMessage.SendSuccess = true;
DBManager.Instance.Save(aMessage.MessageCore); DBManager.Instance.Save(aMessage.MessageCore);
break; break;
case dbh.response.RootType.TRANSIT: case dbh.response.RootType.TRANSIT:
aMessage.MessageCore.TransitId = TransitId; aMessage.MessageCore.TransitId = TransitId;
aMessage.MessageCore.BSMDStatusInternal = MessageCore.BSMDStatus.PREPARE; aMessage.MessageCore.BSMDStatusInternal = MessageCore.BSMDStatus.PREPARE;
aMessage.SendSuccess = true;
DBManager.Instance.Save(aMessage.MessageCore); DBManager.Instance.Save(aMessage.MessageCore);
break; break;
case dbh.response.RootType.CANCEL: case dbh.response.RootType.CANCEL:
@ -99,6 +101,7 @@ namespace bsmd.dbh
(int) ReportingClassesFull[0].ReportingClass[0] == (int)aMessage.MessageNotificationClass) (int) ReportingClassesFull[0].ReportingClass[0] == (int)aMessage.MessageNotificationClass)
{ {
// this was successful, save status to MessageHeader // this was successful, save status to MessageHeader
aMessage.SendSuccess = true;
aMessage.InternalStatus = Message.BSMDStatus.CONFIRMED; aMessage.InternalStatus = Message.BSMDStatus.CONFIRMED;
aMessage.Status = Message.MessageStatus.ACCEPTED; aMessage.Status = Message.MessageStatus.ACCEPTED;
} }
@ -125,7 +128,7 @@ namespace bsmd.dbh
error.MessageHeaderId = aMessage.Id.Value; error.MessageHeaderId = aMessage.Id.Value;
aMessage.InternalStatus = Message.BSMDStatus.ERROR; aMessage.InternalStatus = Message.BSMDStatus.ERROR;
DBManager.Instance.Save(error); DBManager.Instance.Save(error);
aMessage.SendSuccess = false;
break; break;
case dbh.response.RootMessageType.VIOLATION: case dbh.response.RootMessageType.VIOLATION:

View File

@ -45,7 +45,23 @@ namespace bsmd.hisnord
{ {
_log.WarnFormat("Transmitter process not exited within {0} minute", Properties.Settings.Default.BatchTimeoutMins); _log.WarnFormat("Transmitter process not exited within {0} minute", Properties.Settings.Default.BatchTimeoutMins);
// kill Kill KILLLL!!! // kill Kill KILLLL!!!
process.Kill(); try
{
process.Kill();
process.WaitForExit(500);
if (process.HasExited)
{
_log.Info("Transmitter killed, process exited");
}
else
{
_log.Warn("Killing Transmitter failed, this thing hangs and we're in trouble now");
}
}
catch (Exception e)
{
_log.WarnFormat("Killing Transmitter failed: {0}", e.Message);
}
} }
string errorString = process.StandardError.ReadToEnd(); string errorString = process.StandardError.ReadToEnd();
if(!errorString.IsNullOrEmpty()) if(!errorString.IsNullOrEmpty())