200825PX Added PaymentReceiptProcess, moved FormMail code to ReplaceReflectEntity<T> for more versatility. Creating SVG basically works

This commit is contained in:
phantomix 2020-08-25 17:37:03 +02:00
parent fc97fa4695
commit 63680d6f7e
7 changed files with 194 additions and 6 deletions

View File

@ -44,7 +44,7 @@ namespace dezentrale
{
public class Program
{
public static uint VersionNumber { get; private set; } = 0x20081400;
public static uint VersionNumber { get; private set; } = 0x20082500;
public static string VersionString { get; private set; } = $"{VersionNumber:x}";
public static string AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);

View File

@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using dezentrale.model;
using dezentrale.model.money;
namespace dezentrale.core
{
public interface IPaymentReceiptProcessData
{
string SvgTemplate { get; set; }
string OutputDirectory { get; set; }
string FileNamePattern { get; set; }
DateTime StartDate { get; set; }
DateTime EndDate { get; set; }
}
//In order to be able to ReplaceReflect, we need to have the SvgData and SvgFileName field separated in a class
class SvgFile : ReplaceReflectEntity<SvgFile>
{
public string SvgFileName { get; set; } = "";
public string SvgData { get; set; } = "";
public SvgFile() { }
public SvgFile(string fileName)
{
SvgFileName = fileName;
SvgData = File.ReadAllText(fileName);
}
}
//! \short Import functionality for the database contents
//! \brief The normal export workflow is to pull the newest commits from mercurial
//! or git and update/checkout them. After that, there is a packed zip file
//! available that will be unpacked to the data directory
public class PaymentReceiptProcess : BackgroundProcess
{
private IPaymentReceiptProcessData data = null;
public PaymentReceiptProcess(IPaymentReceiptProcessData data)
{
this.data = data;
Caption = "Generate receipt PDF for each membership payment";
Steps = 5;
}
//! \short Run receipt generation process
//! \brief For each member-MoneyTransfer combination, generate a
//! temporary SVG output, then run Inkscape to generate PDF
//! \return true if the generation was successful.
protected override bool Run()
{
uint step = 0;
try
{
List<string> svgOutputFiles = new List<string>();
LogTarget.StepStarted(step, "Loading source SVG template");
SvgFile svgTemplate = new SvgFile(data.SvgTemplate);
svgTemplate.SvgFileName = data.FileNamePattern;
LogTarget.LogLine($"Loaded \"{data.SvgTemplate}\" ({svgTemplate.SvgData.Length} characters) into memory.", LogEvent.ELogLevel.Info, "PaymentReceiptProcess");
LogTarget.StepStarted(++step, "Filtering MoneyTransfers for the given timeframe");
List<MoneyTransfer> transfers = Program.MoneyTransfers.Entries.Where //All entries with
(o=>((o.TransferType == MoneyTransfer.eTransferType.MembershipPayment) //Correct transfer type,
&& (o.ValutaDate.CompareTo(data.StartDate) >= 0) //After start date,
&& (o.ValutaDate.CompareTo(data.EndDate) <= 0) //Before end date
)).ToList();
LogTarget.LogLine($"{transfers.Count} MoneyTransfers to process.", LogEvent.ELogLevel.Info, "PaymentReceiptProcess");
LogTarget.StepStarted(++step, "Processing all payments to generate temporary SVGs (main step)");
foreach(MoneyTransfer mt in transfers)
{
Member m = Program.members.Find(mt.MemberNumber);
if(m == null)
{
LogTarget.LogLine($"Cannot find member {mt.MemberNumber} referenced in MoneyTransfer {mt.Id} (from {mt.ValutaDate})", LogEvent.ELogLevel.Error, "PaymentReceiptProcess");
continue;
}
if(!m.MoneyTransfersIds.Contains(mt.Id))
{
LogTarget.LogLine($"Member {m.Nickname} ({m.Number}) doesn't reference MoneyTransfer {mt.Id} (from {mt.ValutaDate})", LogEvent.ELogLevel.Error, "PaymentReceiptProcess");
continue;
}
SvgFile output = svgTemplate.ReplaceReflect(mt).ReplaceReflect(m);
LogTarget.LogLine($"Generating file \"{output.SvgFileName}\"", LogEvent.ELogLevel.Info, "PaymentReceiptProcess");
File.WriteAllText(output.SvgFileName, output.SvgData);
svgOutputFiles.Add(output.SvgFileName);
}
LogTarget.StepStarted(++step, "Converting SVGs to PDF using Inkscape");
foreach (string svg in svgOutputFiles)
{
ImportExportBase.RunProcess("inkscape", $"{svg} --export-filename={svg}.pdf ", ".", LogTarget);
}
LogTarget.StepStarted(++step, "E-Mail the PDFs to the members");
LogTarget.LogLine($"This step is still TODO", LogEvent.ELogLevel.Info, "PaymentReceiptProcess");
LogTarget.StepCompleted(4, $"E-Mail the PDFs to the members", true);
return true;
} catch(Exception ex)
{
LogTarget.LogLine($"An Error occurred: {ex.Message}", LogEvent.ELogLevel.Error, "PaymentReceiptProcess");
LogTarget.StepCompleted(step, $"E-Mail the PDFs to the members", false);
return false;
}
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Reflection;
namespace dezentrale.core
{
public class ReplaceReflectEntity<T> where T : ReplaceReflectEntity<T> //Only child classes are allowed as T parameters
{
protected ReplaceReflectEntity()
{
}
public T ReplaceReflect(object o)
{
T ret = (T) this.MemberwiseClone();
PropertyInfo[] objProperties = o.GetType().GetProperties();
foreach (var childProperty in ret.GetType().GetProperties())
{
if (childProperty.PropertyType != typeof(string)) continue;
if (!childProperty.CanRead) continue;
if (!childProperty.CanWrite) continue;
string propVal = (string)childProperty.GetValue(ret, null);
if (propVal == null) continue;
bool changed = false;
foreach (var objProperty in objProperties)
{
if (!objProperty.CanRead) continue;
//check if objProperty occurs in mailProperty contents, then replace
string token = $"{{{objProperty.Name}}}";
string tokenValue;
try
{
tokenValue = objProperty.GetValue(o, null).ToString();
}
catch (NullReferenceException)
{
//Console.WriteLine($"FormMail.ReplaceReflect({o}): property {token}: {ex.Message}");
continue;
}
if (propVal.Contains(token))
{
//NOTE: This is problematic because it allows the user to generate recursive replacements by setting e.g. the Nickname to "{PgpFingerprint}"
propVal = propVal.Replace(token, tokenValue);
changed = true;
}
}
if (changed)
{
childProperty.SetValue(ret, propVal, null);
}
}
return ret;
}
}
}

View File

@ -194,6 +194,8 @@
<Compile Include="core\Utils.cs" />
<Compile Include="view\frmPaymentReceipts.cs" />
<Compile Include="view\FormWithOkCancel.cs" />
<Compile Include="core\PaymentReceiptProcess.cs" />
<Compile Include="core\ReplaceReflectEntity.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />

View File

@ -32,6 +32,7 @@ namespace dezentrale.model.money
[XmlElement] public string TransferReason { get; set; } = "";
[XmlIgnore] public string AmountString { get { return core.Utils.Int64FPToString(Amount);/* $"{((float) Amount / 100)}";*/ } }
[XmlIgnore] public string ValutaDateString { get { return ValutaDate.ToShortDateString(); } }
public override string ToString() { return Id; }
public MoneyTransfer()
@ -47,12 +48,16 @@ namespace dezentrale.model.money
&& this.GetType().Equals(other.GetType())
&& this.Currency.Equals(other.Currency)
&& this.TransferReason.Equals(other.TransferReason);
}
public int CompareTo(DateTime other)
{
return ValutaDate.CompareTo(other);
}
public int CompareTo(MoneyTransfer other)
{
if (other == null) return 1;
else return ValutaDate.CompareTo(other.ValutaDate);
else return CompareTo(other.ValutaDate);
}
}
}

View File

@ -244,8 +244,14 @@ namespace dezentrale.view
frmPaymentReceipts receipts = new frmPaymentReceipts();
DialogResult dr = receipts.ShowDialog();
if(dr == DialogResult.OK)
{
{
receipts.SvgTemplate = "dezentrale-beitragsquittung-template.svg";
receipts.StartDate = new DateTime(2020, 01, 01);
receipts.EndDate = new DateTime(2020, 12, 31);
receipts.FileNamePattern = "Member{MemberNumber}-{ValutaDateString}-{AmountString}{Currency}.svg";
PaymentReceiptProcess rec = new PaymentReceiptProcess(receipts);
frmProcessWithLog frmRec = new frmProcessWithLog(rec, true);
dr = frmRec.ShowDialog();
}
}
private void mnuMain_Help_About(object sender, EventArgs e)

View File

@ -9,7 +9,7 @@ using dezentrale.model.money;
namespace dezentrale.view
{
public class frmPaymentReceipts : FormWithOkCancel
public class frmPaymentReceipts : FormWithOkCancel, IPaymentReceiptProcessData
{
public string SvgTemplate { get; set; } = "";