Export: Add Ebics and improve Bki export
This commit is contained in:
@ -1,14 +1,35 @@
|
|||||||
using Elwig.Models;
|
using Elwig.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Elwig.Helpers.Billing {
|
namespace Elwig.Helpers.Billing {
|
||||||
public class Transaction {
|
public class Transaction {
|
||||||
|
|
||||||
public readonly Member Member;
|
public readonly Member Member;
|
||||||
public readonly int AmountCent;
|
public readonly long AmountCent;
|
||||||
|
public readonly string Currency;
|
||||||
|
public readonly int Nr;
|
||||||
|
|
||||||
public Transaction(Member m, decimal amount) {
|
public Transaction(Member m, decimal amount, string currency, int nr) {
|
||||||
Member = m;
|
Member = m;
|
||||||
AmountCent = (int)(amount * 100);
|
AmountCent = (long)Math.Round(amount * 100);
|
||||||
|
Currency = currency;
|
||||||
|
Nr = nr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Transaction> FromPaymentVariant(PaymentVar variant) {
|
||||||
|
var last = variant.Season.PaymentVariants.Where(v => v.TransferDate != null).OrderBy(v => v.TransferDate).LastOrDefault();
|
||||||
|
var dict = last?.MemberPayments.ToDictionary(m => m.MgNr, m => m.Amount) ?? new();
|
||||||
|
return variant.MemberPayments
|
||||||
|
.OrderBy(m => m.MgNr)
|
||||||
|
.Select(m => {
|
||||||
|
var amt = Math.Round(dict.GetValueOrDefault(m.MgNr, 0), 2);
|
||||||
|
return new Transaction(m.Member, m.Amount - amt, m.Variant.Season.CurrencyCode, m.TgNr ?? 0);
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FormatAmountCent(long cents) => $"{cents / 100}.{cents % 100:00}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
using Elwig.Models;
|
using System.Collections.Generic;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Elwig.Helpers.Export {
|
namespace Elwig.Helpers.Export {
|
||||||
public class Bki : Csv<DeliveryPart> {
|
|
||||||
|
using Row = Tuple<(string, string?, string?, string?, string, int, string, int), (string, int, string, string, string, int, string, double, double)>;
|
||||||
|
|
||||||
|
public class Bki : Csv<Row> {
|
||||||
|
|
||||||
private readonly string _clientData;
|
private readonly string _clientData;
|
||||||
|
|
||||||
@ -22,29 +25,42 @@ namespace Elwig.Helpers.Export {
|
|||||||
_clientData = $"{c.LfbisNr};{c.NameFull};;{a1};{a2};{c.Plz};{c.Ort}";
|
_clientData = $"{c.LfbisNr};{c.NameFull};;{a1};{a2};{c.Plz};{c.Ort}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExportAsync(AppDbContext ctx, int year) {
|
public async Task ExportAsync(int year) {
|
||||||
await ExportAsync(await ctx.DeliveryParts.Where(p => p.Year == year).ToListAsync());
|
using var cnx = await AppDbContext.ConnectAsync();
|
||||||
|
using var cmd = cnx.CreateCommand();
|
||||||
|
cmd.CommandText = $"""
|
||||||
|
SELECT lfbis_nr, family_name, name, billing_name, address, plz, ort, area,
|
||||||
|
date, weight, type, sortid, qualid, year, hkid, kmw, oe
|
||||||
|
FROM v_bki_delivery
|
||||||
|
WHERE year = {year}
|
||||||
|
""";
|
||||||
|
var r = await cmd.ExecuteReaderAsync();
|
||||||
|
List<Row> rows = new();
|
||||||
|
while (await r.ReadAsync()) {
|
||||||
|
rows.Add(new(
|
||||||
|
(r.GetString(0), r.IsDBNull(1) ? null : r.GetString(1), r.IsDBNull(2) ? null : r.GetString(2), r.IsDBNull(3) ? null : r.GetString(3), r.GetString(4), r.GetInt32(5), r.GetString(6), r.GetInt32(7)),
|
||||||
|
(r.GetString(8), r.GetInt32(9), r.GetString(10), r.GetString(11), r.GetString(12), r.GetInt32(13), r.GetString(14), r.GetDouble(15), r.GetDouble(16))
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Export(AppDbContext ctx, int year) {
|
await ExportAsync(rows);
|
||||||
ExportAsync(ctx, year).GetAwaiter().GetResult();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string FormatRow(DeliveryPart p) {
|
public void Export(int year) {
|
||||||
var d = p.Delivery;
|
ExportAsync(year).GetAwaiter().GetResult();
|
||||||
var m = d.Member;
|
|
||||||
string memberData;
|
|
||||||
if (m.BillingAddress is BillingAddr a) {
|
|
||||||
var (n1, n2) = Utils.SplitName(a.Name, m.FamilyName);
|
|
||||||
var (a1, a2) = Utils.SplitAddress(a.Address);
|
|
||||||
memberData = $"{m.LfbisNr};{n1};{n2};{a1};{a2};{a.PostalDest.AtPlz?.Plz};{a.PostalDest.AtPlz?.Ort.Name}";
|
|
||||||
} else {
|
|
||||||
var (a1, a2) = Utils.SplitAddress(m.Address);
|
|
||||||
memberData = $"{m.LfbisNr};{m.FamilyName};{m.AdministrativeName2};{a1};{a2};{m.PostalDest.AtPlz?.Plz};{m.PostalDest.AtPlz?.Ort.Name}";
|
|
||||||
}
|
}
|
||||||
var s = p.Variant;
|
|
||||||
var deliveryData = $"{d.Date:dd.MM.yyyy};{p.Weight};TB;{(s.IsWhite ? "J" : "")};{(s.IsRed ? "J" : "")};{s.SortId};;;{p.QualId};{d.Year};{p.HkId};{p.Kmw:0.0};{p.Oe:0}";
|
public override string FormatRow(Row row) {
|
||||||
var vollData = $"N;;;{m.AreaCommitments.Where(a => a.YearFrom <= d.Year && (a.YearTo == null || a.YearTo >= d.Year)).Sum(a => a.Area) / 10_000.0}";
|
var (member, delivery) = row;
|
||||||
|
var (lfBisNr, familyName, name, billingName, address, plz, ort, area) = member;
|
||||||
|
var (date, weight, type, sortid, qualid, year, hkid, kmw, oe) = delivery;
|
||||||
|
|
||||||
|
var (n1, n2) = billingName == null ? (familyName, name) : Utils.SplitName(billingName, familyName);
|
||||||
|
var (a1, a2) = Utils.SplitAddress(address);
|
||||||
|
var memberData = $"{lfBisNr};{n1};{n2};{a1};{a2};{plz};{ort}";
|
||||||
|
var deliveryData = $"{string.Join(".", date.Split("-").Reverse())};{weight};TB;{(type == "W" ? "J" : "")};{(type == "R" ? "J" : "")};{sortid};;;{qualid};{year};{hkid};{kmw:0.0};{oe:0}";
|
||||||
|
var vollData = $"N;;;{area / 10_000.0}";
|
||||||
|
|
||||||
return $"{_clientData};{memberData};{deliveryData};{vollData}";
|
return $"{_clientData};{memberData};{deliveryData};{vollData}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,43 +2,54 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Elwig.Helpers.Export {
|
namespace Elwig.Helpers.Export {
|
||||||
public abstract class Csv<T> : IExporter<T> {
|
public abstract class Csv<T> : IExporter<T> {
|
||||||
|
|
||||||
public static string FileExtension => "csv";
|
public static string FileExtension => "csv";
|
||||||
|
|
||||||
protected readonly StreamWriter Writer;
|
private readonly StreamWriter _writer;
|
||||||
protected readonly char Separator;
|
protected readonly char Separator;
|
||||||
protected string? Header;
|
protected string? Header;
|
||||||
|
|
||||||
public Csv(string filename, char separator = ';') : this(filename, separator, Encoding.UTF8) { }
|
public Csv(string filename, char separator = ';') : this(filename, separator, Utils.UTF8) { }
|
||||||
|
|
||||||
public Csv(string filename, char separator, Encoding encoding) {
|
public Csv(string filename, char separator, Encoding encoding) {
|
||||||
Writer = new StreamWriter(filename, false, encoding);
|
_writer = new StreamWriter(filename, false, encoding);
|
||||||
Separator = separator;
|
Separator = separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
Writer.Dispose();
|
_writer.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask DisposeAsync() {
|
public ValueTask DisposeAsync() {
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
return Writer.DisposeAsync();
|
return _writer.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExportAsync(IEnumerable<T> data) {
|
public async Task ExportAsync(IEnumerable<T> data, IProgress<double>? progress = null) {
|
||||||
if (Header != null) await Writer.WriteLineAsync(Header);
|
progress?.Report(0.0);
|
||||||
|
int count = data.Count() + 2, i = 0;
|
||||||
|
|
||||||
|
if (Header != null) await _writer.WriteLineAsync(Header);
|
||||||
|
progress?.Report(100.0 * ++i / count);
|
||||||
|
|
||||||
foreach (var row in data) {
|
foreach (var row in data) {
|
||||||
await Writer.WriteLineAsync(FormatRow(row));
|
await _writer.WriteLineAsync(FormatRow(row));
|
||||||
}
|
progress?.Report(100.0 * ++i / count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Export(IEnumerable<T> data) {
|
await _writer.FlushAsync();
|
||||||
ExportAsync(data).GetAwaiter().GetResult();
|
progress?.Report(100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Export(IEnumerable<T> data, IProgress<double>? progress = null) {
|
||||||
|
ExportAsync(data, progress).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FormatRow(IEnumerable row) {
|
public string FormatRow(IEnumerable row) {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
using Elwig.Helpers.Billing;
|
using Elwig.Helpers.Billing;
|
||||||
|
using Elwig.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Elwig.Helpers.Export {
|
namespace Elwig.Helpers.Export {
|
||||||
@ -8,20 +11,99 @@ namespace Elwig.Helpers.Export {
|
|||||||
|
|
||||||
public static string FileExtension => "xml";
|
public static string FileExtension => "xml";
|
||||||
|
|
||||||
|
private readonly StreamWriter _writer;
|
||||||
|
private readonly DateOnly _date;
|
||||||
|
private readonly int _year;
|
||||||
|
private readonly string _name;
|
||||||
|
private readonly int _nr;
|
||||||
|
|
||||||
|
public Ebics(PaymentVar variant, string filename) {
|
||||||
|
_writer = new(filename, false, Utils.UTF8);
|
||||||
|
_date = variant.TransferDate ?? DateOnly.Parse("2021-01-10"); //throw new ArgumentException("TransferDate has to be set in PaymentVar");
|
||||||
|
_year = variant.Year;
|
||||||
|
_name = variant.Name;
|
||||||
|
_nr = variant.AvNr;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
throw new NotImplementedException();
|
GC.SuppressFinalize(this);
|
||||||
|
_writer.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask DisposeAsync() {
|
public ValueTask DisposeAsync() {
|
||||||
throw new NotImplementedException();
|
GC.SuppressFinalize(this);
|
||||||
|
return _writer.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Export(IEnumerable<Transaction> data) {
|
public void Export(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
|
||||||
throw new NotImplementedException();
|
ExportAsync(transactions, progress).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ExportAsync(IEnumerable<Transaction> data) {
|
public async Task ExportAsync(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
|
||||||
throw new NotImplementedException();
|
progress?.Report(0.0);
|
||||||
|
var nbOfTxs = transactions.Count();
|
||||||
|
int count = nbOfTxs + 2, i = 0;
|
||||||
|
var ctrlSum = Transaction.FormatAmountCent(transactions.Sum(tx => tx.AmountCent));
|
||||||
|
var msgId = $"ELWIG-{App.Client.NameToken}-{_year}-AV{_nr: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.09"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.xsd">
|
||||||
|
<CstmrCdtTrfInitn>
|
||||||
|
<GrpHdr>
|
||||||
|
<MsgId>{msgId}</MsgId>
|
||||||
|
<CreDtTm>{DateTime.UtcNow:o}</CreDtTm>
|
||||||
|
<NbOfTxs>{nbOfTxs}</NbOfTxs>
|
||||||
|
<CtrlSum>{ctrlSum}</CtrlSum>
|
||||||
|
<InitgPty><Nm>{App.Client.NameFull}</Nm></InitgPty>
|
||||||
|
</GrpHdr>
|
||||||
|
<PmtInf>
|
||||||
|
<PmtInfId>{pmtInfId}</PmtInfId>
|
||||||
|
<PmtMtd>TRF</PmtMtd>
|
||||||
|
<NbOfTxs>{nbOfTxs}</NbOfTxs>
|
||||||
|
<CtrlSum>{ctrlSum}</CtrlSum>
|
||||||
|
<ReqdExctnDt><Dt>{_date:yyyy-MM-dd}</Dt></ReqdExctnDt>
|
||||||
|
<Dbtr><Nm>{App.Client.NameFull}</Nm></Dbtr>
|
||||||
|
<DbtrAcct><Id><IBAN>{App.Client.Iban?.Replace(" ", "")}</IBAN></Id></DbtrAcct>
|
||||||
|
<DbtrAgt><FinInstnId><BICFI>{App.Client.Bic ?? "NOTPROVIDED"}</BICFI></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 {_year}/{tx.Nr:000}";
|
||||||
|
await _writer.WriteLineAsync($"""
|
||||||
|
<CdtTrfTxInf>
|
||||||
|
<PmtId><EndToEndId>{id}</EndToEndId></PmtId>
|
||||||
|
<Amt><InstdAmt Ccy="{tx.Currency}">{Transaction.FormatAmountCent(tx.AmountCent)}</InstdAmt></Amt>
|
||||||
|
<Cdtr>
|
||||||
|
<Nm>{a.Name}</Nm>
|
||||||
|
<PstlAdr>
|
||||||
|
<StrtNm>{a1}</StrtNm><BldgNb>{a2}</BldgNb>
|
||||||
|
<PstCd>{a.PostalDest.AtPlz?.Plz}</PstCd><TwnNm>{a.PostalDest.AtPlz?.Ort.Name}</TwnNm>
|
||||||
|
<Ctry>{a.PostalDest.Country.Alpha2}</Ctry>
|
||||||
|
</PstlAdr>
|
||||||
|
</Cdtr>
|
||||||
|
<CdtrAcct><Id><IBAN>{tx.Member.Iban}</IBAN></Id><CdtrAcct>
|
||||||
|
<CdtrAgt><FinInstnId><BICFI>{tx.Member.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></CdtrAgt>
|
||||||
|
<RmtInf><Ustrd>{info}</Ustrd></RmtInf>
|
||||||
|
</CdtTrfTxInf>
|
||||||
|
""");
|
||||||
|
progress?.Report(100.0 * ++i / count);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _writer.WriteLineAsync("""
|
||||||
|
</PmtInf>
|
||||||
|
</CstmrCdtTrfInitn>
|
||||||
|
</Document>
|
||||||
|
""");
|
||||||
|
await _writer.FlushAsync();
|
||||||
|
progress?.Report(100.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,18 +7,20 @@ namespace Elwig.Helpers.Export {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default file extension of the exported files to be used (whithout a preceding ".")
|
/// The default file extension of the exported files to be used (whithout a preceding ".")
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static string FileExtension { get; }
|
static abstract string FileExtension { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Export the given data to the given file.
|
/// Export the given data to the given file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">The data to be exported</param>
|
/// <param name="data">The data to be exported</param>
|
||||||
void Export(IEnumerable<T> data);
|
/// <param name="progress">The progress object to report to</param>
|
||||||
|
void Export(IEnumerable<T> data, IProgress<double>? progress = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Export the given data to the given file.
|
/// Asynchronosly export the given data to the given file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">The data to be exported</param>
|
/// <param name="data">The data to be exported</param>
|
||||||
Task ExportAsync(IEnumerable<T> data);
|
/// <param name="progress">The progress object to report to</param>
|
||||||
|
Task ExportAsync(IEnumerable<T> data, IProgress<double>? progress = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user