2021-01-24PX Multiple changes:

- Added CsvImportProcess (porting ProcessCsv as a background process), not finished yet
- added comment window on member editing
- Changed GUI to allow editing member account balance
- Added Enable/Disable feature for Import/Export, in order to get rid of stupid messages at startup/shutdown
- Added configuration for Schatzmeister/Schriftfuehrer contact to configuration
- Fixed CurrentDebtLevel > 0 was able to prevent BalanceDegrade / postdegrade code from working
- Fixed Member list wasn't refreshed after CSV import
- Changed MembershipDonation enum type to Donation (as non-members can donate too)
This commit is contained in:
phantomix 2022-01-24 22:04:24 +01:00
parent 105afdb203
commit 4d16e11b6e
21 changed files with 512 additions and 186 deletions

View File

@ -34,7 +34,11 @@ namespace dezentrale
public static MemberList members = new MemberList(); public static MemberList members = new MemberList();
public static MvList mvList = new MvList(); public static MvList mvList = new MvList();
public static bool MoneyTransfersLoaded { get { return moneyTransfers != null; } } public static bool MoneyTransfersLoaded
{
get { return moneyTransfers != null; }
set { moneyTransfers = value ? MoneyTransferList.LoadFromFile() : null; }
}
private static MoneyTransferList moneyTransfers = null; private static MoneyTransferList moneyTransfers = null;
public static MoneyTransferList MoneyTransfers public static MoneyTransferList MoneyTransfers
{ {
@ -220,8 +224,13 @@ namespace dezentrale
case eMode.Cronjob: case eMode.Cronjob:
Cronjob.Run(); Cronjob.Run();
break; break;
case eMode.Export: case eMode.Export:
{ {
if (!Program.config.ImportExport.Enabled)
{
Console.WriteLine("ImportExport is disabled in configuration");
return 1;
}
ExportProcess export = new ExportProcess() ExportProcess export = new ExportProcess()
{ {
ImportExportSettings = Program.config.ImportExport, ImportExportSettings = Program.config.ImportExport,
@ -239,7 +248,12 @@ namespace dezentrale
} break; } break;
case eMode.Import: case eMode.Import:
{ {
if(!Program.config.ImportExport.Enabled)
{
Console.WriteLine("ImportExport is disabled in configuration");
return 1;
}
ImportProcess import = new ImportProcess() ImportProcess import = new ImportProcess()
{ {
ImportExportSettings = Program.config.ImportExport, ImportExportSettings = Program.config.ImportExport,

View File

@ -16,52 +16,40 @@ namespace dezentrale.core
//! \brief Perform a Run on one Member. This will perform all necessary actions //! \brief Perform a Run on one Member. This will perform all necessary actions
//! at this point. //! at this point.
//! \param m Member to check //! \param m Member to check
//! \param sm Member entry for the Schatzmeister account, in order to send notification mails for some events //! \param smContact Mail contact entry for the Schatzmeister account, in order to send notification mails for some events
public static void Run(Member m, Member sm) public static void Run(Member m, ConfigEMail smContact)
{ {
Console.WriteLine($"Processing member {m.Number:D3} ({m.Nickname})..."); Console.WriteLine($"Processing member {m.Number:D3} ({m.Nickname})...");
CheckGreeting(m); CheckGreeting(m);
CheckSpawning(m); CheckSpawning(m);
// CheckReducedFeeValidity(m); // CheckReducedFeeValidity(m);
CheckBalance_PreDegrade(m, sm); CheckBalance_PreDegrade(m, smContact);
BalanceDegrade(m); BalanceDegrade(m);
CheckBalance_PostDegrade(m, sm); CheckBalance_PostDegrade(m, smContact);
} }
//! \short Helper function for Run(m, sm). Determines sm automatically. //! \short Helper function for Run(m, sm). Determines sm automatically.
//! \param m Member to check //! \param m Member to check
public static void Run(Member m) public static void Run(Member m)
{ {
Member sm = Program.members.Find(Member.eRole.Schatzmeister); Run(m, Program.config.Schatzmeister);
if (sm == null)
{
Console.WriteLine($"Cronjob.Run(Member {m.Number}, {m.Nickname}): ERROR, cannot find sm account!");
}
else
Run(m, sm);
} }
//! \short Perform a cronjob run on a List of members. //! \short Perform a cronjob run on a List of members.
//! \brief Perform a cronjob run on a List of members. This may be a //! \brief Perform a cronjob run on a List of members. This may be a
//! partial List (e.g. by checking member entries from GUI) //! partial List (e.g. by checking member entries from GUI)sm
//! \param partialList List of members to process. If null, the program will process the complete member list. //! \param partialList List of members to process. If null, the program will process the complete member list.
public static void Run(List<Member> partialList = null) public static void Run(List<Member> partialList = null)
{ {
Member sm = Program.members.Find(Member.eRole.Schatzmeister);
if (sm == null)
{
Console.WriteLine($"Cronjob.Run(List of {partialList.Count} members): ERROR, cannot find schatzmeister account!");
}
if (partialList == null || partialList == Program.members.Entries) if (partialList == null || partialList == Program.members.Entries)
{ {
foreach (Member m in Program.members.Entries) Run(m, sm); foreach (Member m in Program.members.Entries) Run(m, Program.config.Schatzmeister);
//GenerateReport(); //GenerateReport();
if (!Program.config.LastCronjobRun.Equals(ProgramStartTime)) if (!Program.config.LastCronjobRun.Equals(ProgramStartTime))
{ {
Member schriftfuehrer = Program.members.Find(Member.eRole.Schriftfuehrer); try { new MemberReport(true).Send(Program.config.Schatzmeister); }
try { if (sm != null) new MemberReport(true).Send(sm); }
catch (Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to Schatzmeister: {ex.Message}"); } catch (Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to Schatzmeister: {ex.Message}"); }
try { if (schriftfuehrer != null) new MemberReport(false).Send(schriftfuehrer); } try { new MemberReport(false).Send(Program.config.Schriftfuehrer); }
catch (Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to schriftfuehrer: {ex.Message}"); } catch (Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to schriftfuehrer: {ex.Message}"); }
Console.WriteLine("Cronjob.Run(): Member Reports sent."); Console.WriteLine("Cronjob.Run(): Member Reports sent.");
} }
@ -79,7 +67,7 @@ namespace dezentrale.core
} }
else else
{ {
foreach (Member m in partialList) Run(m, sm); foreach (Member m in partialList) Run(m, Program.config.Schatzmeister);
} }
} }
@ -262,11 +250,11 @@ namespace dezentrale.core
private static DateTime ProgramStartTime { get; set; } = DateTime.Now; private static DateTime ProgramStartTime { get; set; } = DateTime.Now;
//! \short Necessary actions prior to BalanceDegrade. //! \short Necessary actions prior to BalanceDegrade.
private static void CheckBalance_PreDegrade(Member m, Member sm = null) private static void CheckBalance_PreDegrade(Member m, ConfigEMail smContact = null)
{ {
} }
private static void CheckBalance_PostDegrade(Member m, Member sm = null) private static void CheckBalance_PostDegrade(Member m, ConfigEMail smContact = null)
{ {
if (m.Status != Member.eStatus.Active || m.PaymentAmount == 0) return; if (m.Status != Member.eStatus.Active || m.PaymentAmount == 0) return;
@ -286,7 +274,7 @@ namespace dezentrale.core
if(!skipInsufficientNotify) if(!skipInsufficientNotify)
{ {
if (debtLevelDecrease) m.DebtLevel--; if (debtLevelDecrease) m.DebtLevel--;
else m.DebtLevel = currentDebtLevel; else m.DebtLevel = currentDebtLevel;
//We don't set skipInsufficientNotify here as we don't want to not-warn //We don't set skipInsufficientNotify here as we don't want to not-warn
//if it's still negative //if it's still negative
@ -300,10 +288,10 @@ namespace dezentrale.core
{ {
//Member has given much money //Member has given much money
m.StartLogEvent($"Excess amount of payments", LogEvent.eEventType.EMail, "automatic"); m.StartLogEvent($"Excess amount of payments", LogEvent.eEventType.EMail, "automatic");
if (sm != null) if (smContact != null)
{ {
FormMail above200 = FormMail.GenerateBalanceAbove200NotifySM().ReplaceReflect(m); FormMail above200 = FormMail.GenerateBalanceAbove200NotifySM().ReplaceReflect(m);
above200.To = $"{sm.EMailName} <{sm.EMail}>"; above200.To = smContact.ToString();
try try
{ {
m.CurrentLog.SubEvents.Add(above200.Send()); m.CurrentLog.SubEvents.Add(above200.Send());
@ -325,7 +313,11 @@ namespace dezentrale.core
case 0: case 0:
{ {
//all is fine. //all is fine.
if (debtLevelDecrease || debtLevelIncrease)
{
m.StartLogEvent($"Debt level changed", LogEvent.eEventType.DataChange, "automatic");
}
} break; } break;
case -1: case -1:
@ -369,25 +361,22 @@ namespace dezentrale.core
}); });
Console.WriteLine($"Cannot send Insufficient amount #2 notification: {ex.Message}"); Console.WriteLine($"Cannot send Insufficient amount #2 notification: {ex.Message}");
} }
if (sm != null)
FormMail below2sm = FormMail.GenerateBalanceNegativeNotify2SM().ReplaceReflect(m);
below2sm.To = smContact.ToString();
try
{ {
FormMail below2sm = FormMail.GenerateBalanceNegativeNotify2SM().ReplaceReflect(m); m.CurrentLog.SubEvents.Add(below2sm.Send());
below2sm.To = $"{sm.EMailName} <{sm.EMail}>"; } catch(Exception ex)
try {
m.CurrentLog.SubEvents.Add(new LogSubEvent()
{ {
m.CurrentLog.SubEvents.Add(below2sm.Send()); Type = LogEvent.eEventType.Error,
} catch(Exception ex) Topic = "Email notification error",
{ Details = ex.Message,
m.CurrentLog.SubEvents.Add(new LogSubEvent() });
{ Console.WriteLine($"Cannot send Insufficient amount #2 SM notification: {ex.Message}");
Type = LogEvent.eEventType.Error, }
Topic = "Email notification error",
Details = ex.Message,
});
Console.WriteLine($"Cannot send Insufficient amount #2 SM notification: {ex.Message}");
}
} else
Console.WriteLine("ERROR: CheckBalance_PostDegrade: sm = null");
} else } else
{ {
m.StartLogEvent($"Deactivation (insufficient payment)", LogEvent.eEventType.Deactivation, "automatic"); m.StartLogEvent($"Deactivation (insufficient payment)", LogEvent.eEventType.Deactivation, "automatic");
@ -420,6 +409,7 @@ namespace dezentrale.core
break; break;
} }
//If there are data changes to the member, there's a log and we need to store everything
if (m.CurrentLog != null) if (m.CurrentLog != null)
{ {
m.LastCronjobBalanceMail = DateTime.Now; m.LastCronjobBalanceMail = DateTime.Now;

124
core/CsvImportProcess.cs Normal file
View File

@ -0,0 +1,124 @@
using System;
using System.IO;
using dezentrale.model;
using System.Collections.Generic;
using dezentrale.model.money;
namespace dezentrale.core
{
public class CsvImportProcess : BackgroundProcess
{
private string fileName = "";
public CsvImportProcess(string fileName)
{
this.fileName = fileName;
Caption = "Import CSV Money Transfers";
Steps = 3;
}
protected override bool Run()
{
uint step = 0;
LogTarget.LogLine($"Importing from {fileName}", LogEvent.ELogLevel.Info, "CsvImportProcess");
List<BankTransfer> tmpList = new List<BankTransfer>();
List<string> headlineFields = null;
List<Member> changedMembers = new List<Member>();
CsvFile csv = new CsvFile() { FieldSeparator = ';' };
try
{
LogTarget.StepStarted(++step, "Reading file...");
try
{
csv.ReadFile(fileName);
LogTarget.StepCompleted(step, "Done reading file.", true);
} catch(Exception ex)
{
LogTarget.LogLine($"Error: {ex.Message}", LogEvent.ELogLevel.Error, "CsvImportProcess");
return false;
}
List<List<string>> contents = csv.FileContents;
LogTarget.StepStarted(++step, $"Parsing file ({contents.Count} csv lines, including headline)...");
for(int csvLine = 0; csvLine < contents.Count; csvLine++)
{
List<string> l = contents[csvLine];
if (headlineFields == null)
{
//The first line is expected to have the headline field first, describing the contents
headlineFields = l;
string fields = "";
foreach(string s in l)
{
fields += $"\"{s}\",";
}
LogTarget.LogLine($"Headline fields: {fields}", LogEvent.ELogLevel.Info, "CsvImportProcess");
continue;
}
BankTransfer bt = new BankTransfer(headlineFields, l, fileName, csvLine);
MoneyTransfer duplicate = Program.MoneyTransfers.FindEqualEntry(bt);
if (duplicate != null)
{
LogTarget.LogLine($"Line {csvLine}: is duplicate of MoneyTransfer {duplicate.Id} (ValutaDate {duplicate.ValutaDateString})", LogEvent.ELogLevel.Info, "CsvImportProcess");
LogTarget.LogLine($" ValutaDate: {bt.ValutaDate}, Amount = {bt.AmountString} {bt.Currency}, Reason = \"{bt.TransferReason.Replace('\r', '\\').Replace('\n', '\\')}\"", LogEvent.ELogLevel.Info, "CsvImportProcess");
}
else
{
Program.MoneyTransfers.AddEntry(bt);
tmpList.Add(bt);
}
}
LogTarget.StepCompleted(step, $"Done parsing entries.", true);
LogTarget.StepStarted(++step, $"Assigning found {tmpList.Count} entries to members");
//try to assign transfers to the members
foreach (BankTransfer bt in tmpList)
{
if (bt.Amount < 0)
{
bt.TransferType = MoneyTransfer.eTransferType.RunningCost;
LogTarget.LogLine($"{bt.Id}: (from CSV line {bt.CsvLine}): Amount = {bt.AmountString} --> RunningCost", LogEvent.ELogLevel.Info, "CsvImportProcess");
continue;
}
foreach (Member m in Program.members.Entries)
{
if (m.CheckBankTransfer(bt))
{
LogTarget.LogLine($"{bt.Id}: (from CSV line {bt.CsvLine}): adding to member {m}", LogEvent.ELogLevel.Info, "CsvImportProcess");
bt.TransferType = MoneyTransfer.eTransferType.MembershipPayment;
changedMembers.Add(m);
m.StartLogEvent("Incoming bank transfer", LogEvent.eEventType.MembershipPayment, "automatic");
m.ApplyMoneyTransfer(bt); //this also stores the member entry
bt.AssignFixed = true;
break; //this is important. We don't want to assign this to multiple members.
}
}
if(!bt.AssignFixed)
{
//bt matches no member!
}
}
LogTarget.StepCompleted(step, $"Done assigning entries.", true);
LogTarget.StepStarted(++step, $"Storing money transfers");
//Store bank transfer list
Program.MoneyTransfers.Entries.Sort();
if (!Program.MoneyTransfers.SaveToFile())
return false;
LogTarget.StepCompleted(step, "", true);
return true;
}
catch (Exception ex)
{
LogTarget.LogLine($"Error while processing csv file \"{fileName}\": {ex.Message}", LogEvent.ELogLevel.Error, "CsvImportProcess");
return false;
}
}
}
}

View File

@ -192,7 +192,7 @@ namespace dezentrale.core
LogTarget.LogLine($"Cannot send payment receipts E-Mail: {ex.Message}", LogEvent.ELogLevel.Error); 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.CurrentLog.SubEvents.Add(new LogSubEvent() { Type = LogEvent.eEventType.Error, Topic = "Email notification error", Details = ex.Message, });
} }
m.SaveToFile(true); m.SaveToFile(null, true);
} }
} else } else
{ {

View File

@ -154,7 +154,7 @@
</Compile> </Compile>
<Compile Include="model\ConfigSmtp.cs" /> <Compile Include="model\ConfigSmtp.cs" />
<Compile Include="model\Configuration.cs" /> <Compile Include="model\Configuration.cs" />
<Compile Include="model\ConfigVSMail.cs" /> <Compile Include="model\ConfigEMail.cs" />
<Compile Include="model\FormMail.cs" /> <Compile Include="model\FormMail.cs" />
<Compile Include="model\Member.cs" /> <Compile Include="model\Member.cs" />
<Compile Include="model\LogEvent.cs" /> <Compile Include="model\LogEvent.cs" />
@ -214,6 +214,8 @@
<Compile Include="view\LvSelectFields.cs" /> <Compile Include="view\LvSelectFields.cs" />
<Compile Include="core\NewExportProcess.cs" /> <Compile Include="core\NewExportProcess.cs" />
<Compile Include="core\NewImportProcess.cs" /> <Compile Include="core\NewImportProcess.cs" />
<Compile Include="view\frmCommentChanges.cs" />
<Compile Include="core\CsvImportProcess.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <None Include="App.config" />

19
model/ConfigEMail.cs Normal file
View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
namespace dezentrale.model
{
public class ConfigEMail
{
[XmlAttribute] public string EMailName { get; set; } = "EMailName";
[XmlAttribute] public string EMail { get; set; } = "user@example.com";
public override string ToString()
{
return $"{EMailName} <{EMail}>";
}
}
}

View File

@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
namespace dezentrale.model
{
public class ConfigVSMail
{
[XmlElement] public string VSName { get; set; } = "dezentrale Vorstand";
[XmlElement] public string VSEmail { get; set; } = "vorstand@dezentrale.space";
}
}

View File

@ -46,8 +46,9 @@ namespace dezentrale.model
//db-wide configuration (in DB folder) //db-wide configuration (in DB folder)
[XmlElement("VS")] public ConfigVSMail VS { get; set; } = new ConfigVSMail(); [XmlElement] public ConfigEMail VS { get; set; } = new ConfigEMail() { EMailName = "dezentrale Vorstand", EMail = "vorstand@dezentrale.space" };
[XmlElement] public ConfigVSMail Schatzmeister { get; set; } = new ConfigVSMail(); [XmlElement] public ConfigEMail Schatzmeister { get; set; } = new ConfigEMail() { EMailName = "Schatzmeister", EMail = "kasse@dezentrale.space" };
[XmlElement] public ConfigEMail Schriftfuehrer { get; set; } = new ConfigEMail() { EMailName = "Schriftfuehrer", EMail = "vorstand@dezentrale.space" };
[XmlElement] public uint RegularPaymentAmount { get; set; } = 3200; //cents [XmlElement] public uint RegularPaymentAmount { get; set; } = 3200; //cents
[XmlElement] public string RegularPaymentCurrency { get; set; } = "EUR"; [XmlElement] public string RegularPaymentCurrency { get; set; } = "EUR";

View File

@ -52,7 +52,7 @@ namespace dezentrale.model
{ {
string[] addr = Program.config.Smtp.CcTo.Split(',', ';'); string[] addr = Program.config.Smtp.CcTo.Split(',', ';');
foreach(string s in addr) foreach(string s in addr)
message.CC.Add(new MailAddress(s)); if(!string.IsNullOrEmpty(s)) message.CC.Add(new MailAddress(s));
} }
message.SubjectEncoding = Encoding.UTF8; message.SubjectEncoding = Encoding.UTF8;
@ -407,6 +407,24 @@ namespace dezentrale.model
+ "Dies ist eine automatisch generierte E-Mail.\n", + "Dies ist eine automatisch generierte E-Mail.\n",
}; };
} }
public static FormMail GenerateDisabledMemberGettingPayments2SM()
{
return new FormMail()
{
To = "schatzmeister <sm@example.com>", //to be replaced!
Subject = "Inaktives Mitglied hat Mitgliedsbeitrag bezahlt",
Body = "Hallo Schatzmeister,\n"
+ "\n"
+ "Für folgenden deaktivierten Mitgliedsaccount wurde Mitgliedsbeitrag bezahlt:\n"
+ "Mitgliednummer: {NumberString}\n"
+ "Nickname: {Nickname} <{EMail}>\n"
+ "Monatsbeitrag: {PaymentAmountString} {PaymentAmountCurrency}\n"
+ "Kontostand: {AccountBalanceString} {PaymentAmountCurrency}\n"
+ "\n"
+ "--\n"
+ "Dies ist eine automatisch generierte E-Mail.\n",
};
}
public static FormMail GenerateBalanceAbove200NotifySM() public static FormMail GenerateBalanceAbove200NotifySM()
{ {
//Konto > 200 EUR //Konto > 200 EUR

View File

@ -23,7 +23,7 @@ namespace dezentrale.model
MembershipFee, MembershipFee,
MembershipPayment, MembershipPayment,
MembershipDonation, Donation,
EMail, EMail,

View File

@ -71,6 +71,7 @@ namespace dezentrale.model
private bool paymentNotify = false; private bool paymentNotify = false;
private DateTime memberFormDate; private DateTime memberFormDate;
private DateTime reducedFeeValid; private DateTime reducedFeeValid;
private int debtLevel = 0;
//This is a bit ugly but I didn't have a good idea how to solve it better. //This is a bit ugly but I didn't have a good idea how to solve it better.
//The main goal is to track every change to every property. //The main goal is to track every change to every property.
@ -112,11 +113,11 @@ namespace dezentrale.model
[XmlElement] public DateTime MemberFormDate { get { return memberFormDate; } set { LogPropertyChange("MemberFormDate", memberFormDate, value); memberFormDate = value; } } [XmlElement] public DateTime MemberFormDate { get { return memberFormDate; } set { LogPropertyChange("MemberFormDate", memberFormDate, value); memberFormDate = value; } }
//internal management data //internal management data
[XmlElement] public int DebtLevel { get { return debtLevel; } set { LogPropertyChange("DebtLevel", debtLevel, value); debtLevel = value; } }
[XmlElement] public DateTime GreetedDate { get; set; } [XmlElement] public DateTime GreetedDate { get; set; }
[XmlElement] public DateTime LastPaymentProcessed { get; set; } = DateTime.Now; [XmlElement] public DateTime LastPaymentProcessed { get; set; } = DateTime.Now;
[XmlElement] public uint PaymentsTotal { get; set; } [XmlElement] public uint PaymentsTotal { get; set; }
[XmlElement] public DateTime LastBalanceDegrade { get; set; } = DateTime.Now; [XmlElement] public DateTime LastBalanceDegrade { get; set; } = DateTime.Now;
[XmlElement] public int DebtLevel { get; set; } = 0;
[XmlElement] public DateTime LastCronjobBalanceMail { get; set; } = DateTime.Now; [XmlElement] public DateTime LastCronjobBalanceMail { get; set; } = DateTime.Now;
[XmlElement] public DateTime LastCronjobReducedFeeMail { get; set; } = DateTime.Now; [XmlElement] public DateTime LastCronjobReducedFeeMail { get; set; } = DateTime.Now;
[XmlElement] public DateTime MvEventDate { get; set; } [XmlElement] public DateTime MvEventDate { get; set; }
@ -160,9 +161,9 @@ namespace dezentrale.model
else return number.CompareTo(other.Number); else return number.CompareTo(other.Number);
} }
public bool SaveToFile(bool finishLog = true) public bool SaveToFile(string comment = null, bool finishLog = true)
{ {
if (finishLog) FinishLogEvent(); if (finishLog) FinishLogEvent(comment);
string completePath = System.IO.Path.Combine(Program.config.DbDirectory, GetFileName()); string completePath = System.IO.Path.Combine(Program.config.DbDirectory, GetFileName());
Program.config.DbChangedSinceExport = true; Program.config.DbChangedSinceExport = true;
Program.config.LastDbLocalChange = DateTime.Now; Program.config.LastDbLocalChange = DateTime.Now;
@ -174,23 +175,23 @@ namespace dezentrale.model
public bool CheckBankTransfer(BankTransfer t) public bool CheckBankTransfer(BankTransfer t)
{ {
if (!evaluateAccountInCharge) return false; if (!evaluateAccountInCharge) return false;
if (!bankAccountInCharge.Equals(t.IBAN))
//on banktransfers, AccountInCharge is the receiver IBAN, we need the sender IBAN here
return false;
if (string.IsNullOrEmpty(BankTransferRegEx))
return true;
try try
{ {
return Regex.IsMatch(t.TransferReason, BankTransferRegEx); if (!string.IsNullOrEmpty(BankTransferRegEx))
{
if (Regex.IsMatch(t.TransferReason, BankTransferRegEx))
return true;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Member {this.Number:D3} invalid RegEx: {ex.Message}"); Console.WriteLine($"Member {this.Number:D3} invalid RegEx: {ex.Message}");
return false;
} }
return bankAccountInCharge.Equals(t.IBAN);
//on banktransfers, AccountInCharge is the receiver IBAN, we need the sender IBAN here
} }
private void PaymentAdjustBalance(Int64 amount, string user = null) private void PaymentAdjustBalance(Int64 amount, string user = null)
@ -243,8 +244,8 @@ namespace dezentrale.model
LogEvent.eEventType evt = LogEvent.eEventType.Generic; LogEvent.eEventType evt = LogEvent.eEventType.Generic;
switch (t.TransferType) switch (t.TransferType)
{ {
case MoneyTransfer.eTransferType.MembershipDonation: case MoneyTransfer.eTransferType.Donation:
evt = LogEvent.eEventType.MembershipDonation; break; evt = LogEvent.eEventType.Donation; break;
case MoneyTransfer.eTransferType.MembershipFee: case MoneyTransfer.eTransferType.MembershipFee:
evt = LogEvent.eEventType.MembershipFee; break; evt = LogEvent.eEventType.MembershipFee; break;
case MoneyTransfer.eTransferType.MembershipPayment: case MoneyTransfer.eTransferType.MembershipPayment:
@ -264,38 +265,47 @@ namespace dezentrale.model
PaymentAdjustBalance(t.Amount); PaymentAdjustBalance(t.Amount);
LastPaymentProcessed = DateTime.Now; LastPaymentProcessed = DateTime.Now;
//find out if we need to send a mail to Schatzmeister (i.e. amount is odd in respect to the monthly fee) //find out if we need to send a mail to Schatzmeister (i.e. amount is odd in respect to the monthly fee)
Int64 months = t.Amount / (Int64)PaymentAmount; Int64 months = t.Amount / (Int64)PaymentAmount;
bool odd = months * (Int64)PaymentAmount != t.Amount; bool odd = months * (Int64)PaymentAmount != t.Amount;
if (odd)
ConfigEMail smContact = Program.config.Schatzmeister;
FormMail fm = null;
if (this.Status == eStatus.Deleted || this.Status == eStatus.Disabled)
{ {
Member sm = Program.members.Find(eRole.Schatzmeister); fm = FormMail.GenerateDisabledMemberGettingPayments2SM();
if (sm == null) }
Console.WriteLine("Member.ApplyMoneyTransfer(): Error - Schatzmeister account not found!"); else if (odd)
else {
fm = new FormMail()
{
To = $"{smContact}",
Subject = $"Schiefe Zahlung von Mitglied {Number} ({Nickname}, {t.AmountString} {t.Currency})",
Body = "s. Betreff.\n"
+ $"Type = {t.GetType()}\n"
+ $"TransferAmount = {t.AmountString}\n"
+ $"PaymentAmount = {PaymentAmountString } (monthly fee)\n"
+ $"accountBalance = {AccountBalanceString} (new)\n"
+ $"Next payment for this member is due at {PaymentDueMonth}\n"
+ "\n\n--\n(automatische mail)"
};
}
if (fm != null)
{
Console.WriteLine($"Member.ApplyMoneyTransfer(): sm={smContact}");
try
{ {
Console.WriteLine($"Member.ApplyMoneyTransfer(): sm={sm.Nickname}"); CurrentLog.SubEvents.Add(fm.Send());
FormMail fm = new FormMail() }
{ catch (Exception ex)
To = $"{sm.EMailName} <{sm.EMail}>", {
Subject = $"Schiefe Zahlung von Mitglied {Number} ({Nickname}, {t.AmountString} {t.Currency})", CurrentLog.SubEvents.Add(new LogSubEvent() { Type = LogEvent.eEventType.Error, Topic = "Email notification error", Details = ex.Message, });
Body = "s. Betreff.\n"
+ $"Type = {t.GetType()}\n"
+ $"TransferAmount = {t.AmountString}\n"
+ $"PaymentAmount = {PaymentAmountString } (monthly fee)\n"
+ $"accountBalance = {AccountBalanceString} (new)\n"
+ $"Next payment for this member is due at {PaymentDueMonth}\n"
+ "\n\n--\n(automatische mail)"
};
try
{
CurrentLog.SubEvents.Add(fm.Send());
} catch(Exception ex)
{
CurrentLog.SubEvents.Add(new LogSubEvent() { Type = LogEvent.eEventType.Error, Topic = "Email notification error", Details = ex.Message, });
}
} }
} }
if (paymentNotify) if (paymentNotify)
{ {
FormMail notify = FormMail.GenerateMemberPaymentNotify(odd).ReplaceReflect(t); FormMail notify = FormMail.GenerateMemberPaymentNotify(odd).ReplaceReflect(t);
@ -310,7 +320,7 @@ namespace dezentrale.model
} }
} }
try { SaveToFile(false); } try { SaveToFile(null, false); }
catch (Exception ex) { Console.WriteLine($"Error while saving member: {ex.Message}"); } catch (Exception ex) { Console.WriteLine($"Error while saving member: {ex.Message}"); }
} }
} }

View File

@ -8,6 +8,7 @@ namespace dezentrale.model
{ {
public class MemberImportExport public class MemberImportExport
{ {
[XmlAttribute] public bool Enabled { get; set; } = false;
[XmlElement] public string ZipFile { get; set; } = "fnord.zip"; [XmlElement] public string ZipFile { get; set; } = "fnord.zip";
[XmlElement] public string ZipPassword { get; set; } = ""; [XmlElement] public string ZipPassword { get; set; } = "";
[XmlElement] public bool GpgEnabled { get; set; } = true; [XmlElement] public bool GpgEnabled { get; set; } = true;

View File

@ -6,7 +6,7 @@ namespace dezentrale.model
{ {
public MemberReport(bool memberList = false) public MemberReport(bool memberList = false)
{ {
To = "{Nickname} <{EMail}>"; To = "{EMailName} <{EMail}>";
Subject = $"Automatic member statistics {DateTime.Now.ToString("yyyy-MM-dd")}"; Subject = $"Automatic member statistics {DateTime.Now.ToString("yyyy-MM-dd")}";
int unGreetedMembers = 0; int unGreetedMembers = 0;

View File

@ -45,8 +45,12 @@ namespace dezentrale.model
}); });
} }
} }
public void FinishLogEvent() public void FinishLogEvent(string comment = null)
{ {
if(CurrentLog != null && !string.IsNullOrEmpty(comment))
{
CurrentLog.Details = string.IsNullOrEmpty(CurrentLog.Details) ? comment : CurrentLog.Details + $"\n{comment}";
}
CurrentLog = null; CurrentLog = null;
} }
} }

