MemberAdminWindow: Add pending member movement export
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using Elwig.Models;
|
||||
using Elwig.Models.Dtos;
|
||||
using Elwig.Models.Entities;
|
||||
using Elwig.Services;
|
||||
@@ -531,6 +532,44 @@ namespace Elwig.Helpers {
|
||||
return _memberHistory[year].GetValueOrDefault(mgnr, new());
|
||||
}
|
||||
|
||||
public async Task<Dictionary<int, SortedDictionary<DateOnly, MemberHistoryPoint>>> GetMemberHistory() {
|
||||
var today = DateOnly.FromDateTime(DateTime.Today).AddDays(1);
|
||||
var history = new Dictionary<int, SortedDictionary<DateOnly, MemberHistoryPoint>>();
|
||||
|
||||
var members = await Members.ToListAsync();
|
||||
foreach (var m in members) {
|
||||
history[m.MgNr] = new() {
|
||||
[today] = new(m.Shares, m.SharesRed, m.SharesWhite, m.SharesDormant),
|
||||
};
|
||||
}
|
||||
|
||||
var hist = await MemberHistory.OrderByDescending(h => h.DateNoticeString).ToListAsync();
|
||||
foreach (var h in hist) {
|
||||
if (h.FromMgNr is int from) {
|
||||
var latest = history[from].Last().Value;
|
||||
switch (h.FromType) {
|
||||
case BusinessShareType.Normal: latest.Shares += h.Shares; break;
|
||||
case BusinessShareType.Red: latest.SharesRed += h.Shares; break;
|
||||
case BusinessShareType.White: latest.SharesWhite += h.Shares; break;
|
||||
case BusinessShareType.Dormant: latest.SharesDormant += h.Shares; break;
|
||||
}
|
||||
history[from][h.DateNotice] = latest;
|
||||
}
|
||||
if (h.ToMgNr is int to) {
|
||||
var latest = history[to].Last().Value;
|
||||
switch (h.ToType) {
|
||||
case BusinessShareType.Normal: latest.Shares -= h.Shares; break;
|
||||
case BusinessShareType.Red: latest.SharesRed -= h.Shares; break;
|
||||
case BusinessShareType.White: latest.SharesWhite -= h.Shares; break;
|
||||
case BusinessShareType.Dormant: latest.SharesDormant -= h.Shares; break;
|
||||
}
|
||||
history[to][h.DateNotice] = latest;
|
||||
}
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, AreaCmtBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
|
||||
if (!_memberAreaCommitmentBuckets.ContainsKey(year))
|
||||
await FetchMemberAreaCommitmentBuckets(year, cnx);
|
||||
|
||||
@@ -46,10 +46,6 @@ namespace Elwig.Helpers.Export {
|
||||
await ExportAsync(rows);
|
||||
}
|
||||
|
||||
public void Export(int year) {
|
||||
ExportAsync(year).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override string FormatRow(Row row) {
|
||||
var (member, delivery) = row;
|
||||
var (lfBisNr, familyName, name, billingName, address, plz, ort, vollablieferer, area) = member;
|
||||
|
||||
@@ -29,11 +29,6 @@ namespace Elwig.Helpers.Export {
|
||||
_writer.Dispose();
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() {
|
||||
GC.SuppressFinalize(this);
|
||||
return _writer.DisposeAsync();
|
||||
}
|
||||
|
||||
public async Task ExportAsync(IEnumerable<T> data, IProgress<double>? progress = null) {
|
||||
progress?.Report(0.0);
|
||||
int count = data.Count() + 2, i = 0;
|
||||
@@ -50,10 +45,6 @@ namespace Elwig.Helpers.Export {
|
||||
progress?.Report(100.0);
|
||||
}
|
||||
|
||||
public void Export(IEnumerable<T> data, IProgress<double>? progress = null) {
|
||||
ExportAsync(data, progress).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public string FormatRow(IEnumerable row) {
|
||||
return string.Join(Separator, row);
|
||||
}
|
||||
|
||||
@@ -28,15 +28,6 @@ namespace Elwig.Helpers.Export {
|
||||
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");
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
public interface IExporter<T> : IDisposable, IAsyncDisposable {
|
||||
public interface IExporter<T> : IDisposable {
|
||||
/// <summary>
|
||||
/// The default file extension of the exported files to be used (whithout a preceding ".")
|
||||
/// </summary>
|
||||
@@ -14,7 +14,9 @@ namespace Elwig.Helpers.Export {
|
||||
/// </summary>
|
||||
/// <param name="data">The data to be exported</param>
|
||||
/// <param name="progress">The progress object to report to</param>
|
||||
void Export(IEnumerable<T> data, IProgress<double>? progress = null);
|
||||
void Export(IEnumerable<T> data, IProgress<double>? progress = null) {
|
||||
ExportAsync(data, progress).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronosly export the given data to the given file.
|
||||
|
||||
@@ -109,8 +109,13 @@ namespace Elwig.Helpers.Export {
|
||||
<style:paragraph-properties fo:text-align="center"/>
|
||||
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="sectionheader" style:family="table-cell" style:parent-style-name="default">
|
||||
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false"/>
|
||||
<style:paragraph-properties fo:text-align="left"/>
|
||||
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold" fo:font-size="14pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="th" style:family="table-cell" style:parent-style-name="default">
|
||||
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false" style:vertical-align="middle"/>
|
||||
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false" style:vertical-align="middle" fo:wrap-option="wrap"/>
|
||||
<style:paragraph-properties fo:text-align="center"/>
|
||||
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
@@ -211,35 +216,59 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
}
|
||||
|
||||
await Content.WriteAsync(
|
||||
$" <table:table-row>\r\n" +
|
||||
FormatCell(table.FullName, colSpan: totalSpan, style: "header") +
|
||||
$" </table:table-row>\r\n" +
|
||||
$" <table:table-row>\r\n" +
|
||||
FormatCell(table.Subtitle, colSpan: totalSpan, style: "subheader") +
|
||||
$" </table:table-row>\r\n" +
|
||||
$" <table:table-row>\r\n" +
|
||||
$" <table:table-cell table:number-columns-repeated=\"{totalSpan}\"/>\r\n" +
|
||||
$" </table:table-row>\r\n" +
|
||||
$" <table:table-row>\r\n");
|
||||
foreach (var (name, span, units) in table.ColumnNames.Zip(table.ColumnSpans, table.ColumnUnits)) {
|
||||
var hasUnits = units.Length > 0;
|
||||
await Content.WriteAsync(FormatCell(name, colSpan: span, rowSpan: hasUnits ? 1 : 2, style: "th"));
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n <table:table-row>\r\n");
|
||||
foreach (var (span, units) in table.ColumnSpans.Zip(table.ColumnUnits)) {
|
||||
if (units.Length == 0) {
|
||||
await Content.WriteAsync($" <table:covered-table-cell table:number-columns-repeated=\"{span}\"/>\r\n");
|
||||
continue;
|
||||
}
|
||||
foreach (var u in units) {
|
||||
await Content.WriteAsync(FormatCell(u == null ? null : $"[{u}]", style: "th"));
|
||||
}
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n");
|
||||
await Content.WriteAsync($"""
|
||||
<table:table-row>
|
||||
{FormatCell(table.FullName, colSpan: totalSpan, style: "header")}
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
{FormatCell(table.Subtitle, colSpan: totalSpan, style: "subheader")}
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:number-columns-repeated="{totalSpan}"/>
|
||||
</table:table-row>
|
||||
""");
|
||||
|
||||
foreach (var row in table.GetData()) {
|
||||
await FormatRow(row, table.ColumnUnits, mergeSubRowCells);
|
||||
var firstSection = true;
|
||||
foreach (var sec in table.GetData()) {
|
||||
if (!firstSection) {
|
||||
await Content.WriteAsync($"""
|
||||
<table:table-row>
|
||||
<table:table-cell table:number-columns-repeated="{totalSpan}"/>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:number-columns-repeated="{totalSpan}"/>
|
||||
</table:table-row>
|
||||
""");
|
||||
}
|
||||
if (sec.Name != null) {
|
||||
await Content.WriteAsync($"""
|
||||
<table:table-row>
|
||||
{FormatCell(sec.Name, colSpan: totalSpan, style: "sectionheader")}
|
||||
</table:table-row>
|
||||
""");
|
||||
}
|
||||
|
||||
await Content.WriteAsync(" <table:table-row>\r\n");
|
||||
foreach (var (name, span, units) in table.ColumnNames.Zip(table.ColumnSpans, table.ColumnUnits)) {
|
||||
var hasUnits = units.Length > 0;
|
||||
await Content.WriteAsync(FormatCell(name, colSpan: span, rowSpan: hasUnits ? 1 : 2, style: "th"));
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n <table:table-row>\r\n");
|
||||
foreach (var (span, units) in table.ColumnSpans.Zip(table.ColumnUnits)) {
|
||||
if (units.Length == 0) {
|
||||
await Content.WriteAsync($" <table:covered-table-cell table:number-columns-repeated=\"{span}\"/>\r\n");
|
||||
continue;
|
||||
}
|
||||
foreach (var u in units) {
|
||||
await Content.WriteAsync(FormatCell(u == null ? null : $"[{u}]", style: "th"));
|
||||
}
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n");
|
||||
|
||||
foreach (var row in sec.Rows) {
|
||||
await FormatRow(row, table.ColumnUnits, mergeSubRowCells);
|
||||
}
|
||||
firstSection = false;
|
||||
}
|
||||
|
||||
await Content.WriteAsync(" </table:table>\r\n");
|
||||
@@ -314,7 +343,8 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
c = $"<{ct} office:value-type=\"float\" calcext:value-type=\"float\" office:value=\"{v.ToString(CultureInfo.InvariantCulture)}\"{add}><text:p>{data}</text:p></{ct}>";
|
||||
} else {
|
||||
c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}><text:p>{SecurityElement.Escape(data.ToString())}</text:p></{ct}>";
|
||||
var lines = string.Join("", data.ToString()?.Split('\n').Select(l => $"<text:p>{SecurityElement.Escape(l)}</text:p>") ?? []);
|
||||
c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}>{lines}</{ct}>";
|
||||
}
|
||||
|
||||
return $" {c}\r\n" + (colSpan > 1 ? $" <table:covered-table-cell table:number-columns-repeated=\"{colSpan - 1}\"/>\r\n" : "");
|
||||
|
||||
@@ -13,7 +13,9 @@ namespace Elwig.Helpers.Export {
|
||||
|
||||
private readonly StreamWriter _writer;
|
||||
|
||||
public VCard(string filename) : this(filename, Utils.UTF8) { }
|
||||
public VCard(string filename) :
|
||||
this(filename, Utils.UTF8) {
|
||||
}
|
||||
|
||||
public VCard(string filename, Encoding encoding) {
|
||||
_writer = new StreamWriter(filename, false, encoding);
|
||||
@@ -24,11 +26,6 @@ namespace Elwig.Helpers.Export {
|
||||
_writer.Dispose();
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() {
|
||||
GC.SuppressFinalize(this);
|
||||
return _writer.DisposeAsync();
|
||||
}
|
||||
|
||||
public async Task ExportAsync(IEnumerable<Member> data, IProgress<double>? progress = null) {
|
||||
progress?.Report(0.0);
|
||||
int count = data.Count() + 1, i = 0;
|
||||
@@ -58,10 +55,6 @@ namespace Elwig.Helpers.Export {
|
||||
progress?.Report(100.0);
|
||||
}
|
||||
|
||||
public void Export(IEnumerable<Member> data, IProgress<double>? progress = null) {
|
||||
ExportAsync(data, progress).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static string? Escape(string? text) {
|
||||
return text?.Replace("\\", "\\\\").Replace(",", "\\,").Replace(";", "\\;").Replace("\n", "\\n").Replace("\r", "");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user