dmdb/core/PaymentReceiptProcess.cs

212 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using dezentrale.model;
using dezentrale.model.money;
namespace dezentrale.core
{
public enum IntermediateFormat
{
Text,
SvgInkscape092,
SvgInkscapeNew,
Latex
}
public interface IPaymentReceiptProcessData
{
List<Member> MemberList { get; set; }
bool AllMembers { get; set; }
string DataTemplate { get; set; }
IntermediateFormat DataFormat { get; set; }
string OutputDirectory { get; set; }
string FileNamePattern { get; set; }
DateTime StartDate { get; set; }
DateTime EndDate { get; set; }
string StartDateString { get; }
string EndDateString { get; }
bool SendEmail { get; set; }
bool WriteProtectPdf { get; set; }
}
//In order to be able to ReplaceReflect, we need to have the SvgData and SvgFileName field separated in a class
class IntermediateFile : ReplaceReflectEntity<IntermediateFile>
{
public string FileName { get; set; } = "";
public string Data { get; set; } = "";
public IntermediateFile() { }
public IntermediateFile(string fileName)
{
FileName = fileName;
Data = 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 = 6;
}
//! \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<Member> memberList;
bool allMembers = data.AllMembers;
if (data.MemberList == null || data.MemberList.Count < 1)
{
if(!data.AllMembers)
{
LogTarget.LogLine($"No member list provided", LogEvent.ELogLevel.Error, "PaymentReceiptProcess");
return false;
}
memberList = Program.members.Entries;
} else
{
memberList = allMembers ? Program.members.Entries : data.MemberList;
LogTarget.LogLine($"Processing {memberList.Count} entries (AllMembers={allMembers})", LogEvent.ELogLevel.Error, "PaymentReceiptProcess");
}
// In preparation to send E-Mail and combine multiple files into one mail later,
// we're using a List of KVP with key = filename, value = member reference
List<KeyValuePair<string, Member>> intermediateFiles = new List<KeyValuePair<string, Member>>();
string intermediateExt = "txt";
string finalExt = ".pdf";
switch (data.DataFormat)
{
case IntermediateFormat.Text: intermediateExt = ".txt"; finalExt = ".txt"; break;
case IntermediateFormat.SvgInkscape092: intermediateExt = ".svg"; break;
case IntermediateFormat.SvgInkscapeNew: intermediateExt = ".svg"; break;
case IntermediateFormat.Latex: intermediateExt = ".tex"; break;
}
LogTarget.StepStarted(step, "Loading source SVG template");
IntermediateFile svgTemplate = new IntermediateFile(data.DataTemplate);
svgTemplate.FileName = data.FileNamePattern;
LogTarget.LogLine($"Loaded \"{data.DataTemplate}\" ({svgTemplate.Data.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 = memberList.Find(x => x.Number == mt.MemberNumber);
if(m == null)
{
//this is only an error, if all members are selected in the dialog, we can check this.
if(allMembers)
LogTarget.LogLine($"Cannot find member {mt.MemberNumber} referenced in MoneyTransfer {mt.Id} (from {mt.ValutaDate})", LogEvent.ELogLevel.Error, "PaymentReceiptProcess");
//but in every case, we need to skip this MoneyTransfer.
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;
}
IntermediateFile output = svgTemplate.ReplaceReflect(mt).ReplaceReflect(m);
LogTarget.LogLine($"Generating file \"{output.FileName}\"", LogEvent.ELogLevel.Info, "PaymentReceiptProcess");
File.WriteAllText(Path.Combine(data.OutputDirectory, output.FileName + intermediateExt), output.Data);
intermediateFiles.Add(new KeyValuePair<string,Member>(output.FileName,m));
}
LogTarget.StepStarted(++step, "Converting intermediate files to PDF");
foreach (KeyValuePair<string,Member> kvp in intermediateFiles)
{
string sourceFile = Path.Combine(data.OutputDirectory, kvp.Key + intermediateExt);
string pdf = Path.Combine(data.OutputDirectory, kvp.Key + finalExt);
switch (data.DataFormat)
{
case IntermediateFormat.Text:
LogTarget.LogLine($"Skipping PDF generation for: \"{kvp.Key}{intermediateExt}\"", LogEvent.ELogLevel.Info, "PaymentReceiptProcess");
break;
case IntermediateFormat.SvgInkscape092:
ImportExportBase.RunProcess("inkscape", $"\"{sourceFile}\" --export-pdf={pdf}", ".", LogTarget);
break;
case IntermediateFormat.SvgInkscapeNew:
ImportExportBase.RunProcess("inkscape", $"\"{sourceFile}\" --export-type=pdf --export-filename={pdf}", ".", LogTarget);
break;
case IntermediateFormat.Latex:
ImportExportBase.RunProcess("pdflatex", $"\"{sourceFile}\"", ".", LogTarget);
break;
}
if(data.WriteProtectPdf && data.DataFormat != IntermediateFormat.Text)
{
//Write-protecting pdf
ImportExportBase.RunProcess("qpdf", $"--encrypt \"\" \"random-owner-pw\" 256 --modify=none -- \"{pdf}\" \"{pdf}.writeprotect\"", ".", LogTarget);
File.Replace(pdf, pdf + ".writeprotect", pdf + ".writable");
}
}
LogTarget.StepStarted(++step, "E-Mail the PDFs to the members");
if (data.SendEmail)
{
FormMail receiptMail = FormMail.GenerateMemberPaymentReceipts().ReplaceReflect(data);
foreach (Member m in memberList)
{
//gather entries per member
List<string> outputs = new List<string>();
foreach (KeyValuePair<string, Member> kvp in intermediateFiles)
{
if (m.Number == kvp.Value.Number) outputs.Add(Path.Combine(data.OutputDirectory, kvp.Key + finalExt));
}
//send E-Mail
m.StartLogEvent($"Payment receipts E-Mail ({data.StartDateString} ... {data.EndDateString})",LogEvent.eEventType.EMail, Program.config.LocalUser);
try
{
m.CurrentLog.SubEvents.Add(receiptMail.Send(m, outputs));
} catch(Exception ex)
{
LogTarget.LogLine($"Cannot send payment receipts E-Mail: {ex.Message}", LogEvent.ELogLevel.Error);
m.CurrentLog.SubEvents.Add(new LogSubEvent() { Type = LogEvent.eEventType.Error, Topic = "Email notification error", Details = ex.Message, });
}
m.SaveToFile(null, true);
}
} else
{
LogTarget.LogLine($"This step is skipped.", 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;
}
}
}
}