View File

@ -22,11 +22,20 @@ namespace dezentrale.model.money
[XmlElement] public string RecipientOrDebitor { get; set; } = ""; [XmlElement] public string RecipientOrDebitor { get; set; } = "";
[XmlElement] public string IBAN { get; set; } = ""; [XmlElement] public string IBAN { get; set; } = "";
[XmlElement] public string BIC { get; set; } = ""; [XmlElement] public string BIC { get; set; } = "";
[XmlElement] public string Info { get; set; } = ""; [XmlElement] public string Info { get; set; } = "";
//During the CSV import process, generated BankTransfer objects will hold CsvFile/Line for later
//logging into member data / Main log (i.e. it is possible to track a MoneyTransfer from a
//Member object to a line from a specific csv file)
[XmlIgnore] public string CsvFile { get; set; } = null;
[XmlIgnore] public int CsvLine { get; set; } = 0;
public BankTransfer() : base() { } public BankTransfer() : base() { }
public BankTransfer(List<string> csvHeadline, List<string> csvEntry) : base() public BankTransfer(List<string> csvHeadline, List<string> csvEntry, string csvFile = "", int csvLineNumber = 0) : base()
{ {
CsvFile = csvFile;
CsvLine = csvLineNumber;
int lc = csvEntry.Count; int lc = csvEntry.Count;
if (lc > csvHeadline.Count) lc = csvHeadline.Count; if (lc > csvHeadline.Count) lc = csvHeadline.Count;
for (int i = 0; i < lc; i++) for (int i = 0; i < lc; i++)
@ -58,7 +67,7 @@ namespace dezentrale.model.money
case "Waehrung": Currency = csvEntry[i]; break; case "Waehrung": Currency = csvEntry[i]; break;
case "Info": Info = csvEntry[i]; break; case "Info": Info = csvEntry[i]; break;
default: default:
throw new Exception($"invalid csv headline field: \"{csvHeadline[i]}\""); throw new Exception($"invalid csv headline field: \"{csvHeadline[i]}\" csvFile {csvFile} line {csvLineNumber}");
} }
} }
} }

