Files
elwig/Elwig/Helpers/Export/Ebics.cs

117 lines
5.8 KiB
C#

using Elwig.Models;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Threading.Tasks;
namespace Elwig.Helpers.Export {
public class Ebics(PaymentVar variant, string filename, int version, Ebics.AddressMode mode = Ebics.AddressMode.Full) : IBankingExporter {
public enum AddressMode { Omit = 0, Lines = 1, Full = 2 }
public static string FileExtension => "xml";
private readonly StreamWriter Writer = new(filename, false, Utils.UTF8);
private readonly DateOnly Date = variant.TransferDate ?? throw new ArgumentException("TransferDate has to be set in PaymentVar");
private readonly int Year = variant.Year;
private readonly string Name = variant.Name;
private readonly int AvNr = variant.AvNr;
private readonly int Version = version;
private readonly AddressMode ShowAddresses = mode;
public void Dispose() {
GC.SuppressFinalize(this);
Writer.Dispose();
}
public ValueTask DisposeAsync() {
GC.SuppressFinalize(this);
return Writer.DisposeAsync();
}
public void Export(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
ExportAsync(transactions, progress).GetAwaiter().GetResult();
}
public async Task ExportAsync(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
if (transactions.Any(tx => tx.Amount < 0)) {
throw new ArgumentException("Tranaction amount may not be negative");
} else if (App.Client.Iban == null) {
throw new ArgumentException("Client IBAN has to be set");
}
progress?.Report(0.0);
var nbOfTxs = transactions.Count();
int count = nbOfTxs + 2, i = 0;
var ctrlSum = transactions.Sum(tx => tx.Amount);
var msgId = $"ELWIG-{App.Client.NameToken}-{Year}-AV{AvNr:00}";
var pmtInfId = $"{msgId}-1";
await Writer.WriteLineAsync($"""
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.{Version:00}">
<CstmrCdtTrfInitn>
<GrpHdr>
<MsgId>{msgId}</MsgId>
<CreDtTm>{DateTime.UtcNow:o}</CreDtTm>
<NbOfTxs>{nbOfTxs}</NbOfTxs>
<CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum>
<InitgPty><Nm>{SecurityElement.Escape(App.Client.NameFull)}</Nm></InitgPty>
</GrpHdr>
<PmtInf>
<PmtInfId>{pmtInfId}</PmtInfId>
<PmtMtd>TRF</PmtMtd>
<NbOfTxs>{nbOfTxs}</NbOfTxs>
<CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum>
<ReqdExctnDt>{(Version >= 8 ? "<Dt>" : "")}{Date:yyyy-MM-dd}{(Version >= 8 ? "</Dt>" : "")}</ReqdExctnDt>
<Dbtr><Nm>{SecurityElement.Escape(App.Client.NameFull)}</Nm></Dbtr>
<DbtrAcct><Id><IBAN>{App.Client.Iban!.Replace(" ", "")}</IBAN></Id></DbtrAcct>
<DbtrAgt><FinInstnId>{(Version >= 4 ? "<BICFI>" : "<BIC>")}{App.Client.Bic ?? "NOTPROVIDED"}{(Version >= 4 ? "</BICFI>" : "</BIC>")}</FinInstnId></DbtrAgt>
""");
progress?.Report(100.0 * ++i / count);
foreach (var tx in transactions) {
var a = (IAddress?)tx.Member.BillingAddress ?? tx.Member;
var (a1, a2) = Utils.SplitAddress(a.Address);
var id = $"ELWIG-{App.Client.NameToken}-{Year}-TG{tx.Nr:0000}";
var info = $"{Name} - Traubengutschrift Nr. {Year}/{tx.Nr:000}";
await Writer.WriteLineAsync($"""
<CdtTrfTxInf>
<PmtId><EndToEndId>{id}</EndToEndId></PmtId>
<Amt><InstdAmt Ccy="{tx.Currency}">{Transaction.FormatAmount(tx.Amount)}</InstdAmt></Amt>
<Cdtr>
<Nm>{SecurityElement.Escape(a.Name[..Math.Min(140, a.Name.Length)])}</Nm>
""");
if (ShowAddresses != AddressMode.Omit) {
var full = ShowAddresses == AddressMode.Full;
await Writer.WriteLineAsync($"""
<PstlAdr>{(full ? "" : $"\r\n <Ctry>{a.PostalDest.Country.Alpha2}</Ctry>")}
{(full ? $"<StrtNm>{SecurityElement.Escape(a1?[..Math.Min(70, a1.Length)])}</StrtNm> <BldgNb>{SecurityElement.Escape(a2?[..Math.Min(16, a2.Length)])}</BldgNb>" :
$"<AdrLine>{SecurityElement.Escape(a.Address[..Math.Min(70, a.Address.Length)])}</AdrLine>")}
<{(full ? "PstCd" : "AdrLine")}>{a.PostalDest.AtPlz?.Plz}{(full ? "</PstCd> <TwnNm>" : " ")}{SecurityElement.Escape(a.PostalDest.AtPlz?.Ort.Name)}</{(full ? "TwnNm" : "AdrLine")}>
{(full ? $" <Ctry>{a.PostalDest.Country.Alpha2}</Ctry>\r\n " : "")}</PstlAdr>
""");
}
await Writer.WriteLineAsync($"""
</Cdtr>
<CdtrAcct><Id><IBAN>{tx.Member.Iban!}</IBAN></Id></CdtrAcct>
<RmtInf><Ustrd>{SecurityElement.Escape(info)}</Ustrd></RmtInf>
</CdtTrfTxInf>
""");
progress?.Report(100.0 * ++i / count);
}
await Writer.WriteLineAsync("""
</PmtInf>
</CstmrCdtTrfInitn>
</Document>
""");
await Writer.FlushAsync();
progress?.Report(100.0);
}
}
}