View File

@ -13,7 +13,7 @@ namespace dezentrale.model.money
{ {
MembershipFee = LogEvent.eEventType.MembershipFee, MembershipFee = LogEvent.eEventType.MembershipFee,
MembershipPayment = LogEvent.eEventType.MembershipPayment, MembershipPayment = LogEvent.eEventType.MembershipPayment,
MembershipDonation = LogEvent.eEventType.MembershipDonation, Donation = LogEvent.eEventType.Donation,
Unassigned, Unassigned,
RunningCost, RunningCost,
Ignored, Ignored,
@ -42,7 +42,8 @@ namespace dezentrale.model.money
public bool Equals(MoneyTransfer other) { return Equals(other, true); } public bool Equals(MoneyTransfer other) { return Equals(other, true); }
public bool Equals(MoneyTransfer other, bool evaluateReason) public bool Equals(MoneyTransfer other, bool evaluateReason)
{ {
#warning This doesn't respect the account ID and may lead to false positives.
return this.ValutaDate.Equals(other.ValutaDate) return this.ValutaDate.Equals(other.ValutaDate)
&& (this.Amount == other.Amount) && (this.Amount == other.Amount)
&& this.GetType().Equals(other.GetType()) && this.GetType().Equals(other.GetType())

View File

@ -157,7 +157,7 @@ namespace dezentrale.view
void CustomListView_MouseUp(object sender, MouseEventArgs e) void CustomListView_MouseUp(object sender, MouseEventArgs e)
{ {
Console.WriteLine($"MouseUp(): MouseButtons={Control.MouseButtons}"); //Console.WriteLine($"MouseUp(): MouseButtons={Control.MouseButtons}");
if (resizeWhileMouseDown && (resizedWhileMouseDown != null)) if (resizeWhileMouseDown && (resizedWhileMouseDown != null))
{ {
//mono: Store new width. //mono: Store new width.

41
view/frmCommentChanges.cs Normal file
View File

@ -0,0 +1,41 @@
using System;
//using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace dezentrale.view
{
public class frmCommentChanges : FormWithActionButtons
{
public string Comment { get; private set; } = "";
private TextBox tbComment;
public frmCommentChanges(string captionEntity, string changes)
{
this.StartPosition = FormStartPosition.CenterParent;
this.Size = new System.Drawing.Size(800, 600);
this.Text = $"Comment changes to \"{captionEntity}\"";
int w = this.ClientSize.Width;
int h = this.ClientSize.Height - 35;
this.Controls.Add(tbComment = new TextBox()
{
Location = new Point(groupOffset, 0 * line + tm + groupOffset),
Size = new Size(w - 2 * groupOffset, h - 12 - tm),
Multiline = true,
ScrollBars = ScrollBars.Both,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
});
AddButton("Ok", btnOk_Click);
}
private void btnOk_Click(object sender, EventArgs e)
{
Comment = tbComment.Text;
this.Close();
}
}
}

View File

@ -33,8 +33,13 @@ namespace dezentrale.view
//Vorstand settings //Vorstand settings
private TextBox tbVSName; private TextBox tbVSName;
private TextBox tbVSEmail; private TextBox tbVSEmail;
private TextBox tbSMName;
private TextBox tbSMEmail;
private TextBox tbSFName;
private TextBox tbSFEmail;
//Import / Export settings //Import / Export settings
private CheckBox cbIeEnabled;
private TextBox tbIeZipFile; private TextBox tbIeZipFile;
private TextBox tbIeZipPassword; private TextBox tbIeZipPassword;
private CheckBox cbIeGpgEnabled; private CheckBox cbIeGpgEnabled;
@ -237,7 +242,7 @@ namespace dezentrale.view
gui.Controls.Add(new Label() gui.Controls.Add(new Label()
{ {
Text = "Name:", Text = "Vorstand:",
Location = new Point(lm, 9 * line + tm + labelOffs), Location = new Point(lm, 9 * line + tm + labelOffs),
Size = new Size(110, labelHeight), Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight, TextAlign = ContentAlignment.BottomRight,
@ -250,7 +255,7 @@ namespace dezentrale.view
}); });
gui.Controls.Add(new Label() gui.Controls.Add(new Label()
{ {
Text = "E-Mail:", Text = "VS-Mail:",
Location = new Point(lm, 10 * line + tm + labelOffs), Location = new Point(lm, 10 * line + tm + labelOffs),
Size = new Size(110, labelHeight), Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight, TextAlign = ContentAlignment.BottomRight,
@ -261,6 +266,60 @@ namespace dezentrale.view
Width = 200, Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left, Anchor = AnchorStyles.Top | AnchorStyles.Left,
}); });
gui.Controls.Add(new Label()
{
Text = "Schatzmeister:",
Location = new Point(lm, 11 * line + tm + labelOffs),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
gui.Controls.Add(tbSMName = new TextBox()
{
Location = new Point(lm + 113, 11 * line + tm),
Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left,
});
gui.Controls.Add(new Label()
{
Text = "SM-Mail:",
Location = new Point(lm, 12 * line + tm + labelOffs),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
gui.Controls.Add(tbSMEmail = new TextBox()
{
Location = new Point(lm + 113, 12 * line + tm),
Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left,
});
gui.Controls.Add(new Label()
{
Text = "Schriftfuehrer:",
Location = new Point(lm, 13 * line + tm + labelOffs),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
gui.Controls.Add(tbSFName = new TextBox()
{
Location = new Point(lm + 113, 13 * line + tm),
Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left,
});
gui.Controls.Add(new Label()
{
Text = "SF-Mail:",
Location = new Point(lm, 14 * line + tm + labelOffs),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
gui.Controls.Add(tbSFEmail = new TextBox()
{
Location = new Point(lm + 113, 14 * line + tm),
Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left,
});
cbSmtpEnabled.Checked = Program.config.Smtp.Enabled; cbSmtpEnabled.Checked = Program.config.Smtp.Enabled;
tbSmtpHost.Text = Program.config.Smtp.Host; tbSmtpHost.Text = Program.config.Smtp.Host;
@ -271,8 +330,12 @@ namespace dezentrale.view
tbSmtpPassword.Text = Program.config.Smtp.Password; tbSmtpPassword.Text = Program.config.Smtp.Password;
tbSmtpCcTo.Text = Program.config.Smtp.CcTo; tbSmtpCcTo.Text = Program.config.Smtp.CcTo;
tbVSName.Text = Program.config.VS.VSName; tbVSName.Text = Program.config.VS.EMailName;
tbVSEmail.Text = Program.config.VS.VSEmail; tbVSEmail.Text = Program.config.VS.EMail;
tbSMName.Text = Program.config.Schatzmeister.EMailName;
tbSMEmail.Text = Program.config.Schatzmeister.EMail;
tbSFName.Text = Program.config.Schriftfuehrer.EMailName;
tbSFEmail.Text = Program.config.Schriftfuehrer.EMail;
return gui; return gui;
} }
public TabPage BuildMoneyTransfersGui() public TabPage BuildMoneyTransfersGui()
@ -286,16 +349,24 @@ namespace dezentrale.view
{ {
TabPage gui = new TabPage("Import / Export"); TabPage gui = new TabPage("Import / Export");
gui.Controls.Add(cbIeEnabled = new CheckBox()
{
Text = "Enable Import/Export with the settings below",
Location = new Point(lm + 113, 0 * line + tm),
Width = 400,
});
gui.Controls.Add(new Label() gui.Controls.Add(new Label()
{ {
Text = "Zip filename:", Text = "Zip filename:",
Location = new Point(lm, 0 * line + tm + labelOffs), Location = new Point(lm, 1 * line + tm + labelOffs),
Size = new Size(110, labelHeight), Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight, TextAlign = ContentAlignment.BottomRight,
}); });
gui.Controls.Add(tbIeZipFile = new TextBox() gui.Controls.Add(tbIeZipFile = new TextBox()
{ {
Location = new Point(lm + 113, 0 * line + tm), Location = new Point(lm + 113, 1 * line + tm),
Width = 200, Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left, Anchor = AnchorStyles.Top | AnchorStyles.Left,
}); });
@ -303,14 +374,14 @@ namespace dezentrale.view
gui.Controls.Add(new Label() gui.Controls.Add(new Label()
{ {
Text = "Zip password:", Text = "Zip password:",
Location = new Point(lm, 1 * line + tm + labelOffs), Location = new Point(lm, 2 * line + tm + labelOffs),
Size = new Size(110, labelHeight), Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight, TextAlign = ContentAlignment.BottomRight,
}); });
gui.Controls.Add(tbIeZipPassword = new TextBox() gui.Controls.Add(tbIeZipPassword = new TextBox()
{ {
PasswordChar = '*', PasswordChar = '*',
Location = new Point(lm + 113, 1 * line + tm), Location = new Point(lm + 113, 2 * line + tm),
Width = 200, Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left, Anchor = AnchorStyles.Top | AnchorStyles.Left,
}); });
@ -318,20 +389,20 @@ namespace dezentrale.view
gui.Controls.Add(cbIeGpgEnabled = new CheckBox() gui.Controls.Add(cbIeGpgEnabled = new CheckBox()
{ {
Text = "Enable file encryption with GPG (AES + password)", Text = "Enable file encryption with GPG (AES + password)",
Location = new Point(lm + 113, 2 * line + tm), Location = new Point(lm + 113, 3 * line + tm),
Width = 400, Width = 400,
}); });
gui.Controls.Add(new Label() gui.Controls.Add(new Label()
{ {
Text = "Gpg filename:", Text = "Gpg filename:",
Location = new Point(lm, 3 * line + tm + labelOffs), Location = new Point(lm, 4 * line + tm + labelOffs),
Size = new Size(110, labelHeight), Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight, TextAlign = ContentAlignment.BottomRight,
}); });
gui.Controls.Add(tbIeGpgFile = new TextBox() gui.Controls.Add(tbIeGpgFile = new TextBox()
{ {
Location = new Point(lm + 113, 3 * line + tm), Location = new Point(lm + 113, 4 * line + tm),
Width = 200, Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left, Anchor = AnchorStyles.Top | AnchorStyles.Left,
}); });
@ -339,48 +410,33 @@ namespace dezentrale.view
gui.Controls.Add(new Label() gui.Controls.Add(new Label()
{ {
Text = "Gpg password:", Text = "Gpg password:",
Location = new Point(lm, 4 * line + tm + labelOffs), Location = new Point(lm, 5 * line + tm + labelOffs),
Size = new Size(110, labelHeight), Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight, TextAlign = ContentAlignment.BottomRight,
}); });
gui.Controls.Add(tbIeGpgPassword = new TextBox() gui.Controls.Add(tbIeGpgPassword = new TextBox()
{ {
PasswordChar = '*', PasswordChar = '*',
Location = new Point(lm + 113, 4 * line + tm), Location = new Point(lm + 113, 5 * line + tm),
Width = 200, Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left, Anchor = AnchorStyles.Top | AnchorStyles.Left,
}); });
gui.Controls.Add(cbIeHgEnabled = new CheckBox() gui.Controls.Add(cbIeHgEnabled = new CheckBox()
{ {
Text = "Enable data synchronisation over mercurial", Text = "Enable data synchronisation over mercurial",
Location = new Point(lm + 113, 5 * line + tm), Location = new Point(lm + 113, 6 * line + tm),
Width = 400, Width = 400,
}); });
gui.Controls.Add(new Label() gui.Controls.Add(new Label()
{ {
Text = "HG UserName:", Text = "HG UserName:",
Location = new Point(lm, 6 * line + tm + labelOffs), Location = new Point(lm, 7 * line + tm + labelOffs),
Size = new Size(110, labelHeight), Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight, TextAlign = ContentAlignment.BottomRight,
}); });
gui.Controls.Add(tbIeHgUserName = new TextBox() gui.Controls.Add(tbIeHgUserName = new TextBox()
{ {
Location = new Point(lm + 113, 6 * line + tm),
Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left,
});
gui.Controls.Add(new Label()
{
Text = "HG password:",
Location = new Point(lm, 7 * line + tm + labelOffs),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
gui.Controls.Add(tbIeHgPassword = new TextBox()
{
PasswordChar = '*',
Location = new Point(lm + 113, 7 * line + tm), Location = new Point(lm + 113, 7 * line + tm),
Width = 200, Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left, Anchor = AnchorStyles.Top | AnchorStyles.Left,
@ -388,18 +444,34 @@ namespace dezentrale.view
gui.Controls.Add(new Label() gui.Controls.Add(new Label()
{ {
Text = "HG URL:", Text = "HG password:",
Location = new Point(lm, 8 * line + tm + labelOffs), Location = new Point(lm, 8 * line + tm + labelOffs),
Size = new Size(110, labelHeight), Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight, TextAlign = ContentAlignment.BottomRight,
}); });
gui.Controls.Add(tbIeHgPassword = new TextBox()
{
PasswordChar = '*',
Location = new Point(lm + 113, 8 * line + tm),
Width = 200,
Anchor = AnchorStyles.Top | AnchorStyles.Left,
});
gui.Controls.Add(new Label()
{
Text = "HG URL:",
Location = new Point(lm, 9 * line + tm + labelOffs),
Size = new Size(110, labelHeight),
TextAlign = ContentAlignment.BottomRight,
});
gui.Controls.Add(tbIeHgURL = new TextBox() gui.Controls.Add(tbIeHgURL = new TextBox()
{ {
Location = new Point(lm + 113, 8 * line + tm), Location = new Point(lm + 113, 9 * line + tm),
Width = 400, Width = 400,
Anchor = AnchorStyles.Top | AnchorStyles.Left, Anchor = AnchorStyles.Top | AnchorStyles.Left,
}); });
cbIeEnabled.Checked = Program.config.ImportExport.Enabled;
tbIeZipFile.Text = Program.config.ImportExport.ZipFile; tbIeZipFile.Text = Program.config.ImportExport.ZipFile;
tbIeZipPassword.Text = Program.config.ImportExport.ZipPassword; tbIeZipPassword.Text = Program.config.ImportExport.ZipPassword;
cbIeGpgEnabled.Checked = Program.config.ImportExport.GpgEnabled; cbIeGpgEnabled.Checked = Program.config.ImportExport.GpgEnabled;
@ -472,12 +544,17 @@ namespace dezentrale.view
Program.config.Smtp.Password = tbSmtpPassword.Text; Program.config.Smtp.Password = tbSmtpPassword.Text;
Program.config.Smtp.CcTo = tbSmtpCcTo.Text; Program.config.Smtp.CcTo = tbSmtpCcTo.Text;
Program.config.VS.VSName = tbVSName.Text; Program.config.VS.EMailName = tbVSName.Text;
Program.config.VS.VSEmail = tbVSEmail.Text; Program.config.VS.EMail = tbVSEmail.Text;
Program.config.Schatzmeister.EMailName = tbSMName.Text;
Program.config.Schatzmeister.EMail = tbSMEmail.Text;
Program.config.Schriftfuehrer.EMailName = tbSFName.Text;
Program.config.Schriftfuehrer.EMail = tbSFEmail.Text;
//use MoneyTransferList for that, not Program.config //use MoneyTransferList for that, not Program.config
//[XmlElement] public List<KeyValue> MoneyTransferRegEx { get; set; } = new List<KeyValue>(); //[XmlElement] public List<KeyValue> MoneyTransferRegEx { get; set; } = new List<KeyValue>();
Program.config.ImportExport.Enabled = cbIeEnabled.Checked;
Program.config.ImportExport.ZipFile = tbIeZipFile.Text; Program.config.ImportExport.ZipFile = tbIeZipFile.Text;
Program.config.ImportExport.ZipPassword = tbIeZipPassword.Text; Program.config.ImportExport.ZipPassword = tbIeZipPassword.Text;
Program.config.ImportExport.GpgEnabled = cbIeGpgEnabled.Checked; Program.config.ImportExport.GpgEnabled = cbIeGpgEnabled.Checked;

View File

@ -388,7 +388,7 @@ namespace dezentrale.view
Width = 100, Width = 100,
Anchor = AnchorStyles.Top | AnchorStyles.Left, Anchor = AnchorStyles.Top | AnchorStyles.Left,
TextAlign = HorizontalAlignment.Right, TextAlign = HorizontalAlignment.Right,
ReadOnly = true, //ReadOnly = true,
}); });
parent.Controls.Add(new Label() parent.Controls.Add(new Label()
{ {
@ -695,9 +695,17 @@ namespace dezentrale.view
member.BankTransferRegEx = tbBankTransferRegEx.Text; member.BankTransferRegEx = tbBankTransferRegEx.Text;
member.Remarks = tbRemarks.Text; member.Remarks = tbRemarks.Text;
member.FinishLogEvent(); //member.FinishLogEvent(); //implicit in SaveToFile()
this.DialogResult = DialogResult.OK; this.DialogResult = DialogResult.OK;
member.SaveToFile();
string comment = null;
if (!newMember)
{
frmCommentChanges commentChanges = new frmCommentChanges($"{member.Number:D3} ({ member.Nickname})", "");
commentChanges.ShowDialog();
comment = commentChanges.Comment;
}
member.SaveToFile(comment);
this.Close(); this.Close();
} }

View File

@ -15,6 +15,7 @@ namespace dezentrale.view
private LVMoneyTransfers mtv; private LVMoneyTransfers mtv;
private LvMv lstMv; private LvMv lstMv;
private MenuItem mnuFileExport, mnuFileImport;
private void BuildMoneyTransfers(Control parent) private void BuildMoneyTransfers(Control parent)
{ {
parent.Controls.Add(mtv = new LVMoneyTransfers() { Dock = DockStyle.Fill, }); parent.Controls.Add(mtv = new LVMoneyTransfers() { Dock = DockStyle.Fill, });
@ -31,7 +32,7 @@ namespace dezentrale.view
this.Size = new Size(640, 480); this.Size = new Size(640, 480);
this.FormClosing += (sender, e) => this.FormClosing += (sender, e) =>
{ {
if(!enforceClosing && Program.config.DbChangedSinceExport) if(!enforceClosing && Program.config.DbChangedSinceExport && Program.config.ImportExport.Enabled)
{ {
DialogResult dr = DialogResult dr =
MessageBox.Show("Database changed since last export.\r\n\r\n" MessageBox.Show("Database changed since last export.\r\n\r\n"
@ -51,8 +52,8 @@ namespace dezentrale.view
{ MenuItems = { { MenuItems = {
new MenuItem("&Configuration...", mnuMain_File_Configuration) { Enabled = true }, new MenuItem("&Configuration...", mnuMain_File_Configuration) { Enabled = true },
new MenuItem("-"), new MenuItem("-"),
new MenuItem("&Export database", mnuMain_File_Export), (mnuFileExport = new MenuItem("&Export database", mnuMain_File_Export)),
new MenuItem("&Import database", mnuMain_File_Import), (mnuFileImport = new MenuItem("&Import database", mnuMain_File_Import)),
new MenuItem("-"), new MenuItem("-"),
new MenuItem("&Quit", mnuMain_File_Quit), new MenuItem("&Quit", mnuMain_File_Quit),
} }, } },
@ -88,6 +89,8 @@ namespace dezentrale.view
} }
}; };
mnuFileImport.Enabled = mnuFileExport.Enabled = Program.config.ImportExport.Enabled;
TabPage tabMembers = new TabPage("Members"); TabPage tabMembers = new TabPage("Members");
tabMembers.Controls.Add(lstMembers = new LVMembers() { Dock = DockStyle.Fill }); tabMembers.Controls.Add(lstMembers = new LVMembers() { Dock = DockStyle.Fill });
@ -129,32 +132,35 @@ namespace dezentrale.view
lstMv.LoadFromList(Program.mvList.Entries); lstMv.LoadFromList(Program.mvList.Entries);
//Check for needed import //Check for needed import
DateTime now = DateTime.Now; if (Program.config.ImportExport.Enabled)
int totalHours = (int) now.Subtract(Program.config.LastDbImport).TotalHours;
int totalDays = totalHours / 24;
if (totalHours >= 8)
{ {
totalHours %= 24; DateTime now = DateTime.Now;
string timeSpan = $"{totalDays} d, {totalHours} h"; int totalHours = (int) now.Subtract(Program.config.LastDbImport).TotalHours;
int totalDays = totalHours / 24;
if (Program.config.DbChangedSinceExport) if (totalHours >= 8)
{ {
MessageBox.Show( "Warning: There are local changes to DB since last export!\r\n" totalHours %= 24;
+ "Please check if there are changes on the server side and then\r\n" string timeSpan = $"{totalDays} d, {totalHours} h";
+ "- perform a manual backup-import-merge\r\n"
+ "- or simply an export if there are no changes.\r\n\r\n" if (Program.config.DbChangedSinceExport)
+ $"Last import was: {Program.config.LastDbImport}\r\n"
+ $"Last export was: {Program.config.LastDbExport}\r\n"
+ $"Last db change was {Program.config.LastDbLocalChange}\r\n");
}
else
{
if (totalDays > 365) timeSpan = "too long";
DialogResult dr = MessageBox.Show($"Last Db import was {timeSpan} ago.\r\nImport now?", "Import database", MessageBoxButtons.YesNo);
if (dr == DialogResult.Yes)
{ {
mnuMain_File_Import(null, null); MessageBox.Show( "Warning: There are local changes to DB since last export!\r\n"
+ "Please check if there are changes on the server side and then\r\n"
+ "- perform a manual backup-import-merge\r\n"
+ "- or simply an export if there are no changes.\r\n\r\n"
+ $"Last import was: {Program.config.LastDbImport}\r\n"
+ $"Last export was: {Program.config.LastDbExport}\r\n"
+ $"Last db change was {Program.config.LastDbLocalChange}\r\n");
} }
else
{
if (totalDays > 365) timeSpan = "too long";
DialogResult dr = MessageBox.Show($"Last Db import was {timeSpan} ago.\r\nImport now?", "Import database", MessageBoxButtons.YesNo);
if (dr == DialogResult.Yes)
{
mnuMain_File_Import(null, null);
}
}
} }
} }
} }
@ -165,8 +171,11 @@ namespace dezentrale.view
frmConfig.ShowDialog(); frmConfig.ShowDialog();
if (frmConfig.DialogResult == DialogResult.OK) if (frmConfig.DialogResult == DialogResult.OK)
{ {
frmConfig.FillAndSaveConfig(); frmConfig.FillAndSaveConfig();
if(frmConfig.KeylockCombiChanged && Program.config.Smtp.Enabled)
mnuFileImport.Enabled = mnuFileExport.Enabled = Program.config.ImportExport.Enabled;
if (frmConfig.KeylockCombiChanged && Program.config.Smtp.Enabled)
{ {
DialogResult dr = MessageBox.Show("You've changed the keylock combination.\n Do you want to send an E-Mail to every active member to inform them?", "Keylock-Combi changed", MessageBoxButtons.YesNo); DialogResult dr = MessageBox.Show("You've changed the keylock combination.\n Do you want to send an E-Mail to every active member to inform them?", "Keylock-Combi changed", MessageBoxButtons.YesNo);
if (dr == DialogResult.Yes) if (dr == DialogResult.Yes)
@ -454,7 +463,19 @@ namespace dezentrale.view
DialogResult dr = ofd.ShowDialog(); DialogResult dr = ofd.ShowDialog();
//ofd.FilterIndex //ofd.FilterIndex
if (dr == DialogResult.OK) if (dr == DialogResult.OK)
{
lstMembers.SuspendLayout();
ProcessCsv.ProcessCSV(ofd.FileName); ProcessCsv.ProcessCSV(ofd.FileName);
foreach (Member m in Program.members.Entries) lstMembers.UpdateEntry(m);
if (Program.MoneyTransfersLoaded)
{
//This will enforce reloading the moneytransfer list
Program.MoneyTransfersLoaded = false;
mtv.LoadFromList(Program.MoneyTransfers.Entries);
}
lstMembers.ResumeLayout(false);
}
} }
private void mnuMain_Payments_Receipts(object sender, EventArgs e) private void mnuMain_Payments_Receipts(object sender, EventArgs e)
{ {